pax_global_header00006660000000000000000000000064133766113670014527gustar00rootroot0000000000000052 comment=32160d74cd266a59e81a75b655c16de27b8c7681 dcm2niix-1.0.20181125/000077500000000000000000000000001337661136700140065ustar00rootroot00000000000000dcm2niix-1.0.20181125/.gitignore000066400000000000000000000000521337661136700157730ustar00rootroot00000000000000.DS_Store /build/ /bin/ /console/dcm2niix dcm2niix-1.0.20181125/.gitmodules000066400000000000000000000004271337661136700161660ustar00rootroot00000000000000[submodule "dcm_qa"] path = dcm_qa url = https://github.com/neurolabusc/dcm_qa.git [submodule "dcm_qa_nih"] path = dcm_qa_nih url = https://github.com/neurolabusc/dcm_qa_nih.git [submodule "dcm_qa_uih"] path = dcm_qa_uih url = https://github.com/neurolabusc/dcm_qa_uih.git dcm2niix-1.0.20181125/.travis.yml000066400000000000000000000026131337661136700161210ustar00rootroot00000000000000language: cpp git: depth: 1 matrix: include: - os: linux dist: trusty addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-5 # support c++14 env: - MATRIX_EVAL="CC=gcc-5 && CXX=g++-5" - TARGET=lnx - os: osx osx_image: xcode8.3 # Tracvis default: OS X 10.12.6 and Xcode 8.3.3 env: TARGET=mac before_install: - eval "${MATRIX_EVAL}" - git submodule update --init --depth=3 dcm_qa - git submodule update --init --depth=3 dcm_qa_nih - git submodule update --init --depth=3 dcm_qa_uih script: - mkdir build && cd build && cmake -DBATCH_VERSION=ON -DUSE_OPENJPEG=ON -DUSE_JPEGLS=true -DZLIB_IMPLEMENTATION=Cloudflare .. && make && cd - - export PATH=$PWD/build/bin:$PATH - cd dcm_qa && ./batch.sh && cd - - cd dcm_qa_nih && ./batch.sh && cd - - cd dcm_qa_uih && ./batch.sh && cd - before_deploy: - export DATE=`date +%-d-%b-%Y` - zip -j dcm2niix_${DATE}_${TARGET}.zip build/bin/* - sleep 300 # make sure appveyor deployment is done, thus proper release name is set deploy: provider: releases api_key: secure: sVIYRakcEQdMPEdGSSePtMVCMQvaohqV7NNzEErAgZ+b/4ofv2aPpJb5kNTv3JRl2FrPy7iXJ8lOUQ/95pqvimX6jv5ztksTNXtSMnHZNbjjWwIc99enPY+mSdWMO2lb9vGBWQ9GNfXjmk7MgtDHPjjygbuZfUw9fmGy4ocxkws= file_glob: true file: dcm2niix*.zip skip_cleanup: true on: tags: true dcm2niix-1.0.20181125/BATCH.md000066400000000000000000000014651337661136700151570ustar00rootroot00000000000000**Optional batch processing version:** Perform a batch conversion of multiple dicoms using the configurations specified in a yaml file. ```bash dcm2niibatch batch_config.yml ``` The configuration file should be in yaml format as shown in example `batch_config.yaml` ```yaml Options: isGz: false isFlipY: false isVerbose: false isCreateBIDS: false isOnlySingleFile: false Files: - in_dir: /path/to/first/folder out_dir: /path/to/output/folder filename: dcemri - in_dir: /path/to/second/folder out_dir: /path/to/output/folder filename: fa3 ``` You can add as many files as you want to convert as long as this structure stays consistent. Note that a dash must separate each file. dcm2niix-1.0.20181125/CMakeLists.txt000066400000000000000000000002701337661136700165450ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.11) if(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0003 NEW) endif() project(dcm2niix) include(${CMAKE_SOURCE_DIR}/SuperBuild/SuperBuild.cmake) dcm2niix-1.0.20181125/COMPILE.md000066400000000000000000000323231337661136700154230ustar00rootroot00000000000000## About The README.md file describes the typical compilation of the software, using the `make` command to build the software. This document describes advanced methods for compiling and tuning the software. Beyond the complexity of compiling the software, the only downside to adding optional modules is that the dcm2niix executable size will require a tiny bit more disk space. For example, on MacOS the stripped basic executable is 238kb, miniz (GZip support) adds 18kb, NanoJPEG (lossy JPEG support) adds 13kb, CharLS (JPEG-LS support) adds 271kb, and OpenJPEG (JPEG2000 support) adds 192kb. So with all these features installed the executable weighs in at 732kb. ## Choosing your compiler The text below generally describes how to build dcm2niix using the [GCC](https://gcc.gnu.org) compiler using the `g++` command. However, the code is portable and you can use different compilers. For [clang/llvm](https://clang.llvm.org) compile using `clang++`. If you have the [Intel C compiler](https://software.intel.com/en-us/c-compilers), you can substitute the `icc` command. The code is compatible with Microsoft's VS 2015 or later. For [Microsoft's C compiler](http://landinghub.visualstudio.com/visual-cpp-build-tools) you would use the `cl` command. In theory, the code should support other compilers, but this has not been tested. Be aware that if you do not have gcc installed the `g++` command may use a default to a compiler (e.g. clang). To check what compiler was used, run the dcm2niix software: it always reports the version and the compiler used for the build. ## Building the command line version without cmake You can also build the software without C-make. The easiest way to do this is to run the function "make" from the "console" folder. Note that this only creates the default version of dcm2niix, not the optional batch version described above. The make command simply calls the g++ compiler, and if you want you can tune this for your build. In essence, the make function simply calls ``` g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG ``` The following sub-sections list how you can modify this basic recipe for your needs. ## Trouble Shooting Some [Centos/Redhat](https://github.com/rordenlab/dcm2niix/issues/137) may report "/usr/bin/ld: cannot find -lstdc++". This can be resolved by installing static versions of libstdc++: `yum install libstdc++-static`. To compile with debugging symbols, use ``` cmake -DUSE_OPENJPEG=ON -DCMAKE_CXX_FLAGS=-g .. && make ``` ##### ZLIB BUILD If we have zlib, we can use it (-lz) and disable [miniz](https://code.google.com/p/miniz/) (-myDisableMiniZ) ``` g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -dead_strip -o dcm2niix -lz -DmyDisableMiniZ ``` ##### MINGW BUILD If you use the (obsolete) compiler MinGW on Windows you will want to include the rare libgcc libraries with your executable so others can use it. Here I also demonstrate the optional "-DmyDisableZLib" to remove zip support. ``` g++ -O3 -s -DmyDisableOpenJPEG -DmyDisableZLib -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -static-libgcc ``` ##### DISABLING CLASSIC JPEG DICOM images can be stored as either raw data or compressed using one of many formats as described by the [transfer syntaxes](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Transfer_Syntaxes_and_Compressed_Images). One of the compressed formats is the lossy classic JPEG format (which is separate from and predates the lossy JPEG 2000 format). This software comes with the [NanoJPEG](http://keyj.emphy.de/nanojpeg/) library to handle these images. However, you can use the `myDisableClassicJPEG` compiler switch to remove this dependency. The resulting executable will be smaller but will not be able to convert images stored with this format. ``` g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableClassicJPEG -DmyDisableOpenJPEG ``` ##### USING LIBJPEG-TURBO TO DECODE CLASSIC JPEG By default, classic JPEG images will be decoded using the [compact NanoJPEG decoder](http://keyj.emphy.de/nanojpeg/). However, the compiler directive `myTurboJPEG` will create an executable based on the [libjpeg-turbo](http://www.libjpeg-turbo.org) library. This library is a faster decoder and is the standard for many Linux distributions. On the other hand, the lossy classic JPEG is rarely used for DICOM images, so this compilation has extra dependencies and can result in a larger executable size (for static builds). ``` g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -DmyTurboJPEG -I/opt/libjpeg-turbo/include /opt/libjpeg-turbo/lib/libturbojpeg.a ``` ##### JPEG-LS BUILD You can compile dcm2niix to convert DICOM images compressed with the [JPEG-LS](https://en.wikipedia.org/wiki/JPEG_2000) [transfer syntaxes 1.2.840.10008.1.2.4.80 and 1.2.840.10008.1.2.4.81](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Transfer_Syntaxes_and_Compressed_Images). Decoding this format is handled by the [CharLS library](https://github.com/team-charls/charls), which is included with dcm2niix in the `charls` folder. The included code was downloaded from the CharLS website on 6 June 2018. To enable support you will need to include the `myEnableJPEGLS` compiler flag as well as a few file sin the `charls` folder. Therefore, a minimal compile (with just JPEG-LS and without JPEG2000) should look like this: `g++ -I. -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG` Alternatively, you can decompress an image in JPEG-LS to an uncompressed DICOM using [gdcmconv](https://github.com/malaterre/GDCM)(e.g. `gdcmconv -w 3691459 3691459.dcm`). Or you can use gdcmconv compress a DICOM to JPEG-LS (e.g. `gdcmconv -L 3691459 3691459.dcm`). Alternatively, the DCMTK tool [dcmcjpls](https://support.dcmtk.org/docs/dcmcjpls.html) provides JPEG-LS support. ##### JPEG2000 BUILD You can compile dcm2niix to convert DICOM images compressed with the [JPEG2000](https://en.wikipedia.org/wiki/JPEG_2000) [transfer syntaxes 1.2.840.10008.1.2.4.90 and 1.2.840.10008.1.2.4.91](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Transfer_Syntaxes_and_Compressed_Images). This is optional, as JPEG2000 is very rare in DICOMs (usually only created by the proprietary DCMJP2K or OsiriX). Due to the challenges discussed below this is a poor choice for archiving DICOM data. Rather than support conversion with dcm2niix, a better solution would be to use DCMJP2K to do a DICOM-to-DICOM conversion to a more widely supported transfer syntax. Unfortunately, JPEG2000 saw poor adoption as a general image format. This situation is unlikely to change, as JPEG2000 only offered incremental benefits over the simpler classic JPEG, and is outperformed by the more recent [HEIF](https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format). This has implications for DICOM, as there is little active development on libraries to decode JPEG2000. Indeed, the two popular open-source libraries that decode JPEG2000 have serious limitations for processing these images. Some JPEG2000 DICOM images can not be decoded by the default compilation of OpenJPEG library after version [2.1.0](https://github.com/uclouvain/openjpeg/issues/962). On the other hand, the Jasper library does not handle lossy [16-bit](https://en.wikipedia.org/wiki/JPEG_2000) images with good precision. You can build dcm2niix with JPEG2000 decompression support using OpenJPEG 2.1.0. You will need to have the OpenJPEG library installed (use the package manager of your Linux distribution, Homebrew for macOS, or see [here](https://github.com/uclouvain/openjpeg/blob/master/INSTALL.md) if you want to build it yourself). If you want to use a more recent version of OpenJPEG, it must be custom-compiled with `-DOPJ_DISABLE_TPSOT_FIX` compiler flag. I suggest building static libraries where you would [download the code](https://github.com/uclouvain/openjpeg) and run ``` cmake -DBUILD_SHARED_LIBS:bool=off -DOPJ_DISABLE_TPSOT_FIX:bool=on . make sudo make install ``` You should then be able to run: ``` g++ -O3 -dead_strip -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -lopenjp2 ``` But in my experience this works best if you explicitly tell the software how to find the libraries, so your compile will probably look like one of these options: ``` #for MacOS g++ -O3 -dead_strip -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -I/usr/local/include/openjpeg-2.1 /usr/local/lib/libopenjp2.a ``` ``` #For older Linux g++ -O3 -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -I/usr/local/lib /usr/local/lib/libopenjp2.a ``` ``` #For modern Linux g++ -O3 -s -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -lpthread -o dcm2niix -I/usr/local/include/openjpeg-2.2 ~/openjpeg-master/build/bin/libopenjp2.a ``` If you want to build this with JPEG2000 decompression support using Jasper: You will need to have the Jasper (http://www.ece.uvic.ca/~frodo/jasper/) and libjpeg (http://www.ijg.org) libraries installed which for Linux users may be as easy as running 'sudo apt-get install libjasper-dev' (otherwise, see http://www.ece.uvic.ca/~frodo/jasper/#doc). You can then run: ``` g++ -O3 -DmyDisableOpenJPEG -DmyEnableJasper -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -s -o dcm2niix -ljasper -ljpeg ``` ##### VISUAL STUDIO BUILD This software can be compiled with VisualStudio 2015. This example assumes the compiler is in your path. ``` vcvarsall amd64 cl /EHsc main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -DmyDisableOpenJPEG /o dcm2niix ``` ##### OSX BUILD WITH BOTH 32 AND 64-BIT SUPPORT Building command line version universal binary from OSX 64 bit system: This requires a C compiler. With a terminal, change directory to the 'conosle' folder and run the following: ``` g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -dead_strip -arch i386 -o dcm2niix32 ``` ``` g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -dead_strip -o dcm2niix64 ``` ``` lipo -create dcm2niix32 dcm2niix64 -o dcm2niix ``` To validate that the resulting executable supports both architectures type ``` file ./dcm2niix ``` ##### OSX GRAPHICAL INTERFACE BUILD You can building the OSX graphical user interface using Xcode. First, Copy contents of "console" folder to /xcode/dcm2/core. Next, open and compile the project "dcm2.xcodeproj" with Xcode 4.6 or later ##### THE QT AND wxWIDGETS GUIs ARE NOT YET SUPPORTED - FOLLOWING LINES FOR FUTURE VERSIONS Building QT graphical user interface: Open "dcm2.pro" with QTCreator. This should work on OSX and Linux. On Windows the printf information is not redirected to the user interface If compile gives you grief look at the .pro file which has notes for different operating systems. Building using wxWidgets wxWdigets makefiles are pretty complex and specific for your operating system. For simplicity, we will build the "clipboard" example that comes with wxwidgets and then substitute our own code. The process goes something like this. a.) Install wxwdigets b.) successfully "make" the samples/clipboard program c.) DELETE console/makefile. WE DO NOT WANT TO OVERWRITE the WX MAKEFILE d.) with the exception of "makefile", copy the contents of console to /samples/clipboard e.) overwrite the original /samples/clipboard.cpp with the dcm2niix file of the same name f.) Older Xcodes have problems with .cpp files, whereas wxWidgets's makefiles do not compile with "-x cpp". So the core files are called '.c' but we will rename them to .cpp for wxWidgets: rename 's/\.c$/\.cpp/' * g.) edit the /samples/clipboard makefile: Add "nii_dicom.o nifti1_io_core.o nii_ortho.o nii_dicom_batch.o \" to CLIPBOARD_OBJECTS: CLIPBOARD_OBJECTS = \ nii_dicom.o nifti1_io_core.o nii_ortho.o nii_dicom_batch.o \ $(__clipboard___win32rc) \ $(__clipboard_os2_lib_res) \ clipboard_clipboard.o h.) edit the /samples/clipboard makefile: With wxWidgets we will capture std::cout comments, not printf, so we need to add "-DDmyUseCOut" to CXXFLAGS: CXXFLAGS = -DmyUseCOut -DWX_PRECOMP .... i.) For a full refresh rm clipboard rm *.o make dcm2niix-1.0.20181125/Dockerfile000066400000000000000000000010561337661136700160020ustar00rootroot00000000000000FROM ubuntu:trusty MAINTAINER # feel free to change/adopt # Install Dependencies RUN apt-get update && apt-get upgrade -y && \ apt-get install -y build-essential pkg-config cmake git pigz && \ apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y # Get dcm2niix from github and compile RUN cd /tmp && \ git clone https://github.com/rordenlab/dcm2niix.git && \ cd dcm2niix && mkdir build && cd build && \ cmake -DBATCH_VERSION=ON -DUSE_OPENJPEG=ON .. && \ make && make install ENTRYPOINT ["/usr/local/bin/dcm2niix"] dcm2niix-1.0.20181125/FILENAMING.md000066400000000000000000000113121337661136700157370ustar00rootroot00000000000000## About DICOM files tend to have bizarre file names, for example based on the instance UID, e.g. `MR.1.3.12.2.1107.5.2.32.35131.2014031013003871821190579`. In addition, DICOM images are often 2D slices or 3D volumes that we will combine into a single unified NIfTI file. On the other hand, some enhanced DICOM images save different reconstructions (e.g. phase and magnitude) of the same image that we will want to save as separate NIfTI files. Therefore, dcm2niix attempts to provide a sensible file naming scheme. ## Basics You request the output filename with the `-f` argument. For example, consider you convert files with `dcm2niix -f %s_%p`: in this case an image from series 3 with the protocol name `T1` will be saved as `3_T1.nii`. Here are the available parameters for file names: - %a=antenna (coil) name (from Siemens 0051,100F) - %b=basename (filename of first DICOM) - %c=comments (from 0020,4000) - %d=description (from 0008,103E) - %e=echo number (from 0018,0086) - %f=folder name (name of folder containing first DICOM) - %i=ID of patient (from 0010,0020) - %j=seriesInstanceUID (from 0020,000E) - %k=studyInstanceUID (from 0020,000D) - %m=manufacturer short name (from 0008,0070: GE, Ph, Si, To, UI, NA) - %n=name of patient (from 0010,0010) - %p=protocol name (from 0018,1030) - %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) - %v=vendor long name (from 0008,0070: GE, Philips, Siemens, Toshiba, UIH, NA) - %x=study ID (from 0020,0010) - %z=sequence name (from 0018,0024) ## Filename Post-fixes: Image Disambiguation In general dcm2niix creates images with 3D dimensions, or 4 dimensions when the 4th dimension is time (fMRI) or gradient number (DWI). It will use the following extensions to disambiguate additional dimensions from the same series: - _cNx.._cNz where C* refers to the coil name (typically only seen for uncombined data, where a separate image is generated for each antenna) - _e1..eN echo number for multi-echo sequences - _ph phase map - _imaginary imaginary component of complex image - _real real component of complex image - _phMag rare case where phase and magnitude are saved as the 4th dimension - _t If the trigger delay time (0020,9153) is non-zero, it will be recorded in the filename. For example, the files "T1_t178.nii" and "T1_t511" suggests that the T1 scan was acquired with two cardiac trigger delays (178 and 511ms after the last R-peak). - _ADC Philips specific case. A DWI image where derived isotropic, ADC or trace volume was appended to the series. Since this image will disrupt subsequent processing, and because subsequent processing (dwidenoise, topup, eddy) will yield better derived images, dcm2niix will also create an additional image without this volume. Therefore, the _ADC file should typically be discarded. If you want dcm2niix to discard these useless derived images, use the ignore feature ('-i y'). - _Eq is specific to [CT scans](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Computed_Tomography_.28CT.2C_CAT.29). These scans can be acquired with variable distance between the slices of a 3D volume. NIfTI asumes all 2D slices that form a 3D stack are equidistant. Therefore, dcm2niix reslices the input data to generate an equidistant volume. - _Tilt is specific to [CT scans](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Computed_Tomography_.28CT.2C_CAT.29). These scans can be acquired with a gantry tilt that causes a skew that can not be stored in a NIfTI qForm. Therefore, the slices are resampled to remove the effect of tilt. - _MoCo is appended to the ProtocolName if Image Type (0008,0008) includes the term 'MOCO'. This helps disambiguate Siemens fMRI runs where both motion corrected and raw data is stored for a single session. ## Special Characters [Some characters are not permitted](https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names) in filenames. The following characters will be replaced with underscorces (`_`). Note that the forbidden characters vary between operating systems (Linux only forbids the forward slash, MacOS forbids forward slash and colon, while Windows forbids any of the characters listed below). To ensure that files can be easily copied between file systems, [dcm2niix restricts filenames to characters allowed by Windows](https://github.com/rordenlab/dcm2niix/issues/237). ### List of Forbidden Characters (based on Windows) ``` < (less than) > (greater than) : (colon - sometimes works, but is actually NTFS Alternate Data Streams) " (double quote) / (forward slash) \ (backslash) | (vertical bar or pipe) ? (question mark) * (asterisk) ```dcm2niix-1.0.20181125/GE/000077500000000000000000000000001337661136700143015ustar00rootroot00000000000000dcm2niix-1.0.20181125/GE/README.md000066400000000000000000000137051337661136700155660ustar00rootroot00000000000000## About dcm2niix attempts to convert GE DICOM format images to NIfTI. The current generation DICOM files generated be 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. ## Diffusion Tensor Notes The [NA-MIC Wiki](https://www.na-mic.org/wiki/NAMIC_Wiki:DTI:DICOM_for_DWI_and_DTI#Private_vendor:_GE) provides a nice description of the GE diffusion tags. In brief, the B-value is stored as the first element in the array of 0043,1039. The DICOM elements 0019,10bb, 0019,10bc and 0019,10bd provide the gradient direction relative to the frequency, phase and slice. As noted by Jaemin Shin (GE), the GE convention for reported diffusion gradient direction has always been in “MR physics” logical coordinate, i.e Freq (X), Phase (Y), Slice (Z). Note that this is neither “with reference to the scanner bore” (like Siemens or Philips) nor “with reference to the imaging plane” (as expected by FSL tools). This is the main source of confusion. This explains why the dcm2niix function geCorrectBvecs() checks whether the DICOM tag In-plane Phase Encoding Direction (0018,1312) is 'ROW' or 'COL'. In addition, it will generate the warning 'reorienting for ROW phase-encoding untested' if you acquire DTI data with the phase encoding in the ROW direction. If you do test this feature, please report your findings as a Github issue. Assuming you have COL phase encoding, dcm2niix should provide [FSL format](http://justinblaber.org/brief-introduction-to-dwmri/) [bvec files](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FDT/FAQ#What_conventions_do_the_bvecs_use.3F). ## Slice Timing Knowing the relative timing of the acquisition for each 2D slice in a 3D volume is useful for [slice time correction](https://www.mccauslandcenter.sc.edu/crnl/tools/stc) of both fMRI and DTI data. Unfortunately, current GE software does not provide a consistent way to record this. [Some sequences](https://afni.nimh.nih.gov/afni/community/board/read.php?1,154006) encode the RTIA Timer (0021,105E) element. For example, [this dataset DV24](https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/ge-mr750-slice-timing) includes timing data, while [this DV26 dataset does not](https://github.com/neurolabusc/dcm_qa_nih). Even with the sequences that do encode the RTIA Timer, there is some debate regarding the accuracy of this element. In the example listed, the slice times are clearly wrong in the first volume. Therefore, dcm2niix always estimates slice times based on the 2nd volume in a time series. In general, fMRI acquired using GE product sequence (PSD) “epi” with the multiphase option will store slice timing in the Trigger Time (DICOM 0018,1060) element. The current version of dcm2niix ignores this field, as no examples are available. In contrast, the popular PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics) does not save this tag (though in some cases it saves the RTIA Timer). Examples are [available](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Slice_timing_correction) for both the “epiRT” and “epi” sequences. ## User Define Data GE (0043,102A) This private element of the DICOM header is used to determine the phase encoding polarity. Specifically, we need to know the "Ky traversal direction" (top-down, or bottom up) and the phase encoding polarity. Unfortunately, this data is stored in a complicated, proprietary structure, that has changed with different releases of GE software. [Click here to see the definition for this structure](https://github.com/ScottHaileRobertson/GE-MRI-Tools/blob/master/GePackage/%2BGE/%2BPfile/%2BHeader/%2BRDB15/rdbm.h). ## Total Readout Time One often wants to determine [echo spacing, bandwidth, ](https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth) and total read-out time for EPI data so they can be undistorted. Total readout time is influence by parallel acceleration factor, bandwidth, number of EPI lines, and partial Fourier. Not all of these parameters are available from the GE DICOM images, so a user needs to check the scanner console. ## GE Protocol Data Block In addition to the public DICOM tags, previous versions of dcm2niix attempted to decode the proprietary GE Protocol Data Block (0025,101B). This is essentially a [GZip format](http://www.onicos.com/staff/iz/formats/gzip.html) file embedded inside the DICOM header. Unfortunately, this data seems to be [unreliable](https://github.com/rordenlab/dcm2niix/issues/163) and therefore this strategy is not used anymore. The notes below regarding the usage of this data block are provided for historical purposes. - The VIEWORDER tag is used to set the polarity of the BIDS tag PhaseEncodingDirection, with VIEWORDER of 1 suggesting bottom up phase encoding. Unfortunately, users can separately reverse the phase encoding direction making this tag unreliable. - The SLICEORDER tag could be used to set the SliceTiming for the BIDS tag PhaseEncodingDirection, with a SLICEORDER of 1 suggesting interleaved acquisition. - There are reports that newer versions of GE equipement (e.g. DISCOVERY MR750 / 24\MX\MR Software release:DV24.0_R01_1344.a) are now storing an [XML](https://groups.google.com/forum/#!msg/comp.protocols.dicom/mxnCkv8A-i4/W_uc6SxLwHQJ) file within the Protocolo Data Block (compressed). In theory this might also provide useful information. ## Sample Datasets - [A validation dataset for dcm2niix commits](https://github.com/neurolabusc/dcm_qa_nih). - [Examples of phase encoding polarity, slice timing and diffusion gradients](https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/). - The dcm2niix (wiki)[https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage] includes examples of diffusion data, slice timing, and other variations.dcm2niix-1.0.20181125/PARREC/000077500000000000000000000000001337661136700147625ustar00rootroot00000000000000dcm2niix-1.0.20181125/PARREC/README.md000066400000000000000000000073721337661136700162520ustar00rootroot00000000000000## 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. 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. ## Detecting, Reporting and Fixing the V4 Image Offcente Bug The PAR header contains a field 'image offcentre (ap,fh,rl in mm )' that we use to detect the spatial position of slices (e.g. for an axial scan is the first slice inferior or superior to the final slice). However, it appears that in some V4 images the values in these columns are actually stored in the order "rl,ap,fh". This has never been reported in V3, V4.1 and V4.2 images. A nice example of this is the ['philips_1_5T_intera' dataset provided with Rosetta Bit](https://www.nitrc.org/projects/rosetta/)(actually from a 3T MRI). This sample includes both DICOM and V4 PAR/REC data. Note the 'Off Centre midslice(ap,fh,rl) [mm]' field gives the volume center in the correct order. However, the subsequent 'image offcentre' fields are swizzled. The latest versions of dcm2niix will detect, report and correct this error. If you do see an error like the one below, please report it on Github as an issue, so we can have a better understanding of its prevalence. ``` Order of 'image offcentre (ap,fh,rl in mm )' appears incorrect (assuming rl,ap,fh) err[ap,fh,rl]= 12.7279 (-7.05 -4.55 2.95) err[rl,ap,fh]= 0.0223597 (-4.55 2.95 -7.05) ``` ## File naming You can specify the preferred name for your output file with dcm2niix. You do this by passing an argument string (default is '%f_%p_%t_%s'). For example, If you run "dcm2niix -f %p_%s ~/myParDir" the output name will be based on the protocol name and the series name. Here is a list of the possible argument you can use with Philips, and the tag from the header that is used for this value: - %c: comment : "Examination name" - %d: description : "Series Type" - %e: echo number : "echo number" Column - %f: folder name - %i: ID : "Technique" - %m: manufacturer : always 'Ph' for Philips - %n: name : "Patient name" - %p: protocol name : "Protocol name" - %s: series number : "Acquisition nr" - %t: time : "Examination date/time" - %u: acquisition number : "Acquisition nr" - %v: vendor : always 'Philips' for Philips Note that for Philips (unlike DICOM) the For PAR/REC the acquisition (%u) and series (%s) numbers are the same. Also note that there are several arguments that might be useful with DICOM but will always be unused for PAR/REC: - %a: antenna - %j: seriesInstanceUID - %k: studyInstanceUID - %x: study ID - %z: sequence name ## dcm2niix Limitations Be aware that dcm2niix assumes that the data is stored in complete 3D volumes. It will not convert datasets where the scan is interrupted mid-volume (e.g. where the number of 2D slices is not divisible by the number of slices in a volume). This can occur if the user aborts a sequence part way through acquisition. If dcm2niix detects this situation it will suggest you use [dicm2nii](https://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter--nifti-tool-and-viewer) which can handle these files. dcm2niix-1.0.20181125/Philips/000077500000000000000000000000001337661136700154165ustar00rootroot00000000000000dcm2niix-1.0.20181125/Philips/README.md000066400000000000000000000137051337661136700167030ustar00rootroot00000000000000## About dcm2niix attempts to convert all DICOM images to NIfTI. The Philips enhanced DICOM images are elegantly able to save all images from a series as a single file. However, this format is necessarily complex. The usage of this format has evolved over time, and can become further complicated when DICOM are handled by DICOM tools (for example, anonymization, transfer which converts explicit VRs to implicit VRs, etc.). This web page describes some of the strategies handle these images. However, users should be vigilant when handling these datasets. If you encounter problems using dcm2niix you can explore [alternative DICOM to NIfTI converters](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Alternatives) or [report an issue](https://github.com/rordenlab/dcm2niix). ## Image Patient Position The Image Patient Position (0020,0032) tag is required to determine the position of the slices with respect to each other (and with respect to the scanner bore). Philips scans often report two conflicting IPPs for each single slice: with one stored in the private sequence (SQ) 2005,140F while the other is in the public sequence. This is unusual, but is [legal](ftp://medical.nema.org/medical/dicom/final/cp758_ft.pdf). In practice, this complication has several major implications. First, not all software translate private SQs well. One potential problem is when the implicit VRs are saved as implicit VRs. This can obscure the fact that 2005,140F is an SQ. Indeed, some tools will convert the private SQ type as a "UN" unknown type and add another public sequence. This can confuse reading software. Furthermore, in the real world there are many Philips DICOM images that ONLY contain IPP inside SQ 2005,140F. These situations appear to reflect modifications applied by a PACS to the DICOM data or attempts to anonymize the DICOM images (e.g. using simple Matlab scripts). Note that the private IPP differs from the public one by half a voxel. Therefore, in theory if one only has the private IPP, one can infer the public IPP location. Current versions of dcm2niix do not do this: the error is assumed small enough that it will not impact image processing steps such as coregistration. In general it is recommended that you archive and convert DICOM images as they are created from the scanner. If one does use an export tool such as the excellent dcmtk, it is recommended that you preserve the explicit VR, as implicit VR has the potential of obscuring private sequence (SQ) tags. Be aware that subsequent processing of DICOM data can disrupt data conversion. Therefore, dcm2niix will ignore the IPP enclosed in 2005,140F unless no alternative exists. ## 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. For example, ADC, Trace and Isotropic images can all be derived from the raw scans. As scientists, we want to discard these derived images, as they will disrupt data processing and we can generate better parametric maps after we have applied undistortion methods such as [Eddy and Topup](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy/UsersGuide). The current version of dcm2niix uses the Diffusion Directionality (0018,9075) tag to detect B=0 unweighted ("NONE"), B-weighted ("DIRECTIONAL"), and derived ("ISOTROPIC") images. Note that the Dimension Index Values (0020,9157) tag provides an alternative approach to discriminate these images. Here are sample tags from a Philips enhanced image that includes and derived map (3rd dimension is "1" while the other images set this to "2"). ``` (0018,9075) CS [DIRECTIONAL] (0018,9089) FD 1\0\0 (0018,9087) FD 1000 (0020,9157) UL 1\1\2\32 ... (0018,9075) CS [ISOTROPIC] (0018,9087) FD 1000 (0020,9157) UL 1\2\1\33 ... (0018,9075) CS [NONE] (0018,9087) FD 0 (0020,9157) UL 1\1\2\33 ``` ## Diffusion Direction Proper Philips enhanced scans use tag 0018,9089 to report the 3 gradient directions. However, in the wild, other files from Philips (presumably using older versions of Philips software) use the tags 2005,10b0, 2005,10b1, 2005,10b2. In general, dcm2niix will use the values that most closely precede the Dimension Index Values (0020,9157). The current version of dcm2niix uses Dimension Index Values (0020,9157) to determine gradient number, which can also be found in (2005,1413). However, while 2005,1413 is always indexed from one, this is not necessarily the case for 0020,9157. For example, the ADNI DWI dataset for participant 018_S_4868 has values of 2005,1413 that range from 1..36 for the 36 directions, while 0020,9157 ranges from 2..37. The current version of dcm2niix compensates for this by re-indexing the values of 0020,9157 after all the volumes have been read. ## General variations Prior versions of dcm2niix used different methods to sort images. However, these have proved unreliable The undocumented tags SliceNumberMrPhilips (2001,100A). In theory, InStackPositionNumber (0020,9057) should be present in all enhanced files, but has not proved reliable (perhaps not in older Philips images or DICOM images that were modified after leaving the scanner). MRImageGradientOrientationNumber (2005,1413) is complicated by the inclusion of derived images. Therefore, current versions of dcm2niix do not generally depend on any of these. ## Sample Datasets - [National Alliance for Medical Image Computing (NAMIC) samples](http://www.insight-journal.org/midas/collection/view/194) - [Unusual Philips Examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Unusual_MRI). - [Diffusion Examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging).dcm2niix-1.0.20181125/README.md000066400000000000000000000247641337661136700153020ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/rordenlab/dcm2niix.svg?branch=master)](https://travis-ci.org/rordenlab/dcm2niix) [![Build status](https://ci.appveyor.com/api/projects/status/7o0xp2fgbhadkgn1?svg=true)](https://ci.appveyor.com/project/neurolabusc/dcm2niix) ## About dcm2niix is a 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). ## License This software is open source. The bulk of the code is covered by the BSD license. Some units are either public domain (nifti*.*, miniz.c) or use the MIT license (ujpeg.cpp). See the license.txt file for more details. ## Dependencies This software should run on macOS, Linux and Windows typically without requiring any other software. However, if you use dcm2niix to create gz-compressed images it will be faster if you have [pigz](https://github.com/madler/pigz) installed. You can get a version of both dcm2niix and pigz compiled for your operating system by downloading [MRIcroGL](https://www.nitrc.org/projects/mricrogl/). ## Image Conversion and Compression DICOM provides many ways to store/compress image data, known as [transfer syntaxes](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#DICOM_Transfer_Syntaxes_and_Compressed_Images). The [COMPILE.md file describes details](./COMPILE.md) on how to enable different options to provide support for more formats. - The base code includes support for raw, run-length encoded, and classic JPEG lossless decoding. - Lossy JPEG is handled by the included [NanoJPEG](https://keyj.emphy.de/nanojpeg/). This support is modular: you can compile for [libjpeg-turbo](https://github.com/chris-allan/libjpeg-turbo) or disable it altogether. - JPEG-LS lossless support is optional, and can be provided by using [CharLS](https://github.com/team-charls/charls). - JPEG2000 lossy and lossless support is optional, and can be provided using [OpenJPEG](https://github.com/uclouvain/openjpeg) or [Jasper](https://www.ece.uvic.ca/~frodo/jasper/). - GZ compression (e.g. creating .nii.gz images) is optional, and can be provided using either the included [miniz](https://github.com/richgel999/miniz) or the popular zlib. Of particular note, the [Cloudflare zlib](https://github.com/cloudflare/zlib) exploits modern hardware (available since 2008) for very rapid compression. Alternatively, you can compile dcm2niix without a gzip compressor. Regardless of how you compile dcm2niix, it can use the external program [pigz](https://github.com/madler/pigz) for parallel compression. ## Versions [See the VERSIONS.md file for details on releases](./VERSIONS.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`. [See the BATCH.md file for instructions on using the batch processing version](./BATCH.md). ## Install There are a couple ways to install dcm2niix - [Github Releases](https://github.com/rordenlab/dcm2niix/releases) provides the latest compiled executables. This is an excellent option for MacOS and Windows users. However, the provided Linux executable requires a recent version of Linux, so the provided Unix executable is not suitable for all distributions. - [MRIcroGL](https://github.com/neurolabusc/MRIcroGL/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, 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 Conda, [`conda install -c conda-forge dcm2niix`](https://anaconda.org/conda-forge/dcm2niix) on Linux, MacOS or Windows. - On Debian Linux computers you can run `sudo apt-get install dcm2niix`. ## Build from source It is often easier to download and install a precompiled version. However, you can also build from source. ### Build command line version with cmake (Linux, MacOS, Windows) `cmake` and `pkg-config` (optional) can be installed as follows: Ubuntu: `sudo apt-get install cmake pkg-config` MacOS: `brew install cmake pkg-config` **Basic build:** ```bash git clone https://github.com/rordenlab/dcm2niix.git cd dcm2niix mkdir build && cd build cmake .. make ``` `dcm2niix` will be created in the `bin` subfolder. To install on the system run `make install` instead of `make` - this will copy the executable to your path so you do not have to provide the full path to the executable. In rare case if cmake fails with the message like `"Generator: execution of make failed"`, it could be fixed by ``sudo ln -s `which make` /usr/bin/gmake``. **Advanced build:** As noted in the `Image Conversion and Compression Support` section, the software provides many optional modules with enhanced features. A common choice might be to include support for JPEG2000, [JPEG-LS](https://github.com/team-charls/charls) (this option requires a c++14 compiler), as well as using the high performance Cloudflare zlib library (this option requires a CPU built after 2008). To build with these options simply request them when configuring cmake: ```bash cmake -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON .. ``` **optional batch processing version:** The batch processing binary `dcm2niibatch` is optional. To build `dcm2niibatch` as well change the cmake command to `cmake -DBATCH_VERSION=ON ..`. This requires a compiler that supports c++11. ### Building the command line version without cmake If you have any problems with the cmake build script described above or want to customize the software see the [COMPILE.md file for details on manual compilation](./COMPILE.md). ## Alternatives - [dinifti](http://cbi.nyu.edu/software/dinifti.php) is focused on conversion of Siemens data. - [dcm2nii](http://www.mccauslandcenter.sc.edu/mricro/mricron/dcm2nii.htm) 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). - [DWIConvert](https://github.com/BRAINSia/BRAINSTools/tree/master/DWIConvert) converts DICOM images to NRRD and NIfTI formats. - [dicm2nii](http://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter) is written in Matlab. The Matlab language makes this very scriptable. - [dicom2nifti](https://github.com/icometrix/dicom2nifti) uses the scriptable Python wrapper utilizes the [high performance GDCMCONV](http://gdcm.sourceforge.net/wiki/index.php/Gdcmconv) executables. - [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. - [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. - [SPM12](http://www.fil.ion.ucl.ac.uk/spm/software/spm12/) is one of the most popular tools in the field. It includes DICOM to NIfTI conversion. Being based on Matlab it is easy to script. ## Links - [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. - [boutiques-dcm2niix](https://github.com/lalet/boutiques-dcm2niix) is a dockerfile for installing and validating dcm2niix. - [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. - [dcm2niir](https://github.com/muschellij2/dcm2niir) R wrapper for dcm2niix/dcm2nii. - [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. - [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. - [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](https://users.fmrib.ox.ac.uk/~paulmc/fsleyes/fslpy/latest/fsl.data.dicom.html). - [heudiconv](https://github.com/nipy/heudiconv) can use dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. - [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/). - [neuro_docker](https://github.com/Neurita/neuro_docker) includes dcm2niix as part of a single, static Dockerfile. - [NeuroDebian](http://neuro.debian.net/pkgs/dcm2niix.html) provides up-to-date version of dcm2niix for Debian-based systems. - [neurodocker](https://github.com/kaczmarj/neurodocker) generates [custom](https://github.com/rordenlab/dcm2niix/issues/138) Dockerfiles given specific versions of neuroimaging software. - [nipype](https://github.com/nipy/nipype) can use dcm2niix to convert images. - [pydcm2niix is a Python module for working with dcm2niix](https://github.com/jstutters/pydcm2niix). - [sci-tran dcm2niix](https://github.com/scitran-apps/dcm2niix) Flywheel Gear (docker). dcm2niix-1.0.20181125/RENAMING.md000066400000000000000000000075341337661136700155410ustar00rootroot00000000000000## About dcm2niix is primarily designed to convert DICOM images into NIfTI images. However, it does include a primitive ability to organize and rename DICOM images without converting them. This can be useful, as DICOM images are often stored with random file names that make it difficult to recognize the images. ## Limitation dcm2niix renames and copies the DICOM images, but the current version does not copy or create a new [DICOMDIR](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_F.2.2.2.html) file. Most users ignore these files. However, you should not use this featire if you wish to preserve your DICOMDIR files. Note that this feature only copies your DICOM images with a new filename. It does not modify the contents of the DICOM header. This means it will not compress or anonymize your files. Free tools for these functions include [dcmcjpeg](https://dicom.offis.de/dcmtk.php.en), [gdcmanon](http://gdcm.sourceforge.net/html/gdcmanon.html) and [gdcmconv](http://gdcm.sourceforge.net/html/gdcmconv.html). In addition, this tool assumes that the DICOM images can be uniquely identified by the filenaming argument you provide. ## Usage The command line argument `-r y` instructs dcm2niix to rename your DICOM files rather than convert them. It does not delete your DICOM images, but rather creates a copy with the organization specified by the [filenaming argument `-f`](FILENAMING.md). Here is an example where the DICOM images will be sorted into folders, with the folder name reflecting the study time (`%t`) and series number (`%s`), each DICOM image will be named based on the image number (`%r`) which will be padded with zeros to fill 5 characters. - `dcm2niix -r y -f %t_%s/%5r.dcm -o ~/out ~/in` Therefore, the 9th DICOM image from series 3 acquired on 4 February 2012 would be saved as ~/out/20120204084424_3/00009.dcm. It is very important that your file naming disambiguates all your images. For example, consider a naming scheme that only used the image number (`-f %r.dcm`) and was applied to multiple series (each which had an image number 1,2,...). When there are naming conflicts, dcm2niix will terminate with an error message, e.g. `Error: File naming conflict. Existing file /home/c/dcm/1.dcm`. A special situation is the fieldmaps generated by Siemens scanners. Users often acquire gradient-echo fieldmaps so they can undistort EPI images. These fieldmaps acquire two (or more) echoes. Unfortunately, Siemens will give each of these echoes an identical series and image number. DICOM tools that are unaware of this often [overwrite](https://neurostars.org/t/field-mapping-siemens-scanners-dcm2niix-output-2-bids/2075/7) some of the images from each echo. To combat this situation, dcm2niix will add the post-fix `_e2` to the second echo. Therefore, if you converted a series with `-f %s_%4r` your fieldmap might generate files named `5_0001.dcm` and `5_0001_e2.dcm`. Note you could also explicitly number each echo (`-f %s_%4r_%e`), though in this case all your series (not just the fieldmaps) will have the echo appended. ## Alternatives An advantage of using dcm2niix is simplicity: it is a free, single file executable that you can [download](https://github.com/rordenlab/dcm2niix/releases) that is available for MacOS, Linux and Windows that you can run from the command line. On the other hand, this simplicity means it is fairly inflexible. You may want to consider a DICOM renamer built in your favorite scripting language. - [dicom-rename is a Python script](https://github.com/joshy/dicom-rename). - [dicomsort is a Python script](https://github.com/pieper/dicomsort). - [rename_dir is a Matlab script that requires the proprietary Image Processing Toolbox](https://gist.github.com/htygithub/ad3597577e1de004e9f5). - [dicm2nii includes the Matlab script rename_dicm that does not require any additional toolboxes](https://github.com/xiangruili/dicm2nii).dcm2niix-1.0.20181125/Siemens/000077500000000000000000000000001337661136700154115ustar00rootroot00000000000000dcm2niix-1.0.20181125/Siemens/README.md000066400000000000000000000077141337661136700167010ustar00rootroot00000000000000## About dcm2niix attempts to convert Siemens DICOM format images to NIfTI. This page describes some vendor-specific details. ## Vida XA10A The Siemens Vida introduced the new XA10A DICOM format. 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). In brief, the Vida can export to enhanced, mosaic or classic 2D. Note that the mosaics are considered secondary capture images 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. 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 XA10A format is that it retains no versioning details for software and hardware stepping, despite the fact that the data format is rapidly evolving. If you use a Vida, you are strongly encouraged to log every hardware or software upgrade to allow future analyses to identify and regress out any effects of these modifications. Since the XA10A 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). ## CSA Header Many crucial Siemens parameters are stored in the [proprietary CSA header](http://nipy.org/nibabel/dicom/siemens_csa.html). This has a binary section that allows quick reading for many useful parameters. It also includes an ASCII text portion that includes a lot of information but is slow to parse and poorly curated. ## Slice Timing The CSA header provides [slice timing](https://www.mccauslandcenter.sc.edu/crnl/tools/stc), and therefore dcm2niix should provide accurate slice timing information for non-XA10 datasets. For archival studies, be aware that some sequences [incorrectly reported slice timing](https://github.com/rordenlab/dcm2niix/issues/126). ## Total Readout Time One often wants to determine [echo spacing, bandwidth, ](https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth) and total read-out time for EPI data so they can be undistorted. The [Siemens validation dataset](https://github.com/neurolabusc/dcm_qa/tree/master/In/TotalReadoutTime) demonstrates that dcm2niix can accurately report these parameters - the included notes and spreadsheet describe this in more detail. ## Diffusion Tensor Notes Diffusion specific parameters are described on the [NA-MIC](https://www.na-mic.org/wiki/NAMIC_Wiki:DTI:DICOM_for_DWI_and_DTI#Private_vendor:_Siemens) website. Gradient vectors are reported with respect to the scanner bore, and dcm2niix will attempt to re-orient these to [FSL format](http://justinblaber.org/brief-introduction-to-dwmri/) [bvec files](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FDT/FAQ#What_conventions_do_the_bvecs_use.3F). ## Sample Datasets - [Slice timing dataset](httphttps://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Slice_timing_corrections://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage). - [A validation dataset for dcm2niix commits](https://github.com/neurolabusc/dcm_qa). - [A mixture of GE and Siemens data](https://github.com/neurolabusc/dcm_qa_nih). - [DTI examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging). - [Archival (old) examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Archival_MRI). - [Unusual examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Unusual_MRI). dcm2niix-1.0.20181125/SuperBuild/000077500000000000000000000000001337661136700160645ustar00rootroot00000000000000dcm2niix-1.0.20181125/SuperBuild/External-CLOUDFLARE-ZLIB.cmake000066400000000000000000000006601337661136700230260ustar00rootroot00000000000000set(CLOUDFLARE_BRANCH gcc.amd64) # Cloudflare zlib branch ExternalProject_Add(zlib GIT_REPOSITORY "${git_protocol}://github.com/ningfei/zlib.git" GIT_TAG "${CLOUDFLARE_BRANCH}" SOURCE_DIR cloudflare-zlib BINARY_DIR cloudflare-zlib-build CMAKE_ARGS -Wno-dev -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=${DEP_INSTALL_DIR} ) set(ZLIB_ROOT ${DEP_INSTALL_DIR}) dcm2niix-1.0.20181125/SuperBuild/External-OPENJPEG.cmake000066400000000000000000000007211337661136700220550ustar00rootroot00000000000000set(OPENJPEG_TAG v2.1-static) # version v2.1-static ExternalProject_Add(openjpeg GIT_REPOSITORY "${git_protocol}://github.com/ningfei/openjpeg.git" GIT_TAG "${OPENJPEG_TAG}" SOURCE_DIR openjpeg BINARY_DIR openjpeg-build CMAKE_ARGS -Wno-dev --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=${DEP_INSTALL_DIR} ) set(OpenJPEG_DIR ${DEP_INSTALL_DIR}/lib/openjpeg-2.1) dcm2niix-1.0.20181125/SuperBuild/External-YAML-CPP.cmake000066400000000000000000000007301337661136700220300ustar00rootroot00000000000000set(YAML-CPP_TAG yaml-cpp-0.5.3) # version yaml-cpp-0.5.3 ExternalProject_Add(yaml-cpp GIT_REPOSITORY "${git_protocol}://github.com/ningfei/yaml-cpp.git" GIT_TAG "${YAML-CPP_TAG}" SOURCE_DIR yaml-cpp BINARY_DIR yaml-cpp-build CMAKE_ARGS -Wno-dev --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=${DEP_INSTALL_DIR} ) set(YAML-CPP_DIR ${DEP_INSTALL_DIR}/lib/cmake/yaml-cpp) dcm2niix-1.0.20181125/SuperBuild/SuperBuild.cmake000066400000000000000000000135301337661136700211460ustar00rootroot00000000000000# Check if git exists find_package(Git) if(NOT GIT_FOUND) message(FATAL_ERROR "Cannot find Git. Git is required for Superbuild") endif() # Use git protocol or not option(USE_GIT_PROTOCOL "If behind a firewall turn this off to use http instead." ON) if(USE_GIT_PROTOCOL) set(git_protocol "git") else() set(git_protocol "https") endif() # Basic CMake build settings if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel") endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) option(USE_STATIC_RUNTIME "Use static runtime" ON) if(USE_STATIC_RUNTIME) if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") find_file(STATIC_LIBCXX "libstdc++.a" ${CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES}) mark_as_advanced(STATIC_LIBCXX) if(NOT STATIC_LIBCXX) unset(STATIC_LIBCXX CACHE) # Only on some Centos/Redhat systems message(FATAL_ERROR "\"USE_STATIC_RUNTIME\" set to ON but \"libstdcxx.a\" not found! \ \"yum install libstdc++-static\" to resolve the error.") endif() endif() endif() option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) include(ExternalProject) set(DEPENDENCIES) option(INSTALL_DEPENDENCIES "Optionally install built dependent libraries (OpenJPEG and yaml-cpp) for future use." OFF) if(INSTALL_DEPENDENCIES) set(DEP_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}) else() set(DEP_INSTALL_DIR ${CMAKE_BINARY_DIR}) endif() if(USE_OPENJPEG) message("-- Build with OpenJPEG: ${USE_OPENJPEG}") if(OpenJPEG_DIR) set(OpenJPEG_DIR "${OpenJPEG_DIR}" CACHE PATH "Path to OpenJPEG configuration file" FORCE) message("-- Using OpenJPEG library from ${OpenJPEG_DIR}") else() find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(OPENJPEG libopenjp2) endif() if(OPENJPEG_FOUND AND NOT ${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg") set(OpenJPEG_DIR ${OPENJPEG_LIBDIR}/openjpeg-2.1 CACHE PATH "Path to OpenJPEG configuration file" FORCE) message("-- Using OpenJPEG library from ${OpenJPEG_DIR}") else() if(${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg") message("-- Unable to use GDCM's internal OpenJPEG") endif() include(${CMAKE_SOURCE_DIR}/SuperBuild/External-OPENJPEG.cmake) list(APPEND DEPENDENCIES openjpeg) set(BUILD_OPENJPEG TRUE) message("-- Will build OpenJPEG library from github") endif() endif() endif() if(BATCH_VERSION) message("-- Build dcm2niibatch: ${BATCH_VERSION}") if(YAML-CPP_DIR) set(YAML-CPP_DIR ${YAML-CPP_DIR} CACHE PATH "Path to yaml-cpp configuration file" FORCE) message("-- Using yaml-cpp library from ${YAML-CPP_DIR}") else() find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(YAML-CPP yaml-cpp) endif() # Build from github if not found or version < 0.5.3 if(YAML-CPP_FOUND AND NOT (YAML-CPP_VERSION VERSION_LESS "0.5.3")) set(YAML-CPP_DIR ${YAML-CPP_LIBDIR}/cmake/yaml-cpp CACHE PATH "Path to yaml-cpp configuration file" FORCE) message("-- Using yaml-cpp library from ${YAML-CPP_DIR}") else() include(${CMAKE_SOURCE_DIR}/SuperBuild/External-YAML-CPP.cmake) list(APPEND DEPENDENCIES yaml-cpp) set(BUILD_YAML-CPP TRUE) message("-- Will build yaml-cpp library from github") endif() endif() endif() set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") set_property(CACHE ZLIB_IMPLEMENTATION PROPERTY STRINGS "Miniz;System;Cloudflare;Custom") if(${ZLIB_IMPLEMENTATION} STREQUAL "Cloudflare") message("-- Build with Cloudflare zlib: ON") include(${CMAKE_SOURCE_DIR}/SuperBuild/External-CLOUDFLARE-ZLIB.cmake) list(APPEND DEPENDENCIES zlib) set(BUILD_CLOUDFLARE-ZLIB TRUE) message("-- Will build Cloudflare zlib from github") elseif(${ZLIB_IMPLEMENTATION} STREQUAL "Custom") set(ZLIB_ROOT ${ZLIB_ROOT} CACHE PATH "Specify custom zlib root directory.") if(NOT ZLIB_ROOT) message(FATAL_ERROR "ZLIB_ROOT needs to be set to locate custom zlib!") endif() endif() ExternalProject_Add(console DEPENDS ${DEPENDENCIES} DOWNLOAD_COMMAND "" SOURCE_DIR ${CMAKE_SOURCE_DIR}/console BINARY_DIR console-build CMAKE_ARGS -Wno-dev --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR} -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} -DCMAKE_VERBOSE_MAKEFILE:BOOL=${CMAKE_VERBOSE_MAKEFILE} -DUSE_STATIC_RUNTIME:BOOL=${USE_STATIC_RUNTIME} -DUSE_TURBOJPEG:BOOL=${USE_TURBOJPEG} -DUSE_JASPER:BOOL=${USE_JASPER} -DUSE_JPEGLS:BOOL=${USE_JPEGLS} -DZLIB_IMPLEMENTATION:STRING=${ZLIB_IMPLEMENTATION} -DZLIB_ROOT:PATH=${ZLIB_ROOT} # OpenJPEG -DUSE_OPENJPEG:BOOL=${USE_OPENJPEG} -DOpenJPEG_DIR:PATH=${OpenJPEG_DIR} # yaml-cpp -DBATCH_VERSION:BOOL=${BATCH_VERSION} -DYAML-CPP_DIR:PATH=${YAML-CPP_DIR} ) install(DIRECTORY ${CMAKE_BINARY_DIR}/bin/ DESTINATION bin USE_SOURCE_PERMISSIONS) option(BUILD_DOCS "Build documentation (manpages)" OFF) if(BUILD_DOCS) add_subdirectory(docs) endif() dcm2niix-1.0.20181125/UIH/000077500000000000000000000000001337661136700144335ustar00rootroot00000000000000dcm2niix-1.0.20181125/UIH/README.md000066400000000000000000000100311337661136700157050ustar00rootroot00000000000000## About dcm2niix attempts to convert UIH DICOM format images to NIfTI. ## Notes Shan C Young provided the [following information](https://github.com/rordenlab/dcm2niix/issues/225), which is used by dcm2niix to generate NIfTI and BIDS format files. UIH supports two ways of archiving the DWI/DTI and fMRI data. One way is one DICOM file per slice and the other is one dicom file per volume (UIH refers to this as GRID format, similar to the Siemens Mosaic format). The private tags used in the images are shown in the following table. Tag ID | Tag Name | VR | VM | Description | Sample -- | -- | -- | -- | -- | -- 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 ## Sample Datasets - [UIH has provided a reference dataset](https://1drv.ms/f/s!Avf7THyflzj1gnO37GL2I8Hk-0MV). - [A validation dataset for dcm2niix commits](https://github.com/neurolabusc/dcm_qa_uih).dcm2niix-1.0.20181125/VERSIONS.md000066400000000000000000000213021337661136700155760ustar00rootroot00000000000000## Versions 14-November-2018 - [GE images provide more BIDS tags](https://github.com/rordenlab/dcm2niix/issues/163). - [Bruker enhanced DICOM support](https://github.com/rordenlab/dcm2niix/issues/241). - [Siemens Vida XA10 support](https://github.com/rordenlab/dcm2niix/issues/240). Note that Vida DICOM data is crippled [if the user exports as mosaics or anonymized/reduced](https://github.com/rordenlab/dcm2niix/issues/236). - [UIH enhanced DICOM support](https://github.com/rordenlab/dcm2niix/issues/225). - New DICOM [renaming](RENAMING.md) feature. 22-June-2018 - [Fix issues where 6-June-2018 release could save Enhanced DICOM Philips bvec/bval with different order than .nii images](https://github.com/rordenlab/dcm2niix/issues/201). 6-June-2018 - [Improved Philips PAR/REC support](https://github.com/rordenlab/dcm2niix/issues/171) - [Improved Philips Enhanced DICOM support](https://github.com/rordenlab/dcm2niix/issues/170) including saving different [real, imaginary, magnitude and phase images in a single DICOM file](https://github.com/rordenlab/dcm2niix/issues/189). - GE and Philips data now report [PhaseEncodingAxis](https://github.com/rordenlab/dcm2niix/issues/163) instead of PhaseEncodingDirection (these DICOMs store the dimension, but not the polarity). - Experimental detection of [phase encoding direction for GE](https://github.com/rordenlab/dcm2niix/issues/163). To enable compile with "MY_DEBUG_GE" flag. - Support for Philips Private RLE (1.3.46.670589.33.1.4.1) transfer syntax. - Optional support for JPEG-LS (1.2.840.10008.1.2.4.80/1.2.840.10008.1.2.4.81) transfer syntaxes (using [CharLS](https://github.com/team-charls/charls)). Requires c++14. - [Improved GE support](https://github.com/rordenlab/dcm2niix/issues/163) - Optional [lossless integer scaling](https://github.com/rordenlab/dcm2niix/issues/198) for INT16 and UINT16 DICOM images that only use a fraction of the possible range. 15-Dec-2017 - Support [Siemens XA10 images](https://github.com/rordenlab/dcm2niix/pull/145). - [Ability to select specific series to convert](https://github.com/rordenlab/dcm2niix/pull/146). 4-Dec-2017 - Handle implicit VR DICOMs where [critical values nested in sequence groups (SQ)](https://github.com/rordenlab/dcm2niix/commit/7f5649c6fe6ed366d07776aa54397b50f6641aff) - Better support for [PAR/REC files with segmented 3D EPI](https://github.com/rordenlab/dcm2niix/commit/66cdf2dcc60d55a6ef37f5a6db8d500d3eeb7c88). - Allow Protocol Name to be [empty](https://github.com/rordenlab/dcm2niix/commit/94f3129898ba83bf310c9ff28e994f29feb13068). 17-Oct-2017 - Swap [phase-encoding direction polarity](https://github.com/rordenlab/dcm2niix/issues/125) for Siemens images where PE is in the Column direction. - Sort diffusion volumes by [B-value amplitude](https://www.nitrc.org/forum/forum.php?thread_id=8396&forum_id=4703) (use "-d n"/"-d y" to turn the feature off/on). - BIDS tag [TotalReadoutTime](https://github.com/rordenlab/dcm2niix/issues/130) handles partial fourier, Phase Resolution, etc (Michael Harms). - Additional [json fields](https://github.com/rordenlab/dcm2niix/issues/127). 18-Aug-2017 - Better BVec extraction for [PAR/REC 4.1](https://www.nitrc.org/forum/forum.php?thread_id=8387&forum_id=4703). - Support for [Segami Cerescan volumes](https://www.nitrc.org/forum/forum.php?thread_id=8076&forum_id=4703). 24-July-2017 - Compiles with recent releases of [OpenJPEG](https://github.com/neurolabusc/dcm_qa/issues/5#issuecomment-317443179) for JPEG2000 support. 23-June-2017 - [Ensure slice timing always reported for Siemens EPI](https://github.com/neurolabusc/dcm_qa/issues/4#issuecomment-310707906) - [Integrates validation](https://github.com/neurolabusc/dcm_qa) - JSON fix (InstitutionName -> InstitutionAddress) 21-June-2017 - Read DICOM header in 1Mb segments rather than loading whole file : reduces ram usage and [faster for systems with slow io](https://github.com/rordenlab/dcm2niix/issues/104). - Report [TotalReadoutTime](https://github.com/rordenlab/dcm2niix/issues/98). - Fix JPEG2000 support in [Superbuild](https://github.com/rordenlab/dcm2niix/issues/105). 28-May-2017 - Remove all derived images from [Philips DTI series](http://www.nitrc.org/forum/message.php?msg_id=21025). - Provide some [Siemens EPI sequence details](https://github.com/rordenlab/dcm2niix/issues). 28-April-2017 - Experimental [ECAT support](https://github.com/rordenlab/dcm2niix/issues/95). - Updated cmake to make JPEG2000 support easier with improved Travis and AppVeyor support [Ningfei Li](https://github.com/ningfei). - Supports Data/Time for images that report Data/Time (0008,002A) but not separate Date and Time (0008,0022 and 0008,0032). - [BIDS reports SliceTiming correctly](http://www.nitrc.org/forum/message.php?msg_id=20852). - Options -1..-9 to control [gz compression level](https://github.com/rordenlab/dcm2niix/issues/90). - Includes some [PET details in the BIDS JSON sidecar](https://github.com/rordenlab/dcm2niix/issues/87). - Better detection of image order for Philips 4D DICOM (reported by Jason McMorrow and Stephen Wilson). - [Include StudyInstanceUID and SeriesInstanceUID in filename](https://github.com/rordenlab/dcm2niix/issues/94). 7-Feb-2017 - Can be compiled to use either Philips [Float or Display](http://www.nitrc.org/forum/message.php?msg_id=20213) intensity intercept and slope values. - Handle 3D Philips DICOM and [PAR/REC](https://www.nitrc.org/forum/forum.php?thread_id=7707&forum_id=4703) files where images are not stored in a spatially contiguous order. - Handle DICOM violations where icon is uncompressed but image data is compressed. - Best guess matrix for 2D slices (similar to dcm2nii, SPM and MRIconvert). - Linux (case sensitive filenames) now handles par/rec as well as PAR/REC. - Images with unknown phase encoding do not generate [BIDS entry](https://github.com/rordenlab/dcm2niix/issues/79). - Unified printMessage/printWarning/printError aids embedding in other projects, such as [divest](https://github.com/jonclayden/divest). 1-Nov-2016 - AppVeyor Support (Ningfei Li & Chris Filo Gorgolewski) - Swap 3rd/4th dimensions for GE sequential multi-phase acquisitions (Niels Janssen). 10-Oct-2016 - Restores/improves building for the Windows operating system using MinGW. 30-Sept-2016 - Save ImageType (0x0008,0x0008) to BIDS. - Separate CT scans with different exposures. - Fixed issues where some compilers would generate erratic filenames for zero-padded series (e.g. "-f %3s"). 21-Sept-2016 - Reduce verbosity (reduce number of repeated warnings, less scary warnings for derived rather than raw images). - Re-enable custom output directory "-o" option broken by 30-Apr-2016 version. - Deal with mis-behaved GE CT images where slice direction across images is not consistent. - Add new BIDS fields (field strength, manufacturer, etc). - Philips PAR/REC conversion now reports inconsistent requested vs measured TR (due to prospect. motion corr.?). - GE: Locations In Acquisition (0054, 0081) is inaccurate if slices are interpolated, use Images In Acquisition (0020,1002) if available. - New filename options %d Series description (0008,103E), %z Sequence Name (0018,0024). - New filename options %a antenna (coil) number, %e echo number. - Initialize unused portions of NIfTI header to zero so multiple runs always produce identical results. - Supports 3D lossless JPEG saved as [multiple fragments](http://www.nitrc.org/forum/forum.php?thread_id=5872&forum_id=4703). 5-May-2016 - Crop 3D T1 acquisitions (e.g. ./dcm2niix -x y ~/DICOM). 30-Apr-2016 - Convert multiple files/folders with single command line invocation (e.g. ./dcm2niix -b y ~/tst ~/tst2). 22-Apr-2016 - Detect Siemens Phase maps (phase image names end with "_ph"). - Use current working directory if file name not specified. 12-Apr-2016 - Provide override (command line option "-m y") to stack images of the same series even if they differ in study date/time, echo/coil number, or slice orientation. This mechanism allows users to concatenate images that break strict DICOM compliance. 22-Mar-2016 - Experimental support for [DICOM datasets without DICOM file meta information](http://dicom.nema.org/dicom/2013/output/chtml/part10/chapter_7.html). 12-Dec-2015 - Support PAR/REC FP values when possible (see PMC3998685). 11-Nov-2015 - Minor refinements. 12-June-2015 - Uses less memory (helpful for large datasets). 2-Feb-2015 - Support for Visual Studio. - Remove dependency on zlib (now uses miniz). 1-Jan-2015 - Images separated based on TE (fieldmaps). - Support for JPEG2000 using OpenJPEG or Jasper libraries. - Support for JPEG using NanoJPEG library. - Support for lossless JPEG using custom library. 24-Nov-2014 - Support for CT scans with gantry tilt and varying distance between slices. 11-Oct-2014 - Initial public release.dcm2niix-1.0.20181125/appveyor.yml000066400000000000000000000025601337661136700164010ustar00rootroot00000000000000version: build-{build} image: Visual Studio 2015 branches: only: - master configuration: Release platform: x64 clone_depth: 1 clone_folder: c:\projects\dcm2niix init: - ps: >- $env:DATE = $(Get-Date -Format d-MMM-yyyy) $githash = $env:APPVEYOR_REPO_COMMIT.Substring(0, 7) $gittag = if ($env:APPVEYOR_REPO_TAG -eq $True) {"_$($env:APPVEYOR_REPO_TAG_NAME)"} else {""} Update-AppveyorBuild -Version "$($env:DATE)_g${githash}${gittag}" $env:release_version = $(Get-Date -Format d-MMMM-yyyy) before_build: - cmd: >- echo "Running cmake" mkdir c:\projects\dcm2niix\build cd c:\projects\dcm2niix\build cmake -Wno-dev -G "Visual Studio 14 2015 Win64" -DBATCH_VERSION=ON -DUSE_OPENJPEG=ON -DUSE_JPEGLS=true ..\ build: project: c:\projects\dcm2niix\build\dcm2niix.sln verbosity: normal after_build: - ps: >- cd c:\projects\dcm2niix 7z a dcm2niix_$($env:DATE)_win.zip c:\projects\dcm2niix\build\bin\* >$null artifacts: - path: dcm2niix*.zip name: dcm2niix deploy: - provider: GitHub tag: $(appveyor_repo_tag_name) release: version $(release_version) ($(appveyor_repo_tag_name)) description: "" auth_token: secure: gCltVLQEWsjSTRlsi8qw7FGP54ujBq60apjXkWTV954b65bOHl95hXMxxkQ734L4 artifact: dcm2niix draft: false prerelease: false on: branch: master appveyor_repo_tag: true dcm2niix-1.0.20181125/batch_config.yml000066400000000000000000000006241337661136700171410ustar00rootroot00000000000000Options: isGz: false isFlipY: false isVerbose: false isCreateBIDS: false isOnlySingleFile: false Files: - in_dir: /path/to/first/folder out_dir: /path/to/output/folder filename: dcemri - in_dir: /path/to/second/folder out_dir: /path/to/output/folder filename: fa3dcm2niix-1.0.20181125/console/000077500000000000000000000000001337661136700154505ustar00rootroot00000000000000dcm2niix-1.0.20181125/console/CMakeLists.txt000066400000000000000000000156471337661136700202250ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.11) project(console) # Option Choose whether to use static runtime include(ucm.cmake) option(USE_STATIC_RUNTIME "Use static runtime" ON) if(USE_STATIC_RUNTIME) ucm_set_runtime(STATIC) else() ucm_set_runtime(DYNAMIC) endif() # Basic CMake build settings if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel") endif() if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") # using Clang add_definitions(-fno-caret-diagnostics) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip") elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") # using GCC if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.5.0)) add_definitions(-Wno-unused-result) # available since GCC 4.5.0 endif() if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.8.0)) add_definitions(-fno-diagnostics-show-caret) # available since GCC 4.8.0 endif() elseif(MSVC) # using Visual Studio C++ add_definitions(-D_CRT_SECURE_NO_DEPRECATE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4018") # '<': signed/unsigned mismatch set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4068") # unknown pragma set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4101") # unreferenced local variable set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244") # 'initializing': conversion from 'double' to 'int', possible loss of data set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") # 'initializing': conversion from 'size_t' to 'int', possible loss of data set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4305") # 'argument': truncation from 'double' to 'float' set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4308") # negative integral constant converted to unsigned type set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4334") # '<<': result of 32-bit shift implicitly converted to 64 bits set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 'uint32_t' : forcing value to bool 'true' or 'false' set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819") # The file contains a character that cannot be represented in the current code page set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4996") # 'access': The POSIX name for this item is deprecated set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:8388608") # set "Stack Reserve Size" to 8MB (default value is 1MB) endif() # Compiler dependent flags include (CheckCXXCompilerFlag) if(UNIX) check_cxx_compiler_flag(-msse2 HAS_SSE2) if(HAS_SSE2) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -mfpmath=sse") endif() endif() set(PROGRAMS dcm2niix) set(DCM2NIIX_SRCS main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_foreign.cpp nii_ortho.cpp nii_dicom_batch.cpp) option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) if(USE_JPEGLS) add_definitions(-DmyEnableJPEGLS) if(MSVC) add_definitions(-DCHARLS_STATIC) endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") set(CHARLS_SRCS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp) add_executable(dcm2niix ${DCM2NIIX_SRCS} ${CHARLS_SRCS}) else() add_executable(dcm2niix ${DCM2NIIX_SRCS}) endif() set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") set_property(CACHE ZLIB_IMPLEMENTATION PROPERTY STRINGS "Miniz;System;Custom") if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "Miniz") if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "System") set(ZLIB_ROOT ${ZLIB_ROOT} CACHE PATH "Specify custom zlib root directory.") if(NOT ZLIB_ROOT) message(FATAL_ERROR "ZLIB_ROOT needs to be set to locate custom zlib!") endif() endif() find_package(ZLIB REQUIRED) add_definitions(-DmyDisableMiniZ) target_include_directories(dcm2niix PRIVATE ${ZLIB_INCLUDE_DIRS}) target_link_libraries(dcm2niix ${ZLIB_LIBRARIES}) endif() option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) if(USE_TURBOJPEG) find_package(PkgConfig REQUIRED) pkg_check_modules(TURBOJPEG REQUIRED libturbojpeg) add_definitions(-DmyTurboJPEG) target_include_directories(dcm2niix PRIVATE ${TURBOJPEG_INCLUDEDIR}) target_link_libraries(dcm2niix ${TURBOJPEG_LIBRARIES}) endif() option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) if(USE_JASPER) find_package(Jasper REQUIRED) add_definitions(-DmyEnableJasper) target_include_directories(dcm2niix PRIVATE ${JASPER_INCLUDE_DIR}) target_link_libraries(dcm2niix ${JASPER_LIBRARIES}) endif() option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) if(USE_OPENJPEG) set(OpenJPEG_DIR "${OpenJPEG_DIR}" CACHE PATH "Path to OpenJPEG configuration file" FORCE) find_package(OpenJPEG REQUIRED) if(WIN32) if(BUILD_SHARED_LIBS) add_definitions(-DOPJ_EXPORTS) else() add_definitions(-DOPJ_STATIC) endif() endif() target_include_directories(dcm2niix PRIVATE ${OPENJPEG_INCLUDE_DIRS}) target_link_libraries(dcm2niix ${OPENJPEG_LIBRARIES}) else () add_definitions(-DmyDisableOpenJPEG) endif() option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) if(BATCH_VERSION) set(DCM2NIIBATCH_SRCS main_console_batch.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_foreign.cpp nii_ortho.cpp nii_dicom_batch.cpp) if(USE_JPEGLS) add_executable(dcm2niibatch ${DCM2NIIBATCH_SRCS} ${CHARLS_SRCS}) else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") add_executable(dcm2niibatch ${DCM2NIIBATCH_SRCS}) endif() set(YAML-CPP_DIR ${YAML-CPP_DIR} CACHE PATH "Path to yaml-cpp configuration file" FORCE) find_package(YAML-CPP REQUIRED) target_include_directories(dcm2niibatch PRIVATE ${YAML_CPP_INCLUDE_DIR}) target_link_libraries(dcm2niibatch ${YAML_CPP_LIBRARIES}) if(ZLIB_FOUND) target_include_directories(dcm2niibatch PRIVATE ${ZLIB_INCLUDE_DIRS}) target_link_libraries(dcm2niibatch ${ZLIB_LIBRARIES}) endif() if(TURBOJPEG_FOUND) target_include_directories(dcm2niibatch PRIVATE ${TURBOJPEG_INCLUDEDIR}) target_link_libraries(dcm2niibatch ${TURBOJPEG_LIBRARIES}) endif() if(JASPER_FOUND) target_include_directories(dcm2niibatch PRIVATE ${JASPER_INCLUDE_DIR}) target_link_libraries(dcm2niibatch ${JASPER_LIBRARIES}) endif() if(OPENJPEG_FOUND) target_include_directories(dcm2niibatch PRIVATE ${OPENJPEG_INCLUDE_DIRS}) target_link_libraries(dcm2niibatch ${OPENJPEG_LIBRARIES}) endif() list(APPEND PROGRAMS dcm2niibatch) endif() install(TARGETS ${PROGRAMS} DESTINATION bin) dcm2niix-1.0.20181125/console/charls/000077500000000000000000000000001337661136700167245ustar00rootroot00000000000000dcm2niix-1.0.20181125/console/charls/License.txt000066400000000000000000000030211337661136700210430ustar00rootroot00000000000000https://github.com/team-charls/charls Copyright (c) 2007-2010, Jan de Vaan All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of my employer, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. dcm2niix-1.0.20181125/console/charls/README.md000066400000000000000000000220071337661136700202040ustar00rootroot00000000000000[![Build status](https://ci.appveyor.com/api/projects/status/yq0naf3v2m8nfa8r/branch/master?svg=true)](https://ci.appveyor.com/project/vbaderks/charls/branch/master) [![Build Status](https://travis-ci.org/team-charls/charls.svg?branch=master)](https://travis-ci.org/team-charls/charls) # CharLS CharLS is a C++ implementation of the JPEG-LS standard for lossless and near-lossless image compression and decompression. JPEG-LS is a low-complexity image compression standard that matches JPEG 2000 compression ratios. # CharLS and dcm2niix [CharLS](https://github.com/team-charls/charls) is an optional module for dcm2niix. If included, it allows dcm2niix to handle the [JPEG-LS transfer syntaxes](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#DICOM_Transfer_Syntaxes_and_Compressed_Images). The included code was downloaded from the CharLS website on 6 June 2018. It is worth noting that DICOM can specify three different [lossless forms of the JPEG](http://www.mccauslandcenter.sc.edu/crnl/tools/jpeg-formats) image standard. CharLS is used only for the JPEG-LS form (transfer syntaxes 1.2.840.10008.1.2.4.80/81; ISO/IEC 14495-1:1999 ITU-T.87). In contrast, dcm2niix uses bespoke code to handle the older JPEG-Lossless (1.2.840.10008.1.2.4.57/70; ISO/IEC 10918-1:1994 ITU-T.81). Finally, dcm2niix handles the very complex JPEG-2000 lossless (1.2.840.10008.1.2.4.90/91; ISO/IEC 15444-1:2004 ITU-T.800) using the [OpenJPEG](https://github.com/uclouvain/openjpeg) library. To enable support you will need to include the `myEnableJPEGLS` compiler flag as well as a few file sin the `charls` folder. You will also need to specify `-std=c++14` and use a compiler that supports c++14 or later. Therefore, a minimal compile should look like this: `g++ -I. -std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG` The option `myEnableJPEGLS` specifies the latest version of CharLS (currently version 2). Alternatively, you can specify `myEnableJPEGLS1` to compile for CharLS version 1. This older code is not included with dcm2niix, but you can [download it from Github](https://github.com/team-charls/charls/tree/1.x-master). Note that CharLS version 1 is designed for c++03: `g++ -I. -std=c++03 -DmyEnableJPEGLS1 charls1/header.cpp charls1/jpegls.cpp charls1/jpegmarkersegment.cpp charls1/interface.cpp charls1/jpegstreamwriter.cpp main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG` Note that in these examples we have disabled OpenJPEG's JPEG2000 support. In reality, you will probably want to support both JPEG2000 and JPEG-LS, this will allow you to convert a broader range of images, and JPEG2000 includes lossy variations that do not have analogues for JPEG-LS. For details on adding the JPEG2000 module see this [compile page](https://github.com/rordenlab/dcm2niix/blob/master/COMPILE.md). # JPEG-LS versus other lossless JPEG codecs You can use gdcmconv to compare the performance of the ancient JPEG-lossless (gdcmconv -J; default mode for dcmcjpeg), JPEG-LS (gdcmconv -L) and JPEG2000-lossess (gdcmconv -K). Below is a sample test looking at 800 DICOM CT scans - with a raw size of 425mb which dcm2niix can convert in 1.6 seconds. The table shows that JPEG-LS reduces the file sizes to 137mb (0.39 original size), but that decompression takes 7.2 times longer. In contrast, the complicated JPEG2000 achieves only slightly better compression but is much slower to decompress. | CT | Size | Speed | | ----------------------------------------- | -----:| -----:| | Raw 1.2.840.10008.1.2.1 | 1.00 | 1.0 | | JPEG-lossless 1.2.840.10008.1.2.4.70 | 0.37 | 6.4 | | JPEG-LS 1.2.840.10008.1.2.4.80 | 0.32 | 7.2 | | JPEG2000 lossless 1.2.840.10008.1.2.4.90 | 0.31 | 60.1 | Below is a sample test looking at 1092 DICOM MRI scans with a Siemens Prisma with 16-bit output (many older systems use 12-bit output which would presumably provide more compression potential) - with a raw size of 425mb which dcm2niix can convert in 4.7 seconds. Note that the MRI scans show poorer compression for all techniques. | MRI | Size | Speed | | ----------------------------------------- | -----:| -----:| | Raw 1.2.840.10008.1.2.1 | 1.00 | 1.0 | | JPEG-lossless 1.2.840.10008.1.2.4.70 | 0.65 | 5.0 | | JPEG-LS 1.2.840.10008.1.2.4.80 | 0.60 | 7.7 | | JPEG2000 lossless 1.2.840.10008.1.2.4.90 | 0.61 | 71.6 | The tables above describe illustrate the speed for decompression. With respect to compression, the MRI images take 54 seconds to compress as JPEG-lossless, 72 seconds to compress as JPEG-LS and 212 seconds for JPEG2000 lossless. These tests support the notion that JPEG-LS provides similar compression to JPEG2000 lossless with much faster compression and decompression. In fairness, it should be noted that all these tests use open source OpenJPEG library for JPEG2000 compression and decompression. This library is known to be robust but [slow compared to proprietary libraries](https://blog.hexagongeospatial.com/jpeg2000-quirks/). ## Features * C++14 library implementation with a binary C interface for maximum interoperability.
Note: a C++03 compatible implementation is maintained in the 1.x-master branch. * Supports Windows, Linux and Solaris in 32 bit and 64 bit. * Includes an adapter assembly for .NET based languages. * Excellent compression and decompression performance. ## About JPEG-LS JPEG-LS (ISO/IEC 14495-1:1999 / ITU-T.87) is an image compression standard derived from the Hewlett Packard LOCO algorithm. JPEG-LS has low complexity (meaning fast compression) and high compression ratios, similar to the JPEG 2000 lossless ratios. JPEG-LS is more similar to the old Lossless JPEG than to JPEG 2000, but interestingly the two different techniques result in vastly different performance characteristics. Wikipedia on lossless JPEG and JPEG-LS: Tip: the ITU makes their version of the JPEG-LS standard (ITU-T.87) freely available for download, the text is identical with the ISO version. ## About this software This project's goal is to provide a full implementation of the ISO/IEC 14495-1:1999, "Lossless and near-lossless compression of continuous-tone still images: Baseline" standard. This library is written from scratch in portable C++. The master branch uses modern C++14. The 1.x branch is maintained in C++03. All mainstream JPEG-LS features are implemented by this library. According to preliminary test results published on http://imagecompression.info/gralic, CharLS is about *twice as fast* as the original HP code, and beats both JPEG-XR and JPEG 2000 by a factor 3. ### Limitations * No support for (optional) JPEG restart markers (RST). These markers are rarely used in practice. * No support for the SPIFF file header. * No support for oversize image dimension. Maximum supported image dimensions are [1, 65535] by [1, 65535]. * After releasing the original baseline standrd 14495-1:1999, ISO released an extension to the JPEG-LS standard called ISO/IEC 14495-2:2003: "Lossless and near-lossless compression of continuous-tone still images: Extensions". CharLS doesn't support these extensions. ## Supported platforms The code is regularly compiled/tested on Windows and 64 bit Linux. Additionally, the code has been successfully tested on Linux Intel/AMD 32/64 bit (slackware, debian, gentoo), Solaris SPARC systems, Intel based Macs and Windows CE (ARM CPU, emulated), where the less common compilers may require minor code edits. It leverages C++ language features (templates, traits) to create optimized code, which generally perform best with recent compilers. If you compile with GCC, 64 bit code performs substantially better. ## Users & Acknowledgements CharLS is being used by [GDCM DICOM toolkit](http://sourceforge.net/projects/gdcm/), thanks for [Mathieu Malaterre](http://sourceforge.net/users/malat) for getting CharLS started on Linux. [Kato Kanryu](http://knivez.homelinux.org/) wrote an initial version of the color transfroms and the DIB output format code, for an [irfanview](http://www.irfanview.com) plugin using CharLS. Thanks to Uli Schlachter, CharLS now finally runs correctly on big-endian architectures like Sun SPARC. ## Legal The code in this project is available through a BSD style license, allowing use of the code in commercial closed source applications if you wish. **All** the code in this project is written from scratch, and not based on other JPEG-LS implementations. Be aware that Hewlett Packard claims to own patents that apply to JPEG-LS implementations, but they license it for free for conformant JPEG-LS implementations. Read more at before you use this if you use this code for commercial purposes. dcm2niix-1.0.20181125/console/charls/charls.h000066400000000000000000000111211337661136700203450ustar00rootroot00000000000000/* (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. */ #ifndef CHARLS_CHARLS #define CHARLS_CHARLS #include "publictypes.h" // Windows and building CharLS DLL itself. #if defined(WIN32) && defined(CHARLS_DLL_BUILD) #define CHARLS_IMEXPORT(returntype) __declspec(dllexport) returntype __stdcall // NOLINT #endif // Non-windows (static linking) #if !defined(CHARLS_IMEXPORT) && !defined(_WIN32) #define CHARLS_IMEXPORT(returntype) returntype #endif // Windows static linking #if !defined(CHARLS_IMEXPORT) && defined(CHARLS_STATIC) #define CHARLS_IMEXPORT(returntype) returntype #endif // Windows DLL #if !defined(CHARLS_IMEXPORT) && defined(CHARLS_DLL) #define CHARLS_IMEXPORT(returntype) __declspec(dllimport) returntype __stdcall #endif #if !defined(CHARLS_IMEXPORT) #error Please #define CHARLS_STATIC or CHARLS_DLL before including "charls.h" to indicate if CharLS is built as a static library or as a dll. #endif #ifdef __cplusplus extern "C" { #else #include #endif /// /// Encodes a byte array with pixel data to a JPEG-LS encoded (compressed) byte array. /// /// Byte array that holds the encoded bytes when the function returns. /// Length of the array in bytes. If the array is too small the function will return an error. /// This parameter will hold the number of bytes written to the destination byte array. Cannot be NULL. /// Byte array that holds the pixels that should be encoded. /// Length of the array in bytes. /// Parameter object that describes the pixel data and how to encode it. /// Character array of at least 256 characters or NULL. Hold the error message when a failure occurs, empty otherwise. CHARLS_IMEXPORT(CharlsApiResultType) JpegLsEncode(void* destination, size_t destinationLength, size_t* bytesWritten, const void* source, size_t sourceLength, const struct JlsParameters* params, char* errorMessage); /// /// Retrieves the JPEG-LS header. This info can be used to pre-allocate the uncompressed output buffer. /// /// Byte array that holds the JPEG-LS encoded data of which the header should be extracted. /// Length of the array in bytes. /// Parameter object that describes how the pixel data is encoded. /// Character array of at least 256 characters or NULL. Hold the error message when a failure occurs, empty otherwise. CHARLS_IMEXPORT(CharlsApiResultType) JpegLsReadHeader(const void* compressedData, size_t compressedLength, struct JlsParameters* params, char* errorMessage); /// /// Encodes a JPEG-LS encoded byte array to uncompressed pixel data byte array. /// /// Byte array that holds the uncompressed pixel data bytes when the function returns. /// Length of the array in bytes. If the array is too small the function will return an error. /// Byte array that holds the JPEG-LS encoded data that should be decoded. /// Length of the array in bytes. /// Parameter object that describes the pixel data and how to decode it. /// Character array of at least 256 characters or NULL. Hold the error message when a failure occurs, empty otherwise. CHARLS_IMEXPORT(CharlsApiResultType) JpegLsDecode(void* destination, size_t destinationLength, const void* source, size_t sourceLength, const struct JlsParameters* params, char* errorMessage); CHARLS_IMEXPORT(CharlsApiResultType) JpegLsDecodeRect(void* uncompressedData, size_t uncompressedLength, const void* compressedData, size_t compressedLength, struct JlsRect roi, const struct JlsParameters* info, char* errorMessage); #ifdef __cplusplus } CHARLS_IMEXPORT(CharlsApiResultType) JpegLsEncodeStream(ByteStreamInfo compressedStreamInfo, size_t& pcbyteWritten, ByteStreamInfo rawStreamInfo, const JlsParameters& params, char* errorMessage); CHARLS_IMEXPORT(CharlsApiResultType) JpegLsDecodeStream(ByteStreamInfo rawStream, ByteStreamInfo compressedStream, const JlsParameters* info, char* errorMessage); CHARLS_IMEXPORT(CharlsApiResultType) JpegLsReadHeaderStream(ByteStreamInfo rawStreamInfo, JlsParameters* params, char* errorMessage); #endif #endif dcm2niix-1.0.20181125/console/charls/colortransform.h000066400000000000000000000133601337661136700221520ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_COLORTRANSFORM #define CHARLS_COLORTRANSFORM #include "util.h" // This file defines simple classes that define (lossless) color transforms. // They are invoked in processline.h to convert between decoded values and the internal line buffers. // Color transforms work best for computer generated images, but are outside the official JPEG-LS specifications. template struct TransformNoneImpl { static_assert(std::is_integral::value, "Integral required."); using size_type = T; FORCE_INLINE Triplet operator()(int v1, int v2, int v3) const noexcept { return Triplet(v1, v2, v3); } }; template struct TransformNone : TransformNoneImpl { static_assert(std::is_integral::value, "Integral required."); using Inverse = TransformNoneImpl; }; template struct TransformHp1 { static_assert(std::is_integral::value, "Integral required."); using size_type = T; struct Inverse { explicit Inverse(const TransformHp1&) noexcept { } FORCE_INLINE Triplet operator()(int v1, int v2, int v3) const noexcept { return Triplet(v1 + v2 - Range / 2, v2, v3 + v2 - Range / 2); } }; FORCE_INLINE Triplet operator()(int red, int green, int blue) const noexcept { Triplet hp1; hp1.v2 = static_cast(green); hp1.v1 = static_cast(red - green + Range / 2); hp1.v3 = static_cast(blue - green + Range / 2); return hp1; } private: static constexpr size_t Range = 1 << (sizeof(T) * 8); }; template struct TransformHp2 { static_assert(std::is_integral::value, "Integral required."); using size_type = T; struct Inverse { explicit Inverse(const TransformHp2&) noexcept { } FORCE_INLINE Triplet operator()(int v1, int v2, int v3) const noexcept { Triplet rgb; rgb.R = static_cast(v1 + v2 - Range / 2); // new R rgb.G = static_cast(v2); // new G rgb.B = static_cast(v3 + ((rgb.R + rgb.G) >> 1) - Range / 2); // new B return rgb; } }; FORCE_INLINE Triplet operator()(int red, int green, int blue) const noexcept { return Triplet(red - green + Range / 2, green, blue - ((red + green) >> 1) - Range / 2); } private: static constexpr size_t Range = 1 << (sizeof(T) * 8); }; template struct TransformHp3 { static_assert(std::is_integral::value, "Integral required."); using size_type = T; struct Inverse { explicit Inverse(const TransformHp3&) noexcept { } FORCE_INLINE Triplet operator()(int v1, int v2, int v3) const noexcept { const int G = v1 - ((v3 + v2) >> 2) + Range / 4; Triplet rgb; rgb.R = static_cast(v3 + G - Range / 2); // new R rgb.G = static_cast(G); // new G rgb.B = static_cast(v2 + G - Range / 2); // new B return rgb; } }; FORCE_INLINE Triplet operator()(int red, int green, int blue) const noexcept { Triplet hp3; hp3.v2 = static_cast(blue - green + Range / 2); hp3.v3 = static_cast(red - green + Range / 2); hp3.v1 = static_cast(green + ((hp3.v2 + hp3.v3) >> 2)) - Range / 4; return hp3; } private: static constexpr size_t Range = 1 << (sizeof(T) * 8); }; // Transform class that shifts bits towards the high bit when bit count is not 8 or 16 // needed to make the HP color transformations work correctly. template struct TransformShifted { using size_type = typename Transform::size_type; struct Inverse { explicit Inverse(const TransformShifted& transform) noexcept : _shift(transform._shift), _inverseTransform(transform._colortransform) { } FORCE_INLINE Triplet operator()(int v1, int v2, int v3) noexcept { const Triplet result = _inverseTransform(v1 << _shift, v2 << _shift, v3 << _shift); return Triplet(result.R >> _shift, result.G >> _shift, result.B >> _shift); } FORCE_INLINE Quad operator()(int v1, int v2, int v3, int v4) { Triplet result = _inverseTransform(v1 << _shift, v2 << _shift, v3 << _shift); return Quad(result.R >> _shift, result.G >> _shift, result.B >> _shift, v4); } private: int _shift; typename Transform::Inverse _inverseTransform; }; explicit TransformShifted(int shift) noexcept : _shift(shift) { } FORCE_INLINE Triplet operator()(int red, int green, int blue) noexcept { const Triplet result = _colortransform(red << _shift, green << _shift, blue << _shift); return Triplet(result.R >> _shift, result.G >> _shift, result.B >> _shift); } FORCE_INLINE Quad operator()(int red, int green, int blue, int alpha) { Triplet result = _colortransform(red << _shift, green << _shift, blue << _shift); return Quad(result.R >> _shift, result.G >> _shift, result.B >> _shift, alpha); } private: int _shift; Transform _colortransform; }; #endif dcm2niix-1.0.20181125/console/charls/constants.h000066400000000000000000000011541337661136700211120ustar00rootroot00000000000000// // Copyright CharLS Team, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_CONSTANTS #define CHARLS_CONSTANTS // Default threshold values for JPEG-LS statistical modeling as defined in ISO/IEC 14495-1, Table C.3 // for the case MAXVAL = 255 and NEAR = 0. // Can be overridden at compression time, however this is rarely done. const int DefaultThreshold1 = 3; // BASIC_T1 const int DefaultThreshold2 = 7; // BASIC_T2 const int DefaultThreshold3 = 21; // BASIC_T3 const int DefaultResetValue = 64; // Default RESET value as defined in ISO/IEC 14495-1, table C.2 #endif dcm2niix-1.0.20181125/console/charls/context.h000066400000000000000000000043711337661136700205660ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_CONTEXT #define CHARLS_CONTEXT #include // // Purpose: a JPEG-LS context with it's current statistics. // struct JlsContext { int32_t A; int32_t B; int16_t C; int16_t N; JlsContext() noexcept : A(), B(), C(), N(1) { } explicit JlsContext(int32_t a) noexcept : A(a), B(0), C(0), N(1) { } FORCE_INLINE int32_t GetErrorCorrection(int32_t k) const noexcept { if (k != 0) return 0; return BitWiseSign(2 * B + N - 1); } FORCE_INLINE void UpdateVariables(int32_t errorValue, int32_t NEAR, int32_t NRESET) noexcept { ASSERT(N != 0); // For performance work on copies of A,B,N (compiler will use registers). int a = A + std::abs(errorValue); int b = B + errorValue * (2 * NEAR + 1); int n = N; ASSERT(a < 65536 * 256); ASSERT(std::abs(b) < 65536 * 256); if (n == NRESET) { a = a >> 1; b = b >> 1; n = n >> 1; } A = a; n = n + 1; N = static_cast(n); if (b + n <= 0) { b = b + n; if (b <= -n) { b = -n + 1; } C = C - (C > -128); } else if (b > 0) { b = b - n; if (b > 0) { b = 0; } C = C + (C < 127); } B = b; ASSERT(N != 0); } FORCE_INLINE int32_t GetGolomb() const noexcept { const int32_t Ntest = N; const int32_t Atest = A; if (Ntest >= Atest) return 0; if (Ntest << 1 >= Atest) return 1; if (Ntest << 2 >= Atest) return 2; if (Ntest << 3 >= Atest) return 3; if (Ntest << 4 >= Atest) return 4; int32_t k = 5; for(; (Ntest << k) < Atest; k++) { ASSERT(k <= 32); } return k; } }; #endif dcm2niix-1.0.20181125/console/charls/contextrunmode.h000066400000000000000000000046241337661136700221610ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_CONTEXTRUNMODE #define CHARLS_CONTEXTRUNMODE #include // Implements statistical modeling for the run mode context. // Computes model dependent parameters like the Golomb code lengths struct CContextRunMode { // Note: members are sorted based on their size. int32_t A; int32_t _nRItype; uint8_t _nReset; uint8_t N; uint8_t Nn; CContextRunMode() noexcept : A(), _nRItype(), _nReset(), N(), Nn() { } CContextRunMode(int32_t a, int32_t nRItype, int32_t nReset) noexcept : A(a), _nRItype(nRItype), _nReset(static_cast(nReset)), N(1), Nn(0) { } FORCE_INLINE int32_t GetGolomb() const noexcept { const int32_t TEMP = A + (N >> 1) * _nRItype; int32_t Ntest = N; int32_t k = 0; for (; Ntest < TEMP; k++) { Ntest <<= 1; ASSERT(k <= 32); } return k; } void UpdateVariables(int32_t Errval, int32_t EMErrval) noexcept { if (Errval < 0) { Nn = Nn + 1; } A = A + ((EMErrval + 1 - _nRItype) >> 1); if (N == _nReset) { A = A >> 1; N = N >> 1; Nn = Nn >> 1; } N = N + 1; } FORCE_INLINE int32_t ComputeErrVal(int32_t temp, int32_t k) const noexcept { const bool map = temp & 1; const int32_t errvalabs = (temp + static_cast(map)) / 2; if ((k != 0 || (2 * Nn >= N)) == map) { ASSERT(map == ComputeMap(-errvalabs, k)); return -errvalabs; } ASSERT(map == ComputeMap(errvalabs, k)); return errvalabs; } bool ComputeMap(int32_t Errval, int32_t k) const noexcept { if ((k == 0) && (Errval > 0) && (2 * Nn < N)) return true; if ((Errval < 0) && (2 * Nn >= N)) return true; if ((Errval < 0) && (k != 0)) return true; return false; } FORCE_INLINE bool ComputeMapNegativeE(int32_t k) const noexcept { return k != 0 || (2 * Nn >= N); } }; #endif dcm2niix-1.0.20181125/console/charls/decoderstrategy.h000066400000000000000000000203351337661136700222700ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_DECODERSTATEGY #define CHARLS_DECODERSTATEGY #include "util.h" #include "processline.h" #include // Purpose: Implements encoding to stream of bits. In encoding mode JpegLsCodec inherits from EncoderStrategy class DecoderStrategy { public: explicit DecoderStrategy(const JlsParameters& params) : _params(params), _byteStream(nullptr), _readCache(0), _validBits(0), _position(nullptr), _nextFFPosition(nullptr), _endPosition(nullptr) { } virtual ~DecoderStrategy() = default; DecoderStrategy(const DecoderStrategy&) = delete; DecoderStrategy(DecoderStrategy&&) = delete; DecoderStrategy& operator=(const DecoderStrategy&) = delete; DecoderStrategy& operator=(DecoderStrategy&&) = delete; virtual std::unique_ptr CreateProcess(ByteStreamInfo rawStreamInfo) = 0; virtual void SetPresets(const JpegLSPresetCodingParameters& presets) = 0; virtual void DecodeScan(std::unique_ptr outputData, const JlsRect& size, ByteStreamInfo& compressedData) = 0; void Init(ByteStreamInfo& compressedStream) { _validBits = 0; _readCache = 0; if (compressedStream.rawStream) { _buffer.resize(40000); _position = _buffer.data(); _endPosition = _position; _byteStream = compressedStream.rawStream; AddBytesFromStream(); } else { _byteStream = nullptr; _position = compressedStream.rawData; _endPosition = _position + compressedStream.count; } _nextFFPosition = FindNextFF(); MakeValid(); } void AddBytesFromStream() { if (!_byteStream || _byteStream->sgetc() == std::char_traits::eof()) return; const std::size_t count = _endPosition - _position; if (count > 64) return; for (std::size_t i = 0; i < count; ++i) { _buffer[i] = _position[i]; } const std::size_t offset = _buffer.data() - _position; _position += offset; _endPosition += offset; _nextFFPosition += offset; const std::streamsize readbytes = _byteStream->sgetn(reinterpret_cast(_endPosition), _buffer.size() - count); _endPosition += readbytes; } FORCE_INLINE void Skip(int32_t length) noexcept { _validBits -= length; _readCache = _readCache << length; } static void OnLineBegin(int32_t /*cpixel*/, void* /*ptypeBuffer*/, int32_t /*pixelStride*/) noexcept { } void OnLineEnd(int32_t pixelCount, const void* ptypeBuffer, int32_t pixelStride) const { _processLine->NewLineDecoded(ptypeBuffer, pixelCount, pixelStride); } void EndScan() { if ((*_position) != 0xFF) { ReadBit(); if ((*_position) != 0xFF) throw charls_error(charls::ApiResult::TooMuchCompressedData); } if (_readCache != 0) throw charls_error(charls::ApiResult::TooMuchCompressedData); } FORCE_INLINE bool OptimizedRead() noexcept { // Easy & fast: if there is no 0xFF byte in sight, we can read without bit stuffing if (_position < _nextFFPosition - (sizeof(bufType)-1)) { _readCache |= FromBigEndian::Read(_position) >> _validBits; const int bytesToRead = (bufType_bit_count - _validBits) >> 3; _position += bytesToRead; _validBits += bytesToRead * 8; ASSERT(static_cast(_validBits) >= bufType_bit_count - 8); return true; } return false; } void MakeValid() { ASSERT(static_cast(_validBits) <=bufType_bit_count - 8); if (OptimizedRead()) return; AddBytesFromStream(); do { if (_position >= _endPosition) { if (_validBits <= 0) throw charls_error(charls::ApiResult::InvalidCompressedData); return; } const bufType valnew = _position[0]; if (valnew == 0xFF) { // JPEG bit stream rule: no FF may be followed by 0x80 or higher if (_position == _endPosition - 1 || (_position[1] & 0x80) != 0) { if (_validBits <= 0) throw charls_error(charls::ApiResult::InvalidCompressedData); return; } } _readCache |= valnew << (bufType_bit_count - 8 - _validBits); _position += 1; _validBits += 8; if (valnew == 0xFF) { _validBits--; } } while (static_cast(_validBits) < bufType_bit_count - 8); _nextFFPosition = FindNextFF(); } uint8_t* FindNextFF() const noexcept { auto positionNextFF = _position; while (positionNextFF < _endPosition) { if (*positionNextFF == 0xFF) break; positionNextFF++; } return positionNextFF; } uint8_t* GetCurBytePos() const noexcept { int32_t validBits = _validBits; uint8_t* compressedBytes = _position; for (;;) { const int32_t cbitLast = compressedBytes[-1] == 0xFF ? 7 : 8; if (validBits < cbitLast) return compressedBytes; validBits -= cbitLast; compressedBytes--; } } FORCE_INLINE int32_t ReadValue(int32_t length) { if (_validBits < length) { MakeValid(); if (_validBits < length) throw charls_error(charls::ApiResult::InvalidCompressedData); } ASSERT(length != 0 && length <= _validBits); ASSERT(length < 32); const auto result = static_cast(_readCache >> (bufType_bit_count - length)); Skip(length); return result; } FORCE_INLINE int32_t PeekByte() { if (_validBits < 8) { MakeValid(); } return static_cast(_readCache >> (bufType_bit_count - 8)); } FORCE_INLINE bool ReadBit() { if (_validBits <= 0) { MakeValid(); } const bool bSet = (_readCache & (static_cast(1) << (bufType_bit_count - 1))) != 0; Skip(1); return bSet; } FORCE_INLINE int32_t Peek0Bits() { if (_validBits < 16) { MakeValid(); } bufType valTest = _readCache; for (int32_t count = 0; count < 16; count++) { if ((valTest & (static_cast(1) << (bufType_bit_count - 1))) != 0) return count; valTest <<= 1; } return -1; } FORCE_INLINE int32_t ReadHighbits() { const int32_t count = Peek0Bits(); if (count >= 0) { Skip(count + 1); return count; } Skip(15); for (int32_t highbits = 15; ; highbits++) { if (ReadBit()) return highbits; } } int32_t ReadLongValue(int32_t length) { if (length <= 24) return ReadValue(length); return (ReadValue(length - 24) << 24) + ReadValue(24); } protected: JlsParameters _params; std::unique_ptr _processLine; private: using bufType = std::size_t; static constexpr size_t bufType_bit_count = sizeof(bufType) * 8; std::vector _buffer; std::basic_streambuf* _byteStream; // decoding bufType _readCache; int32_t _validBits; uint8_t* _position; uint8_t* _nextFFPosition; uint8_t* _endPosition; }; #endif dcm2niix-1.0.20181125/console/charls/defaulttraits.h000066400000000000000000000076451337661136700217640ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_DEFAULTTRAITS #define CHARLS_DEFAULTTRAITS #include "util.h" #include "constants.h" #include #include // Default traits that support all JPEG LS parameters: custom limit, near, maxval (not power of 2) // This traits class is used to initialize a coder/decoder. // The coder/decoder also delegates some functions to the traits class. // This is to allow the traits class to replace the default implementation here with optimized specific implementations. // This is done for lossless coding/decoding: see losslesstraits.h WARNING_SUPPRESS(26432) template struct DefaultTraits { using SAMPLE = sample; using PIXEL = pixel; int32_t MAXVAL; const int32_t RANGE; const int32_t NEAR; const int32_t qbpp; const int32_t bpp; const int32_t LIMIT; const int32_t RESET; DefaultTraits(int32_t max, int32_t near, int32_t reset = DefaultResetValue) noexcept : MAXVAL(max), RANGE((max + 2 * near) / (2 * near + 1) + 1), NEAR(near), qbpp(log_2(RANGE)), bpp(log_2(max)), LIMIT(2 * (bpp + std::max(8, bpp))), RESET(reset) { } DefaultTraits(const DefaultTraits& other) noexcept : MAXVAL(other.MAXVAL), RANGE(other.RANGE), NEAR(other.NEAR), qbpp(other.qbpp), bpp(other.bpp), LIMIT(other.LIMIT), RESET(other.RESET) { } DefaultTraits() = delete; DefaultTraits(DefaultTraits&&) = default; DefaultTraits& operator=(const DefaultTraits&) = delete; DefaultTraits& operator=(DefaultTraits&&) = delete; FORCE_INLINE int32_t ComputeErrVal(int32_t e) const noexcept { return ModuloRange(Quantize(e)); } FORCE_INLINE SAMPLE ComputeReconstructedSample(int32_t Px, int32_t ErrVal) const noexcept { return FixReconstructedValue(Px + DeQuantize(ErrVal)); } FORCE_INLINE bool IsNear(int32_t lhs, int32_t rhs) const noexcept { return std::abs(lhs - rhs) <= NEAR; } bool IsNear(Triplet lhs, Triplet rhs) const noexcept { return std::abs(lhs.v1 - rhs.v1) <= NEAR && std::abs(lhs.v2 - rhs.v2) <= NEAR && std::abs(lhs.v3 - rhs.v3) <= NEAR; } FORCE_INLINE int32_t CorrectPrediction(int32_t Pxc) const noexcept { if ((Pxc & MAXVAL) == Pxc) return Pxc; return (~(Pxc >> (int32_t_bit_count-1))) & MAXVAL; } /// /// Returns the value of errorValue modulo RANGE. ITU.T.87, A.4.5 (code segment A.9) /// FORCE_INLINE int32_t ModuloRange(int32_t errorValue) const noexcept { ASSERT(std::abs(errorValue) <= RANGE); if (errorValue < 0) { errorValue += RANGE; } if (errorValue >= (RANGE + 1) / 2) { errorValue -= RANGE; } ASSERT(-RANGE / 2 <= errorValue && errorValue <= (RANGE / 2) - 1); return errorValue; } private: int32_t Quantize(int32_t Errval) const noexcept { if (Errval > 0) return (Errval + NEAR) / (2 * NEAR + 1); return - (NEAR - Errval) / (2 * NEAR + 1); } FORCE_INLINE int32_t DeQuantize(int32_t Errval) const noexcept { return Errval * (2 * NEAR + 1); } FORCE_INLINE SAMPLE FixReconstructedValue(int32_t val) const noexcept { if (val < -NEAR) { val = val + RANGE * (2 * NEAR + 1); } else if (val > MAXVAL + NEAR) { val = val - RANGE * (2 * NEAR + 1); } return static_cast(CorrectPrediction(val)); } }; WARNING_UNSUPPRESS() #endif dcm2niix-1.0.20181125/console/charls/encoderstrategy.h000066400000000000000000000133311337661136700223000ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_ENCODERSTRATEGY #define CHARLS_ENCODERSTRATEGY #include "processline.h" #include "decoderstrategy.h" // Purpose: Implements encoding to stream of bits. In encoding mode JpegLsCodec inherits from EncoderStrategy class EncoderStrategy { public: explicit EncoderStrategy(const JlsParameters& params) : _params(params), _bitBuffer(0), _freeBitCount(sizeof(_bitBuffer) * 8), _compressedLength(0), _position(nullptr), _isFFWritten(false), _bytesWritten(0), _compressedStream(nullptr) { } virtual ~EncoderStrategy() = default; EncoderStrategy(const EncoderStrategy&) = delete; EncoderStrategy(EncoderStrategy&&) = delete; EncoderStrategy& operator=(const EncoderStrategy&) = delete; EncoderStrategy& operator=(EncoderStrategy&&) = delete; virtual std::unique_ptr CreateProcess(ByteStreamInfo rawStreamInfo) = 0; virtual void SetPresets(const JpegLSPresetCodingParameters& presets) = 0; virtual std::size_t EncodeScan(std::unique_ptr rawData, ByteStreamInfo& compressedData) = 0; int32_t PeekByte(); void OnLineBegin(int32_t cpixel, void* ptypeBuffer, int32_t pixelStride) const { _processLine->NewLineRequested(ptypeBuffer, cpixel, pixelStride); } static void OnLineEnd(int32_t /*cpixel*/, void* /*ptypeBuffer*/, int32_t /*pixelStride*/) noexcept { } protected: void Init(ByteStreamInfo& compressedStream) { _freeBitCount = sizeof(_bitBuffer) * 8; _bitBuffer = 0; if (compressedStream.rawStream) { _compressedStream = compressedStream.rawStream; _buffer.resize(4000); _position = _buffer.data(); _compressedLength = _buffer.size(); } else { _position = compressedStream.rawData; _compressedLength = compressedStream.count; } } void AppendToBitStream(int32_t bits, int32_t bitCount) { ASSERT(bitCount < 32 && bitCount >= 0); ASSERT((!_qdecoder) || (bitCount == 0 && bits == 0) ||( _qdecoder->ReadLongValue(bitCount) == bits)); #ifndef NDEBUG const int mask = (1u << (bitCount)) - 1; ASSERT((bits | mask) == mask); // Not used bits must be set to zero. #endif _freeBitCount -= bitCount; if (_freeBitCount >= 0) { _bitBuffer |= bits << _freeBitCount; } else { // Add as much bits in the remaining space as possible and flush. _bitBuffer |= bits >> -_freeBitCount; Flush(); // A second flush may be required if extra marker detect bits were needed and not all bits could be written. if (_freeBitCount < 0) { _bitBuffer |= bits >> -_freeBitCount; Flush(); } ASSERT(_freeBitCount >= 0); _bitBuffer |= bits << _freeBitCount; } } void EndScan() { Flush(); // if a 0xff was written, Flush() will force one unset bit anyway if (_isFFWritten) AppendToBitStream(0, (_freeBitCount - 1) % 8); else AppendToBitStream(0, _freeBitCount % 8); Flush(); ASSERT(_freeBitCount == 0x20); if (_compressedStream) { OverFlow(); } } void OverFlow() { if (!_compressedStream) throw charls_error(charls::ApiResult::CompressedBufferTooSmall); const std::size_t bytesCount = _position - _buffer.data(); const auto bytesWritten = static_cast(_compressedStream->sputn(reinterpret_cast(_buffer.data()), _position - _buffer.data())); if (bytesWritten != bytesCount) throw charls_error(charls::ApiResult::CompressedBufferTooSmall); _position = _buffer.data(); _compressedLength = _buffer.size(); } void Flush() { if (_compressedLength < 4) { OverFlow(); } for (int i = 0; i < 4; ++i) { if (_freeBitCount >= 32) break; if (_isFFWritten) { // JPEG-LS requirement (T.87, A.1) to detect markers: after a xFF value a single 0 bit needs to be inserted. *_position = static_cast(_bitBuffer >> 25); _bitBuffer = _bitBuffer << 7; _freeBitCount += 7; } else { *_position = static_cast(_bitBuffer >> 24); _bitBuffer = _bitBuffer << 8; _freeBitCount += 8; } _isFFWritten = *_position == 0xFF; _position++; _compressedLength--; _bytesWritten++; } } std::size_t GetLength() const noexcept { return _bytesWritten - (_freeBitCount - 32) / 8; } FORCE_INLINE void AppendOnesToBitStream(int32_t length) { AppendToBitStream((1 << length) - 1, length); } std::unique_ptr _qdecoder; JlsParameters _params; std::unique_ptr _processLine; private: unsigned int _bitBuffer; int32_t _freeBitCount; std::size_t _compressedLength; // encoding uint8_t* _position; bool _isFFWritten; std::size_t _bytesWritten; std::vector _buffer; std::basic_streambuf* _compressedStream; }; #endif dcm2niix-1.0.20181125/console/charls/interface.cpp000066400000000000000000000203161337661136700213720ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "charls.h" #include "util.h" #include "jpegstreamreader.h" #include "jpegstreamwriter.h" #include "jpegmarkersegment.h" #include using namespace charls; namespace { void VerifyInput(const ByteStreamInfo& uncompressedStream, const JlsParameters& parameters) { if (!uncompressedStream.rawStream && !uncompressedStream.rawData) throw charls_error(ApiResult::InvalidJlsParameters, "rawStream or rawData needs to reference to something"); if (parameters.width < 1 || parameters.width > 65535) throw charls_error(ApiResult::InvalidJlsParameters, "width needs to be in the range [1, 65535]"); if (parameters.height < 1 || parameters.height > 65535) throw charls_error(ApiResult::InvalidJlsParameters, "height needs to be in the range [1, 65535]"); if (parameters.bitsPerSample < 2 || parameters.bitsPerSample > 16) throw charls_error(ApiResult::InvalidJlsParameters, "bitspersample needs to be in the range [2, 16]"); if (!(parameters.interleaveMode == InterleaveMode::None || parameters.interleaveMode == InterleaveMode::Sample || parameters.interleaveMode == InterleaveMode::Line)) throw charls_error(ApiResult::InvalidJlsParameters, "interleaveMode needs to be set to a value of {None, Sample, Line}"); if (parameters.components < 1 || parameters.components > 255) throw charls_error(ApiResult::InvalidJlsParameters, "components needs to be in the range [1, 255]"); if (uncompressedStream.rawData) { if (uncompressedStream.count < static_cast(parameters.height) * parameters.width * parameters.components * (parameters.bitsPerSample > 8 ? 2 : 1)) throw charls_error(ApiResult::InvalidJlsParameters, "uncompressed size does not match with the other parameters"); } switch (parameters.components) { case 3: break; case 4: if (parameters.interleaveMode == InterleaveMode::Sample) throw charls_error(ApiResult::InvalidJlsParameters, "interleaveMode cannot be set to Sample in combination with components = 4"); break; default: if (parameters.interleaveMode != InterleaveMode::None) throw charls_error(ApiResult::InvalidJlsParameters, "interleaveMode can only be set to None in combination with components = 1"); break; } } ApiResult ResultAndErrorMessage(ApiResult result, char* errorMessage) noexcept { if (errorMessage) { errorMessage[0] = 0; } return result; } ApiResult ResultAndErrorMessageFromException(char* errorMessage) { try { // re-trow the exception. throw; } catch (const charls_error& error) { if (errorMessage) { ASSERT(strlen(error.what()) < ErrorMessageSize); strcpy(errorMessage, error.what()); } return static_cast(error.code().value()); } catch (...) { return ResultAndErrorMessage(ApiResult::UnexpectedFailure, errorMessage); } } } // namespace CHARLS_IMEXPORT(ApiResult) JpegLsEncodeStream(ByteStreamInfo compressedStreamInfo, size_t& pcbyteWritten, ByteStreamInfo rawStreamInfo, const struct JlsParameters& params, char* errorMessage) { try { VerifyInput(rawStreamInfo, params); JlsParameters info = params; if (info.stride == 0) { info.stride = info.width * ((info.bitsPerSample + 7)/8); if (info.interleaveMode != InterleaveMode::None) { info.stride *= info.components; } } JpegStreamWriter writer; if (info.jfif.version) { writer.AddSegment(JpegMarkerSegment::CreateJpegFileInterchangeFormatSegment(info.jfif)); } writer.AddSegment(JpegMarkerSegment::CreateStartOfFrameSegment(info.width, info.height, info.bitsPerSample, info.components)); if (info.colorTransformation != ColorTransformation::None) { writer.AddColorTransform(info.colorTransformation); } if (info.interleaveMode == InterleaveMode::None) { const int32_t cbyteComp = info.width * info.height * ((info.bitsPerSample + 7) / 8); for (int32_t component = 0; component < info.components; ++component) { writer.AddScan(rawStreamInfo, info); SkipBytes(rawStreamInfo, cbyteComp); } } else { writer.AddScan(rawStreamInfo, info); } writer.Write(compressedStreamInfo); pcbyteWritten = writer.GetBytesWritten(); return ResultAndErrorMessage(ApiResult::OK, errorMessage); } catch (...) { return ResultAndErrorMessageFromException(errorMessage); } } CHARLS_IMEXPORT(ApiResult) JpegLsDecodeStream(ByteStreamInfo rawStream, ByteStreamInfo compressedStream, const JlsParameters* info, char* errorMessage) { try { JpegStreamReader reader(compressedStream); if (info) { reader.SetInfo(*info); } reader.Read(rawStream); return ResultAndErrorMessage(ApiResult::OK, errorMessage); } catch (...) { return ResultAndErrorMessageFromException(errorMessage); } } CHARLS_IMEXPORT(ApiResult) JpegLsReadHeaderStream(ByteStreamInfo rawStreamInfo, JlsParameters* params, char* errorMessage) { try { JpegStreamReader reader(rawStreamInfo); reader.ReadHeader(); reader.ReadStartOfScan(true); *params = reader.GetMetadata(); return ResultAndErrorMessage(ApiResult::OK, errorMessage); } catch (...) { return ResultAndErrorMessageFromException(errorMessage); } } extern "C" { CHARLS_IMEXPORT(ApiResult) JpegLsEncode(void* destination, size_t destinationLength, size_t* bytesWritten, const void* source, size_t sourceLength, const struct JlsParameters* params, char* errorMessage) { if (!destination || !bytesWritten || !source || !params) return ApiResult::InvalidJlsParameters; const ByteStreamInfo rawStreamInfo = FromByteArrayConst(source, sourceLength); const ByteStreamInfo compressedStreamInfo = FromByteArray(destination, destinationLength); return JpegLsEncodeStream(compressedStreamInfo, *bytesWritten, rawStreamInfo, *params, errorMessage); } CHARLS_IMEXPORT(ApiResult) JpegLsReadHeader(const void* compressedData, size_t compressedLength, JlsParameters* params, char* errorMessage) { return JpegLsReadHeaderStream(FromByteArrayConst(compressedData, compressedLength), params, errorMessage); } CHARLS_IMEXPORT(ApiResult) JpegLsDecode(void* destination, size_t destinationLength, const void* source, size_t sourceLength, const struct JlsParameters* params, char* errorMessage) { const ByteStreamInfo compressedStream = FromByteArrayConst(source, sourceLength); const ByteStreamInfo rawStreamInfo = FromByteArray(destination, destinationLength); return JpegLsDecodeStream(rawStreamInfo, compressedStream, params, errorMessage); } CHARLS_IMEXPORT(ApiResult) JpegLsDecodeRect(void* uncompressedData, size_t uncompressedLength, const void* compressedData, size_t compressedLength, JlsRect roi, const JlsParameters* info, char* errorMessage) { try { const ByteStreamInfo compressedStream = FromByteArrayConst(compressedData, compressedLength); JpegStreamReader reader(compressedStream); const ByteStreamInfo rawStreamInfo = FromByteArray(uncompressedData, uncompressedLength); if (info) { reader.SetInfo(*info); } reader.SetRect(roi); reader.Read(rawStreamInfo); return ResultAndErrorMessage(ApiResult::OK, errorMessage); } catch (...) { return ResultAndErrorMessageFromException(errorMessage); } } } dcm2niix-1.0.20181125/console/charls/jlscodecfactory.h000066400000000000000000000010171337661136700222520ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JLSCODECFACTORY #define CHARLS_JLSCODECFACTORY #include struct JlsParameters; struct JpegLSPresetCodingParameters; template class JlsCodecFactory { public: std::unique_ptr CreateCodec(const JlsParameters& params, const JpegLSPresetCodingParameters& presets); private: std::unique_ptr CreateOptimizedCodec(const JlsParameters& params); }; #endif dcm2niix-1.0.20181125/console/charls/jpegimagedatasegment.h000066400000000000000000000012561337661136700232460ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGIMAGEDATASEGMENT #define CHARLS_JPEGIMAGEDATASEGMENT #include "jpegsegment.h" #include "jpegstreamwriter.h" class JpegImageDataSegment : public JpegSegment { public: JpegImageDataSegment(ByteStreamInfo rawStream, const JlsParameters& params, int componentCount) noexcept : _componentCount(componentCount), _rawStreamInfo(rawStream), _params(params) { } void Serialize(JpegStreamWriter& streamWriter) override; private: int _componentCount; ByteStreamInfo _rawStreamInfo; JlsParameters _params; }; #endif dcm2niix-1.0.20181125/console/charls/jpegls.cpp000066400000000000000000000143061337661136700207200ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "util.h" #include "decoderstrategy.h" #include "encoderstrategy.h" #include "lookuptable.h" #include "losslesstraits.h" #include "defaulttraits.h" #include "jlscodecfactory.h" #include "jpegstreamreader.h" #include using namespace charls; // As defined in the JPEG-LS standard // used to determine how large runs should be encoded at a time. const int J[32] = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 10, 11, 12, 13, 14, 15}; #include "scan.h" namespace { signed char QuantizeGratientOrg(const JpegLSPresetCodingParameters& preset, int32_t NEAR, int32_t Di) noexcept { if (Di <= -preset.Threshold3) return -4; if (Di <= -preset.Threshold2) return -3; if (Di <= -preset.Threshold1) return -2; if (Di < -NEAR) return -1; if (Di <= NEAR) return 0; if (Di < preset.Threshold1) return 1; if (Di < preset.Threshold2) return 2; if (Di < preset.Threshold3) return 3; return 4; } std::vector CreateQLutLossless(int32_t cbit) { const JpegLSPresetCodingParameters preset = ComputeDefault((1u << static_cast(cbit)) - 1, 0); const int32_t range = preset.MaximumSampleValue + 1; std::vector lut(static_cast(range) * 2); for (int32_t diff = -range; diff < range; diff++) { lut[static_cast(range) + diff] = QuantizeGratientOrg(preset, 0,diff); } return lut; } template std::unique_ptr create_codec(const Traits& traits, const JlsParameters& params) { return std::make_unique>(traits, params); } } // namespace class charls_category : public std::error_category { public: const char* name() const noexcept override { return "charls"; } std::string message(int /* errval */) const override { return "CharLS error"; } }; const std::error_category& charls_error::CharLSCategoryInstance() noexcept { static charls_category instance; return instance; } // Lookup tables to replace code with lookup tables. // To avoid threading issues, all tables are created when the program is loaded. // Lookup table: decode symbols that are smaller or equal to 8 bit (16 tables for each value of k) CTable decodingTables[16] = { InitTable(0), InitTable(1), InitTable(2), InitTable(3), InitTable(4), InitTable(5), InitTable(6), InitTable(7), InitTable(8), InitTable(9), InitTable(10), InitTable(11), InitTable(12), InitTable(13), InitTable(14),InitTable(15) }; // Lookup tables: sample differences to bin indexes. std::vector rgquant8Ll = CreateQLutLossless(8); std::vector rgquant10Ll = CreateQLutLossless(10); std::vector rgquant12Ll = CreateQLutLossless(12); std::vector rgquant16Ll = CreateQLutLossless(16); template std::unique_ptr JlsCodecFactory::CreateCodec(const JlsParameters& params, const JpegLSPresetCodingParameters& presets) { std::unique_ptr codec; if (presets.ResetValue == 0 || presets.ResetValue == DefaultResetValue) { codec = CreateOptimizedCodec(params); } if (!codec) { if (params.bitsPerSample <= 8) { DefaultTraits traits((1 << params.bitsPerSample) - 1, params.allowedLossyError, presets.ResetValue); traits.MAXVAL = presets.MaximumSampleValue; codec = std::make_unique, Strategy>>(traits, params); } else { DefaultTraits traits((1 << params.bitsPerSample) - 1, params.allowedLossyError, presets.ResetValue); traits.MAXVAL = presets.MaximumSampleValue; codec = std::make_unique, Strategy>>(traits, params); } } codec->SetPresets(presets); return codec; } template std::unique_ptr JlsCodecFactory::CreateOptimizedCodec(const JlsParameters& params) { if (params.interleaveMode == InterleaveMode::Sample && params.components != 3) return nullptr; #ifndef DISABLE_SPECIALIZATIONS // optimized lossless versions common formats if (params.allowedLossyError == 0) { if (params.interleaveMode == InterleaveMode::Sample) { if (params.bitsPerSample == 8) return create_codec(LosslessTraits, 8>(), params); } else { switch (params.bitsPerSample) { case 8: return create_codec(LosslessTraits(), params); case 12: return create_codec(LosslessTraits(), params); case 16: return create_codec(LosslessTraits(), params); default: break; } } } #endif const int maxval = (1u << static_cast(params.bitsPerSample)) - 1; if (params.bitsPerSample <= 8) { if (params.interleaveMode == InterleaveMode::Sample) return create_codec(DefaultTraits >(maxval, params.allowedLossyError), params); return create_codec(DefaultTraits((1u << params.bitsPerSample) - 1, params.allowedLossyError), params); } if (params.bitsPerSample <= 16) { if (params.interleaveMode == InterleaveMode::Sample) return create_codec(DefaultTraits >(maxval, params.allowedLossyError), params); return create_codec(DefaultTraits(maxval, params.allowedLossyError), params); } return nullptr; } template class JlsCodecFactory; template class JlsCodecFactory; dcm2niix-1.0.20181125/console/charls/jpegmarkercode.h000066400000000000000000000050461337661136700220640ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #pragma once #include // JPEG Marker codes have the pattern 0xFFaa in a JPEG byte stream. // The valid 'aa' options are defined by several ITU / IEC standards: // 0x00, 0x01, 0xFE, 0xC0-0xDF are defined in ITU T.81/IEC 10918-1 // 0xF0 - 0xF6 are defined in ITU T.84/IEC 10918-3 JPEG extensions // 0xF7 - 0xF8 are defined in ITU T.87/IEC 14495-1 JPEG LS // 0x4F - 0x6F, 0x90 - 0x93 are defined in JPEG 2000 IEC 15444-1 enum class JpegMarkerCode : uint8_t { StartOfImage = 0xD8, // SOI: Marks the start of an image. EndOfImage = 0xD9, // EOI: Marks the end of an image. StartOfScan = 0xDA, // SOS: Marks the start of scan. // The following markers are defined in ITU T.81 | ISO IEC 10918-1. StartOfFrameBaselineJpeg = 0xC0, // SOF_0: Marks the start of a baseline jpeg encoded frame. StartOfFrameExtendedSequential = 0xC1, // SOF_1: Marks the start of a extended sequential Huffman encoded frame. StartOfFrameProgressive = 0xC2, // SOF_2: Marks the start of a progressive Huffman encoded frame. StartOfFrameLossless = 0xC3, // SOF_3: Marks the start of a lossless Huffman encoded frame. StartOfFrameDifferentialSequential = 0xC5, // SOF_5: Marks the start of a differential sequential Huffman encoded frame. StartOfFrameDifferentialProgressive = 0xC6, // SOF_6: Marks the start of a differential progressive Huffman encoded frame. StartOfFrameDifferentialLossless = 0xC7, // SOF_7: Marks the start of a differential lossless Huffman encoded frame. StartOfFrameExtendedArithemtic = 0xC9, // SOF_9: Marks the start of a extended sequential arithmetic encoded frame. StartOfFrameProgressiveArithemtic = 0xCA, // SOF_10: Marks the start of a progressive arithmetic encoded frame. StartOfFrameLosslessArithemtic = 0xCB, // SOF_11: Marks the start of a lossless arithmetic encoded frame. StartOfFrameJpegLS = 0xF7, // SOF_55: Marks the start of a JPEG-LS encoded frame. JpegLSPresetParameters = 0xF8, // LSE: Marks the start of a JPEG-LS preset parameters segment. ApplicationData0 = 0xE0, // APP0: Application data 0: used for JFIF header. ApplicationData7 = 0xE7, // APP7: Application data 7: color-space. ApplicationData8 = 0xE8, // APP8: Application data 8: colorXForm. Comment = 0xFE // COM: Comment block. }; dcm2niix-1.0.20181125/console/charls/jpegmarkersegment.cpp000066400000000000000000000117171337661136700231510ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "jpegmarkersegment.h" #include "jpegmarkercode.h" #include "util.h" #include #include using namespace charls; std::unique_ptr JpegMarkerSegment::CreateStartOfFrameSegment(int width, int height, int bitsPerSample, int componentCount) { ASSERT(width >= 0 && width <= UINT16_MAX); ASSERT(height >= 0 && height <= UINT16_MAX); ASSERT(bitsPerSample > 0 && bitsPerSample <= UINT8_MAX); ASSERT(componentCount > 0 && componentCount <= (UINT8_MAX - 1)); // Create a Frame Header as defined in T.87, C.2.2 and T.81, B.2.2 std::vector content; content.push_back(static_cast(bitsPerSample)); // P = Sample precision push_back(content, static_cast(height)); // Y = Number of lines push_back(content, static_cast(width)); // X = Number of samples per line // Components content.push_back(static_cast(componentCount)); // Nf = Number of image components in frame for (auto component = 0; component < componentCount; ++component) { // Component Specification parameters content.push_back(static_cast(component + 1)); // Ci = Component identifier content.push_back(0x11); // Hi + Vi = Horizontal sampling factor + Vertical sampling factor content.push_back(0); // Tqi = Quantization table destination selector (reserved for JPEG-LS, should be set to 0) } return std::make_unique(JpegMarkerCode::StartOfFrameJpegLS, move(content)); } std::unique_ptr JpegMarkerSegment::CreateJpegFileInterchangeFormatSegment(const JfifParameters& params) { ASSERT(params.units == 0 || params.units == 1 || params.units == 2); ASSERT(params.Xdensity > 0); ASSERT(params.Ydensity > 0); ASSERT(params.Xthumbnail >= 0 && params.Xthumbnail < 256); ASSERT(params.Ythumbnail >= 0 && params.Ythumbnail < 256); // Create a JPEG APP0 segment in the JPEG File Interchange Format (JFIF), v1.02 std::vector content { 'J', 'F', 'I', 'F', '\0' }; push_back(content, static_cast(params.version)); content.push_back(static_cast(params.units)); push_back(content, static_cast(params.Xdensity)); push_back(content, static_cast(params.Ydensity)); // thumbnail content.push_back(static_cast(params.Xthumbnail)); content.push_back(static_cast(params.Ythumbnail)); if (params.Xthumbnail > 0) { if (params.thumbnail) throw charls_error(ApiResult::InvalidJlsParameters, "params.Xthumbnail is > 0 but params.thumbnail == null_ptr"); content.insert(content.end(), static_cast(params.thumbnail), static_cast(params.thumbnail) + static_cast(3) * params.Xthumbnail * params.Ythumbnail); } return std::make_unique(JpegMarkerCode::ApplicationData0, move(content)); } std::unique_ptr JpegMarkerSegment::CreateJpegLSPresetParametersSegment(const JpegLSPresetCodingParameters& params) { std::vector content; // Parameter ID. 0x01 = JPEG-LS preset coding parameters. content.push_back(1); push_back(content, static_cast(params.MaximumSampleValue)); push_back(content, static_cast(params.Threshold1)); push_back(content, static_cast(params.Threshold2)); push_back(content, static_cast(params.Threshold3)); push_back(content, static_cast(params.ResetValue)); return std::make_unique(JpegMarkerCode::JpegLSPresetParameters, move(content)); } std::unique_ptr JpegMarkerSegment::CreateColorTransformSegment(ColorTransformation transformation) { return std::make_unique( JpegMarkerCode::ApplicationData8, std::vector { 'm', 'r', 'f', 'x', static_cast(transformation) }); } std::unique_ptr JpegMarkerSegment::CreateStartOfScanSegment(int componentIndex, int componentCount, int allowedLossyError, InterleaveMode interleaveMode) { ASSERT(componentIndex >= 0); ASSERT(componentCount > 0); // Create a Scan Header as defined in T.87, C.2.3 and T.81, B.2.3 std::vector content; content.push_back(static_cast(componentCount)); for (auto i = 0; i < componentCount; ++i) { content.push_back(static_cast(componentIndex + i)); content.push_back(0); // Mapping table selector (0 = no table) } content.push_back(static_cast(allowedLossyError)); // NEAR parameter content.push_back(static_cast(interleaveMode)); // ILV parameter content.push_back(0); // transformation return std::make_unique(JpegMarkerCode::StartOfScan, move(content)); } dcm2niix-1.0.20181125/console/charls/jpegmarkersegment.h000066400000000000000000000057101337661136700226120ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGMARKERSEGMENT #define CHARLS_JPEGMARKERSEGMENT #include "jpegsegment.h" #include "jpegstreamwriter.h" #include #include #include enum class JpegMarkerCode : uint8_t; class JpegMarkerSegment : public JpegSegment { public: /// /// Creates a JPEG-LS Start Of Frame (SOF-55) segment. /// /// The width of the frame. /// The height of the frame. /// The bits per sample. /// The component count. static std::unique_ptr CreateStartOfFrameSegment(int width, int height, int bitsPerSample, int componentCount); /// /// Creates a JPEG File Interchange (APP1 + jfif) segment. /// /// Parameters to write into the JFIF segment. static std::unique_ptr CreateJpegFileInterchangeFormatSegment(const JfifParameters& params); /// /// Creates a JPEG-LS preset parameters (LSE) segment. /// /// Parameters to write into the JPEG-LS preset segment. static std::unique_ptr CreateJpegLSPresetParametersSegment(const JpegLSPresetCodingParameters& params); /// /// Creates a color transformation (APP8) segment. /// /// Parameters to write into the JFIF segment. static std::unique_ptr CreateColorTransformSegment(charls::ColorTransformation transformation); /// /// Creates a JPEG-LS Start Of Scan (SOS) segment. /// /// The component index of the scan segment or the start index if component count > 1. /// The number of components in the scan segment. Can only be > 1 when the components are interleaved. /// The allowed lossy error. 0 means lossless. /// The interleave mode of the components. static std::unique_ptr CreateStartOfScanSegment(int componentIndex, int componentCount, int allowedLossyError, charls::InterleaveMode interleaveMode); JpegMarkerSegment(JpegMarkerCode markerCode, std::vector&& content) : _markerCode(markerCode), _content(content) { } void Serialize(JpegStreamWriter& streamWriter) override { streamWriter.WriteByte(0xFF); streamWriter.WriteByte(static_cast(_markerCode)); streamWriter.WriteWord(static_cast(_content.size() + 2)); streamWriter.WriteBytes(_content); } private: JpegMarkerCode _markerCode; std::vector _content; }; #endif dcm2niix-1.0.20181125/console/charls/jpegsegment.h000066400000000000000000000012041337661136700214020ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGSEGMENT #define CHARLS_JPEGSEGMENT class JpegStreamWriter; // // Purpose: base class for segments that can be written to JPEG streams. // class JpegSegment { public: virtual ~JpegSegment() = default; virtual void Serialize(JpegStreamWriter& streamWriter) = 0; JpegSegment(const JpegSegment&) = delete; JpegSegment(JpegSegment&&) = delete; JpegSegment& operator=(const JpegSegment&) = delete; JpegSegment& operator=(JpegSegment&&) = delete; protected: JpegSegment() = default; }; #endif dcm2niix-1.0.20181125/console/charls/jpegstreamreader.cpp000066400000000000000000000315021337661136700227550ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "jpegstreamreader.h" #include "util.h" #include "jpegstreamwriter.h" #include "jpegimagedatasegment.h" #include "jpegmarkercode.h" #include "decoderstrategy.h" #include "encoderstrategy.h" #include "jlscodecfactory.h" #include "constants.h" #include #include #include using namespace charls; extern template class JlsCodecFactory; extern template class JlsCodecFactory; namespace { // JFIF\0 uint8_t jfifID[] = { 'J', 'F', 'I', 'F', '\0' }; /// Clamping function as defined by ISO/IEC 14495-1, Figure C.3 int32_t clamp(int32_t i, int32_t j, int32_t maximumSampleValue) noexcept { if (i > maximumSampleValue || i < j) return j; return i; } ApiResult CheckParameterCoherent(const JlsParameters& params) noexcept { if (params.bitsPerSample < 2 || params.bitsPerSample > 16) return ApiResult::ParameterValueNotSupported; if (params.interleaveMode < InterleaveMode::None || params.interleaveMode > InterleaveMode::Sample) return ApiResult::InvalidCompressedData; switch (params.components) { case 4: return params.interleaveMode == InterleaveMode::Sample ? ApiResult::ParameterValueNotSupported : ApiResult::OK; case 3: return ApiResult::OK; case 0: return ApiResult::InvalidJlsParameters; default: return params.interleaveMode != InterleaveMode::None ? ApiResult::ParameterValueNotSupported : ApiResult::OK; } } } // namespace JpegLSPresetCodingParameters ComputeDefault(int32_t maximumSampleValue, int32_t allowedLossyError) noexcept { JpegLSPresetCodingParameters preset; const int32_t factor = (std::min(maximumSampleValue, 4095) + 128) / 256; const int threshold1 = clamp(factor * (DefaultThreshold1 - 2) + 2 + 3 * allowedLossyError, allowedLossyError + 1, maximumSampleValue); const int threshold2 = clamp(factor * (DefaultThreshold2 - 3) + 3 + 5 * allowedLossyError, threshold1, maximumSampleValue); //-V537 preset.Threshold1 = threshold1; preset.Threshold2 = threshold2; preset.Threshold3 = clamp(factor * (DefaultThreshold3 - 4) + 4 + 7 * allowedLossyError, threshold2, maximumSampleValue); preset.MaximumSampleValue = maximumSampleValue; preset.ResetValue = DefaultResetValue; return preset; } void JpegImageDataSegment::Serialize(JpegStreamWriter& streamWriter) { JlsParameters info = _params; info.components = _componentCount; auto codec = JlsCodecFactory().CreateCodec(info, _params.custom); std::unique_ptr processLine(codec->CreateProcess(_rawStreamInfo)); ByteStreamInfo compressedData = streamWriter.OutputStream(); const size_t cbyteWritten = codec->EncodeScan(move(processLine), compressedData); streamWriter.Seek(cbyteWritten); } JpegStreamReader::JpegStreamReader(ByteStreamInfo byteStreamInfo) noexcept : _byteStream(byteStreamInfo), _params(), _rect() { } void JpegStreamReader::Read(ByteStreamInfo rawPixels) { ReadHeader(); const auto result = CheckParameterCoherent(_params); if (result != ApiResult::OK) throw charls_error(result); if (_rect.Width <= 0) { _rect.Width = _params.width; _rect.Height = _params.height; } const int64_t bytesPerPlane = static_cast(_rect.Width) * _rect.Height * ((_params.bitsPerSample + 7)/8); if (rawPixels.rawData && static_cast(rawPixels.count) < bytesPerPlane * _params.components) throw charls_error(ApiResult::UncompressedBufferTooSmall); int componentIndex = 0; while (componentIndex < _params.components) { ReadStartOfScan(componentIndex == 0); std::unique_ptr qcodec = JlsCodecFactory().CreateCodec(_params, _params.custom); std::unique_ptr processLine(qcodec->CreateProcess(rawPixels)); qcodec->DecodeScan(move(processLine), _rect, _byteStream); SkipBytes(rawPixels, static_cast(bytesPerPlane)); if (_params.interleaveMode != InterleaveMode::None) return; componentIndex += 1; } } void JpegStreamReader::ReadNBytes(std::vector& dst, int byteCount) { for (int i = 0; i < byteCount; ++i) { dst.push_back(static_cast(ReadByte())); } } void JpegStreamReader::ReadHeader() { if (ReadNextMarker() != JpegMarkerCode::StartOfImage) throw charls_error(ApiResult::InvalidCompressedData); for (;;) { const JpegMarkerCode marker = ReadNextMarker(); if (marker == JpegMarkerCode::StartOfScan) return; const int32_t cbyteMarker = ReadWord(); const int bytesRead = ReadMarker(marker) + 2; const int paddingToRead = cbyteMarker - bytesRead; if (paddingToRead < 0) throw charls_error(ApiResult::InvalidCompressedData); for (int i = 0; i < paddingToRead; ++i) { ReadByte(); } } } JpegMarkerCode JpegStreamReader::ReadNextMarker() { auto byte = ReadByte(); if (byte != 0xFF) { std::ostringstream message; message << std::setfill('0'); message << "Expected JPEG Marker start byte 0xFF but the byte value was 0x" << std::hex << std::uppercase << std::setw(2) << static_cast(byte); throw charls_error(ApiResult::MissingJpegMarkerStart, message.str()); } // Read all preceding 0xFF fill values until a non 0xFF value has been found. (see T.81, B.1.1.2) do { byte = ReadByte(); } while (byte == 0xFF); return static_cast(byte); } int JpegStreamReader::ReadMarker(JpegMarkerCode marker) { // ISO/IEC 14495-1, ITU-T Recommendation T.87, C.1.1. defines the following markers valid for a JPEG-LS byte stream: // SOF55, LSE, SOI, EOI, SOS, DNL, DRI, RSTm, APPn, COM. // All other markers shall not be present. switch (marker) { case JpegMarkerCode::StartOfFrameJpegLS: return ReadStartOfFrame(); case JpegMarkerCode::Comment: return ReadComment(); case JpegMarkerCode::JpegLSPresetParameters: return ReadPresetParameters(); case JpegMarkerCode::ApplicationData0: return 0; case JpegMarkerCode::ApplicationData7: return ReadColorSpace(); case JpegMarkerCode::ApplicationData8: return ReadColorXForm(); case JpegMarkerCode::StartOfFrameBaselineJpeg: case JpegMarkerCode::StartOfFrameExtendedSequential: case JpegMarkerCode::StartOfFrameProgressive: case JpegMarkerCode::StartOfFrameLossless: case JpegMarkerCode::StartOfFrameDifferentialSequential: case JpegMarkerCode::StartOfFrameDifferentialProgressive: case JpegMarkerCode::StartOfFrameDifferentialLossless: case JpegMarkerCode::StartOfFrameExtendedArithemtic: case JpegMarkerCode::StartOfFrameProgressiveArithemtic: case JpegMarkerCode::StartOfFrameLosslessArithemtic: { std::ostringstream message; message << "JPEG encoding with marker " << static_cast(marker) << " is not supported."; throw charls_error(ApiResult::UnsupportedEncoding, message.str()); } // Other tags not supported (among which DNL DRI) default: { std::ostringstream message; message << "Unknown JPEG marker " << static_cast(marker) << " encountered."; throw charls_error(ApiResult::UnknownJpegMarker, message.str()); } } } int JpegStreamReader::ReadPresetParameters() { const int type = ReadByte(); switch (type) { case 1: { _params.custom.MaximumSampleValue = ReadWord(); _params.custom.Threshold1 = ReadWord(); _params.custom.Threshold2 = ReadWord(); _params.custom.Threshold3 = ReadWord(); _params.custom.ResetValue = ReadWord(); return 11; } case 2: // mapping table specification case 3: // mapping table continuation case 4: // X and Y parameters greater than 16 bits are defined. { std::ostringstream message; message << "JPEG-LS preset parameters with type " << static_cast(type) << " are not supported."; throw charls_error(ApiResult::UnsupportedEncoding, message.str()); } default: { std::ostringstream message; message << "JPEG-LS preset parameters with invalid type " << static_cast(type) << " encountered."; throw charls_error(ApiResult::InvalidJlsParameters, message.str()); } } } void JpegStreamReader::ReadStartOfScan(bool firstComponent) { if (!firstComponent) { if (ReadByte() != 0xFF) throw charls_error(ApiResult::MissingJpegMarkerStart); if (static_cast(ReadByte()) != JpegMarkerCode::StartOfScan) throw charls_error(ApiResult::InvalidCompressedData);// TODO: throw more specific error code. } int length = ReadByte(); length = length * 256 + ReadByte(); // TODO: do something with 'length' or remove it. const int componentCount = ReadByte(); if (componentCount != 1 && componentCount != _params.components) throw charls_error(ApiResult::ParameterValueNotSupported); for (int i = 0; i < componentCount; ++i) { ReadByte(); ReadByte(); } _params.allowedLossyError = ReadByte(); _params.interleaveMode = static_cast(ReadByte()); if (!(_params.interleaveMode == InterleaveMode::None || _params.interleaveMode == InterleaveMode::Line || _params.interleaveMode == InterleaveMode::Sample)) throw charls_error(ApiResult::InvalidCompressedData);// TODO: throw more specific error code. if (ReadByte() != 0) throw charls_error(ApiResult::InvalidCompressedData);// TODO: throw more specific error code. if(_params.stride == 0) { const int width = _rect.Width != 0 ? _rect.Width : _params.width; const int components = _params.interleaveMode == InterleaveMode::None ? 1 : _params.components; _params.stride = components * width * ((_params.bitsPerSample + 7) / 8); } } int JpegStreamReader::ReadComment() noexcept { return 0; } void JpegStreamReader::ReadJfif() { for(int i = 0; i < static_cast(sizeof(jfifID)); i++) { if(jfifID[i] != ReadByte()) return; } _params.jfif.version = ReadWord(); // DPI or DPcm _params.jfif.units = ReadByte(); _params.jfif.Xdensity = ReadWord(); _params.jfif.Ydensity = ReadWord(); // thumbnail _params.jfif.Xthumbnail = ReadByte(); _params.jfif.Ythumbnail = ReadByte(); if(_params.jfif.Xthumbnail > 0 && _params.jfif.thumbnail) { std::vector tempbuff(static_cast(_params.jfif.thumbnail), static_cast(_params.jfif.thumbnail) + static_cast(3) * _params.jfif.Xthumbnail * _params.jfif.Ythumbnail); ReadNBytes(tempbuff, 3*_params.jfif.Xthumbnail*_params.jfif.Ythumbnail); } } int JpegStreamReader::ReadStartOfFrame() { _params.bitsPerSample = ReadByte(); _params.height = ReadWord(); _params.width = ReadWord(); _params.components= ReadByte(); return 6; } uint8_t JpegStreamReader::ReadByte() { if (_byteStream.rawStream) return static_cast(_byteStream.rawStream->sbumpc()); if (_byteStream.count == 0) throw charls_error(ApiResult::CompressedBufferTooSmall); const uint8_t value = _byteStream.rawData[0]; SkipBytes(_byteStream, 1); return value; } int JpegStreamReader::ReadWord() { const int i = ReadByte() * 256; return i + ReadByte(); } int JpegStreamReader::ReadColorSpace() const noexcept { return 0; } int JpegStreamReader::ReadColorXForm() { std::vector sourceTag; ReadNBytes(sourceTag, 4); if (strncmp(sourceTag.data(), "mrfx", 4) != 0) return 4; const auto xform = ReadByte(); switch (xform) { case static_cast(ColorTransformation::None): case static_cast(ColorTransformation::HP1): case static_cast(ColorTransformation::HP2): case static_cast(ColorTransformation::HP3): _params.colorTransformation = static_cast(xform); return 5; case 4: // RgbAsYuvLossy (The standard lossy RGB to YCbCr transform used in JPEG.) case 5: // Matrix (transformation is controlled using a matrix that is also stored in the segment. throw charls_error(ApiResult::ImageTypeNotSupported); default: throw charls_error(ApiResult::InvalidCompressedData); } } dcm2niix-1.0.20181125/console/charls/jpegstreamreader.h000066400000000000000000000032001337661136700224140ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGSTREAMREADER #define CHARLS_JPEGSTREAMREADER #include "publictypes.h" #include #include enum class JpegMarkerCode : uint8_t; struct JlsParameters; class JpegCustomParameters; JpegLSPresetCodingParameters ComputeDefault(int32_t maximumSampleValue, int32_t allowedLossyError) noexcept; // // JpegStreamReader: minimal implementation to read a JPEG byte stream. // class JpegStreamReader { public: explicit JpegStreamReader(ByteStreamInfo byteStreamInfo) noexcept; const JlsParameters& GetMetadata() const noexcept { return _params; } const JpegLSPresetCodingParameters& GetCustomPreset() const noexcept { return _params.custom; } void Read(ByteStreamInfo rawPixels); void ReadHeader(); void SetInfo(const JlsParameters& params) noexcept { _params = params; } void SetRect(const JlsRect& rect) noexcept { _rect = rect; } void ReadStartOfScan(bool firstComponent); uint8_t ReadByte(); private: JpegMarkerCode ReadNextMarker(); int ReadPresetParameters(); static int ReadComment() noexcept; int ReadStartOfFrame(); int ReadWord(); void ReadNBytes(std::vector& dst, int byteCount); int ReadMarker(JpegMarkerCode marker); void ReadJfif(); // Color Transform Application Markers & Code Stream (HP extension) int ReadColorSpace() const noexcept; int ReadColorXForm(); ByteStreamInfo _byteStream; JlsParameters _params; JlsRect _rect; }; #endif dcm2niix-1.0.20181125/console/charls/jpegstreamwriter.cpp000066400000000000000000000042741337661136700230350ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "jpegstreamwriter.h" #include "jpegimagedatasegment.h" #include "jpegmarkercode.h" #include "jpegmarkersegment.h" #include "jpegstreamreader.h" #include "util.h" #include using namespace charls; namespace { bool IsDefault(const JpegLSPresetCodingParameters& custom) noexcept { if (custom.MaximumSampleValue != 0) return false; if (custom.Threshold1 != 0) return false; if (custom.Threshold2 != 0) return false; if (custom.Threshold3 != 0) return false; if (custom.ResetValue != 0) return false; return true; } } JpegStreamWriter::JpegStreamWriter() noexcept : _data(), _byteOffset(0), _lastCompenentIndex(0) { } void JpegStreamWriter::AddColorTransform(ColorTransformation transformation) { AddSegment(JpegMarkerSegment::CreateColorTransformSegment(transformation)); } size_t JpegStreamWriter::Write(const ByteStreamInfo& info) { _data = info; WriteMarker(JpegMarkerCode::StartOfImage); for (size_t i = 0; i < _segments.size(); ++i) { _segments[i]->Serialize(*this); } WriteMarker(JpegMarkerCode::EndOfImage); return _byteOffset; } void JpegStreamWriter::AddScan(const ByteStreamInfo& info, const JlsParameters& params) { if (!IsDefault(params.custom)) { AddSegment(JpegMarkerSegment::CreateJpegLSPresetParametersSegment(params.custom)); } else if (params.bitsPerSample > 12) { const JpegLSPresetCodingParameters preset = ComputeDefault((1 << params.bitsPerSample) - 1, params.allowedLossyError); AddSegment(JpegMarkerSegment::CreateJpegLSPresetParametersSegment(preset)); } // Note: it is a common practice to start to count components by index 1. _lastCompenentIndex += 1; const int componentCount = params.interleaveMode == InterleaveMode::None ? 1 : params.components; AddSegment(JpegMarkerSegment::CreateStartOfScanSegment(_lastCompenentIndex, componentCount, params.allowedLossyError, params.interleaveMode)); AddSegment(std::make_unique(info, params, componentCount)); } dcm2niix-1.0.20181125/console/charls/jpegstreamwriter.h000066400000000000000000000045621337661136700225020ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGSTREAMWRITER #define CHARLS_JPEGSTREAMWRITER #include "util.h" #include "jpegsegment.h" #include #include enum class JpegMarkerCode : uint8_t; // // Purpose: 'Writer' class that can generate JPEG-LS file streams. // class JpegStreamWriter { friend class JpegMarkerSegment; friend class JpegImageDataSegment; public: JpegStreamWriter() noexcept; void AddSegment(std::unique_ptr segment) { _segments.push_back(std::move(segment)); } void AddScan(const ByteStreamInfo& info, const JlsParameters& params); void AddColorTransform(charls::ColorTransformation transformation); std::size_t GetBytesWritten() const noexcept { return _byteOffset; } std::size_t GetLength() const noexcept { return _data.count - _byteOffset; } std::size_t Write(const ByteStreamInfo& info); private: uint8_t* GetPos() const noexcept { return _data.rawData + _byteOffset; } ByteStreamInfo OutputStream() const noexcept { ByteStreamInfo data = _data; data.count -= _byteOffset; data.rawData += _byteOffset; return data; } void WriteByte(uint8_t val) { if (_data.rawStream) { _data.rawStream->sputc(val); } else { if (_byteOffset >= _data.count) throw charls_error(charls::ApiResult::CompressedBufferTooSmall); _data.rawData[_byteOffset++] = val; } } void WriteBytes(const std::vector& bytes) { for (std::size_t i = 0; i < bytes.size(); ++i) { WriteByte(bytes[i]); } } void WriteWord(uint16_t value) { WriteByte(static_cast(value / 0x100)); WriteByte(static_cast(value % 0x100)); } void WriteMarker(JpegMarkerCode marker) { WriteByte(0xFF); WriteByte(static_cast(marker)); } void Seek(std::size_t byteCount) noexcept { if (_data.rawStream) return; _byteOffset += byteCount; } ByteStreamInfo _data; std::size_t _byteOffset; int32_t _lastCompenentIndex; std::vector> _segments; }; #endif dcm2niix-1.0.20181125/console/charls/lookuptable.h000066400000000000000000000027011337661136700214160ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_LOOKUPTABLE #define CHARLS_LOOKUPTABLE #include // Tables for fast decoding of short Golomb Codes. struct Code { Code() noexcept : _value(), _length() { } Code(int32_t value, int32_t length) noexcept : _value(value), _length(length) { } int32_t GetValue() const noexcept { return _value; } int32_t GetLength() const noexcept { return _length; } int32_t _value; int32_t _length; }; class CTable { public: static constexpr size_t byte_bit_count = 8; CTable() noexcept { std::memset(_rgtype, 0, sizeof(_rgtype)); } void AddEntry(uint8_t bvalue, Code c) noexcept { const int32_t length = c.GetLength(); ASSERT(static_cast(length) <= byte_bit_count); for (int32_t i = 0; i < static_cast(1) << (byte_bit_count - length); ++i) { ASSERT(_rgtype[(bvalue << (byte_bit_count - length)) + i].GetLength() == 0); _rgtype[(bvalue << (byte_bit_count - length)) + i] = c; } } FORCE_INLINE const Code& Get(int32_t value) const noexcept { return _rgtype[value]; } private: Code _rgtype[1 << byte_bit_count]; }; #endif dcm2niix-1.0.20181125/console/charls/losslesstraits.h000066400000000000000000000070731337661136700222020ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_LOSSLESSTRAITS #define CHARLS_LOSSLESSTRAITS #include "constants.h" // Optimized trait classes for lossless compression of 8 bit color and 8/16 bit monochrome images. // This class assumes MaximumSampleValue correspond to a whole number of bits, and no custom ResetValue is set when encoding. // The point of this is to have the most optimized code for the most common and most demanding scenario. template struct LosslessTraitsImpl { using SAMPLE = sample; enum { NEAR = 0, bpp = bitsperpixel, qbpp = bitsperpixel, RANGE = (1 << bpp), MAXVAL= (1 << bpp) - 1, LIMIT = 2 * (bitsperpixel + std::max(8, bitsperpixel)), RESET = DefaultResetValue }; static FORCE_INLINE int32_t ComputeErrVal(int32_t d) noexcept { return ModuloRange(d); } static FORCE_INLINE bool IsNear(int32_t lhs, int32_t rhs) noexcept { return lhs == rhs; } // The following optimization is implementation-dependent (works on x86 and ARM, see charlstest). #if defined(__clang__) __attribute__((no_sanitize("shift"))) #endif static FORCE_INLINE int32_t ModuloRange(int32_t errorValue) noexcept { return static_cast(errorValue << (int32_t_bit_count - bpp)) >> (int32_t_bit_count - bpp); } static FORCE_INLINE SAMPLE ComputeReconstructedSample(int32_t Px, int32_t ErrVal) noexcept { return static_cast(MAXVAL & (Px + ErrVal)); } static FORCE_INLINE int32_t CorrectPrediction(int32_t Pxc) noexcept { if ((Pxc & MAXVAL) == Pxc) return Pxc; return (~(Pxc >> (int32_t_bit_count-1))) & MAXVAL; } }; template struct LosslessTraits : LosslessTraitsImpl { using PIXEL = T; }; template<> struct LosslessTraits : LosslessTraitsImpl { using PIXEL = SAMPLE; static FORCE_INLINE signed char ModRange(int32_t Errval) noexcept { return static_cast(Errval); } static FORCE_INLINE int32_t ComputeErrVal(int32_t d) noexcept { return static_cast(d); } static FORCE_INLINE uint8_t ComputeReconstructedSample(int32_t Px, int32_t ErrVal) noexcept { return static_cast(Px + ErrVal); } }; template<> struct LosslessTraits : LosslessTraitsImpl { using PIXEL = SAMPLE; static FORCE_INLINE short ModRange(int32_t Errval) noexcept { return static_cast(Errval); } static FORCE_INLINE int32_t ComputeErrVal(int32_t d) noexcept { return static_cast(d); } static FORCE_INLINE SAMPLE ComputeReconstructedSample(int32_t Px, int32_t ErrVal) noexcept { return static_cast(Px + ErrVal); } }; template struct LosslessTraits, bpp> : LosslessTraitsImpl { using PIXEL = Triplet; static FORCE_INLINE bool IsNear(int32_t lhs, int32_t rhs) noexcept { return lhs == rhs; } static FORCE_INLINE bool IsNear(PIXEL lhs, PIXEL rhs) noexcept { return lhs == rhs; } static FORCE_INLINE T ComputeReconstructedSample(int32_t Px, int32_t ErrVal) noexcept { return static_cast(Px + ErrVal); } }; #endif dcm2niix-1.0.20181125/console/charls/processline.h000066400000000000000000000303171337661136700214270ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_PROCESSLINE #define CHARLS_PROCESSLINE #include "util.h" #include "publictypes.h" #include #include #include #include // // This file defines the ProcessLine base class, its derivatives and helper functions. // During coding/decoding, CharLS process one line at a time. The different Processline implementations // convert the uncompressed format to and from the internal format for encoding. // Conversions include color transforms, line interleaved vs sample interleaved, masking out unused bits, // accounting for line padding etc. // This mechanism could be used to encode/decode images as they are received. // class ProcessLine { public: virtual ~ProcessLine() = default; ProcessLine(const ProcessLine&) = delete; ProcessLine(ProcessLine&&) = delete; ProcessLine& operator=(const ProcessLine&) = delete; ProcessLine& operator=(ProcessLine&&) = delete; virtual void NewLineDecoded(const void* pSrc, int pixelCount, int sourceStride) = 0; virtual void NewLineRequested(void* pDest, int pixelCount, int destStride) = 0; protected: ProcessLine() = default; }; class PostProcesSingleComponent : public ProcessLine { public: PostProcesSingleComponent(void* rawData, const JlsParameters& params, size_t bytesPerPixel) noexcept : _rawData(static_cast(rawData)), _bytesPerPixel(bytesPerPixel), _bytesPerLine(params.stride) { } WARNING_SUPPRESS(26440) void NewLineRequested(void* dest, int pixelCount, int /*byteStride*/) override { std::memcpy(dest, _rawData, pixelCount * _bytesPerPixel); _rawData += _bytesPerLine; } void NewLineDecoded(const void* pSrc, int pixelCount, int /*sourceStride*/) override { std::memcpy(_rawData, pSrc, pixelCount * _bytesPerPixel); _rawData += _bytesPerLine; } WARNING_UNSUPPRESS() private: uint8_t* _rawData; size_t _bytesPerPixel; size_t _bytesPerLine; }; inline void ByteSwap(unsigned char* data, int count) { if (static_cast(count) & 1u) { std::ostringstream message; message << "An odd number of bytes (" << count << ") cannot be swapped."; throw charls_error(charls::ApiResult::InvalidJlsParameters, message.str()); } const auto data32 = reinterpret_cast(data); for(auto i = 0; i < count / 4; i++) { const auto value = data32[i]; data32[i] = ((value >> 8u) & 0x00FF00FFu) | ((value & 0x00FF00FFu) << 8u); } if ((count % 4) != 0) { std::swap(data[count-2], data[count-1]); } } class PostProcesSingleStream : public ProcessLine { public: PostProcesSingleStream(std::basic_streambuf* rawData, const JlsParameters& params, size_t bytesPerPixel) noexcept : _rawData(rawData), _bytesPerPixel(bytesPerPixel), _bytesPerLine(params.stride) { } void NewLineRequested(void* dest, int pixelCount, int /*destStride*/) override { auto bytesToRead = pixelCount * _bytesPerPixel; while (bytesToRead != 0) { const auto bytesRead = _rawData->sgetn(static_cast(dest), bytesToRead); if (bytesRead == 0) throw charls_error(charls::ApiResult::UncompressedBufferTooSmall); bytesToRead = static_cast(bytesToRead - bytesRead); } if (_bytesPerPixel == 2) { ByteSwap(static_cast(dest), 2 * pixelCount); } if (_bytesPerLine - pixelCount * _bytesPerPixel > 0) { _rawData->pubseekoff(static_cast(_bytesPerLine - bytesToRead), std::ios_base::cur); } } void NewLineDecoded(const void* pSrc, int pixelCount, int /*sourceStride*/) override { const auto bytesToWrite = pixelCount * _bytesPerPixel; const auto bytesWritten = static_cast(_rawData->sputn(static_cast(pSrc), bytesToWrite)); if (bytesWritten != bytesToWrite) throw charls_error(charls::ApiResult::UncompressedBufferTooSmall); } private: std::basic_streambuf* _rawData; size_t _bytesPerPixel; size_t _bytesPerLine; }; template void TransformLineToQuad(const T* ptypeInput, int32_t pixelStrideIn, Quad* pbyteBuffer, int32_t pixelStride, TRANSFORM& transform) noexcept { const int cpixel = std::min(pixelStride, pixelStrideIn); Quad* ptypeBuffer = pbyteBuffer; for (auto x = 0; x < cpixel; ++x) { const Quad pixel(transform(ptypeInput[x], ptypeInput[x + pixelStrideIn], ptypeInput[x + 2*pixelStrideIn]), ptypeInput[x + 3 * pixelStrideIn]); ptypeBuffer[x] = pixel; } } template void TransformQuadToLine(const Quad* pbyteInput, int32_t pixelStrideIn, T* ptypeBuffer, int32_t pixelStride, TRANSFORM& transform) noexcept { const auto cpixel = std::min(pixelStride, pixelStrideIn); const Quad* ptypeBufferIn = pbyteInput; for (auto x = 0; x < cpixel; ++x) { const Quad color = ptypeBufferIn[x]; const Quad colorTranformed(transform(color.v1, color.v2, color.v3), color.v4); ptypeBuffer[x] = colorTranformed.v1; ptypeBuffer[x + pixelStride] = colorTranformed.v2; ptypeBuffer[x + 2 * pixelStride] = colorTranformed.v3; ptypeBuffer[x + 3 * pixelStride] = colorTranformed.v4; } } template void TransformRgbToBgr(T* pDest, int samplesPerPixel, int pixelCount) noexcept { for (auto i = 0; i < pixelCount; ++i) { std::swap(pDest[0], pDest[2]); pDest += samplesPerPixel; } } template void TransformLine(Triplet* pDest, const Triplet* pSrc, int pixelCount, TRANSFORM& transform) noexcept { for (auto i = 0; i < pixelCount; ++i) { pDest[i] = transform(pSrc[i].v1, pSrc[i].v2, pSrc[i].v3); } } template void TransformLineToTriplet(const T* ptypeInput, int32_t pixelStrideIn, Triplet* pbyteBuffer, int32_t pixelStride, TRANSFORM& transform) noexcept { const auto cpixel = std::min(pixelStride, pixelStrideIn); Triplet* ptypeBuffer = pbyteBuffer; for (auto x = 0; x < cpixel; ++x) { ptypeBuffer[x] = transform(ptypeInput[x], ptypeInput[x + pixelStrideIn], ptypeInput[x + 2*pixelStrideIn]); } } template void TransformTripletToLine(const Triplet* pbyteInput, int32_t pixelStrideIn, T* ptypeBuffer, int32_t pixelStride, TRANSFORM& transform) noexcept { const auto cpixel = std::min(pixelStride, pixelStrideIn); const Triplet* ptypeBufferIn = pbyteInput; for (auto x = 0; x < cpixel; ++x) { const Triplet color = ptypeBufferIn[x]; const Triplet colorTranformed = transform(color.v1, color.v2, color.v3); ptypeBuffer[x] = colorTranformed.v1; ptypeBuffer[x + pixelStride] = colorTranformed.v2; ptypeBuffer[x + 2 *pixelStride] = colorTranformed.v3; } } template class ProcessTransformed : public ProcessLine { public: ProcessTransformed(ByteStreamInfo rawStream, const JlsParameters& info, TRANSFORM transform) : _params(info), _templine(static_cast(info.width) * info.components), _buffer(static_cast(info.width) * info.components * sizeof(size_type)), _transform(transform), _inverseTransform(transform), _rawPixels(rawStream) { } void NewLineRequested(void* dest, int pixelCount, int destStride) override { if (!_rawPixels.rawStream) { Transform(_rawPixels.rawData, dest, pixelCount, destStride); _rawPixels.rawData += _params.stride; return; } Transform(_rawPixels.rawStream, dest, pixelCount, destStride); } void Transform(std::basic_streambuf* rawStream, void* dest, int pixelCount, int destStride) { std::streamsize bytesToRead = static_cast(pixelCount) * _params.components * sizeof(size_type); while (bytesToRead != 0) { const auto read = rawStream->sgetn(reinterpret_cast(_buffer.data()), bytesToRead); if (read == 0) { std::ostringstream message; message << "No more bytes available in input buffer, still needing " << read; throw charls_error(charls::ApiResult::UncompressedBufferTooSmall, message.str()); } bytesToRead -= read; } Transform(_buffer.data(), dest, pixelCount, destStride); } void Transform(const void* source, void* dest, int pixelCount, int destStride) noexcept { if (_params.outputBgr) { memcpy(_templine.data(), source, sizeof(Triplet) * pixelCount); TransformRgbToBgr(_templine.data(), _params.components, pixelCount); source = _templine.data(); } if (_params.components == 3) { if (_params.interleaveMode == charls::InterleaveMode::Sample) { TransformLine(static_cast*>(dest), static_cast*>(source), pixelCount, _transform); } else { TransformTripletToLine(static_cast*>(source), pixelCount, static_cast(dest), destStride, _transform); } } else if (_params.components == 4 && _params.interleaveMode == charls::InterleaveMode::Line) { TransformQuadToLine(static_cast*>(source), pixelCount, static_cast(dest), destStride, _transform); } } void DecodeTransform(const void* pSrc, void* rawData, int pixelCount, int byteStride) noexcept { if (_params.components == 3) { if (_params.interleaveMode == charls::InterleaveMode::Sample) { TransformLine(static_cast*>(rawData), static_cast*>(pSrc), pixelCount, _inverseTransform); } else { TransformLineToTriplet(static_cast(pSrc), byteStride, static_cast*>(rawData), pixelCount, _inverseTransform); } } else if (_params.components == 4 && _params.interleaveMode == charls::InterleaveMode::Line) { TransformLineToQuad(static_cast(pSrc), byteStride, static_cast*>(rawData), pixelCount, _inverseTransform); } if (_params.outputBgr) { TransformRgbToBgr(static_cast(rawData), _params.components, pixelCount); } } void NewLineDecoded(const void* pSrc, int pixelCount, int sourceStride) override { if (_rawPixels.rawStream) { const std::streamsize bytesToWrite = static_cast(pixelCount) * _params.components * sizeof(size_type); DecodeTransform(pSrc, _buffer.data(), pixelCount, sourceStride); const auto bytesWritten = _rawPixels.rawStream->sputn(reinterpret_cast(_buffer.data()), bytesToWrite); if (bytesWritten != bytesToWrite) throw charls_error(charls::ApiResult::UncompressedBufferTooSmall); } else { DecodeTransform(pSrc, _rawPixels.rawData, pixelCount, sourceStride); _rawPixels.rawData += _params.stride; } } private: using size_type = typename TRANSFORM::size_type; const JlsParameters& _params; std::vector _templine; std::vector _buffer; TRANSFORM _transform; typename TRANSFORM::Inverse _inverseTransform; ByteStreamInfo _rawPixels; }; #endif dcm2niix-1.0.20181125/console/charls/publictypes.h000066400000000000000000000317571337661136700214550ustar00rootroot00000000000000/* (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. */ #ifndef CHARLS_PUBLICTYPES #define CHARLS_PUBLICTYPES #ifdef __cplusplus #include #include namespace charls { /// /// Defines the result values that are returned by the CharLS API functions. /// enum class ApiResult { OK = 0, // The operation completed without errors. InvalidJlsParameters = 1, // One of the JLS parameters is invalid. ParameterValueNotSupported = 2, // The parameter value not supported. UncompressedBufferTooSmall = 3, // The uncompressed buffer is too small to hold all the output. CompressedBufferTooSmall = 4, // The compressed buffer too small, more input data was expected. InvalidCompressedData = 5, // This error is returned when the encoded bit stream contains a general structural problem. TooMuchCompressedData = 6, // Too much compressed data.The decoding process is ready but the input buffer still contains encoded data. ImageTypeNotSupported = 7, // This error is returned when the bit stream is encoded with an option that is not supported by this implementation. UnsupportedBitDepthForTransform = 8, // The bit depth for transformation is not supported. UnsupportedColorTransform = 9, // The color transformation is not supported. UnsupportedEncoding = 10, // This error is returned when an encoded frame is found that is not encoded with the JPEG-LS algorithm. UnknownJpegMarker = 11, // This error is returned when an unknown JPEG marker code is detected in the encoded bit stream. MissingJpegMarkerStart = 12, // This error is returned when the algorithm expect a 0xFF code (indicates start of a JPEG marker) but none was found. UnspecifiedFailure = 13, // This error is returned when the implementation detected a failure, but no specific error is available. UnexpectedFailure = 14 // This error is returned when the implementation encountered a failure it didn't expect. No guarantees can be given for the state after this error. }; /// /// Defines the interleave mode for multi-component (color) pixel data. /// enum class InterleaveMode { /// /// The data is encoded and stored as component for component: RRRGGGBBB. /// None = 0, /// /// The interleave mode is by line. A full line of each component is encoded before moving to the next line. /// Line = 1, /// /// The data is encoded and stored by sample. For color images this is the format like RGBRGBRGB. /// Sample = 2 }; /// /// Defines color space transformations as defined and implemented by the JPEG-LS library of HP Labs. /// These color space transformation decrease the correlation between the 3 color components, resulting in better encoding ratio. /// These options are only implemented for backwards compatibility and NOT part of the JPEG-LS standard. /// The JPEG-LS ISO/IEC 14495-1:1999 standard provides no capabilities to transport which color space transformation was used. /// enum class ColorTransformation { /// /// No color space transformation has been applied. /// None = 0, /// /// Defines the reversible lossless color transformation: /// G = G /// R = R - G /// B = B - G /// HP1 = 1, /// /// Defines the reversible lossless color transformation: /// G = G /// B = B - (R + G) / 2 /// R = R - G /// HP2 = 2, /// /// Defines the reversible lossless color transformation of Y-Cb-Cr): /// R = R - G /// B = B - G /// G = G + (R + B) / 4 /// HP3 = 3, }; } using CharlsApiResultType = charls::ApiResult; using CharlsInterleaveModeType = charls::InterleaveMode; using CharlsColorTransformationType = charls::ColorTransformation; // Defines the size of the char buffer that should be passed to the CharLS API to get the error message text. const std::size_t ErrorMessageSize = 256; #else #include enum CharlsApiResult { CHARLS_API_RESULT_OK = 0, // The operation completed without errors. CHARLS_API_RESULT_INVALID_JLS_PARAMETERS = 1, // One of the JLS parameters is invalid. CHARLS_API_RESULT_PARAMETER_VALUE_NOT_SUPPORTED = 2, // The parameter value not supported. CHARLS_API_RESULT_UNCOMPRESSED_BUFFER_TOO_SMALL = 3, // The uncompressed buffer is too small to hold all the output. CHARLS_API_RESULT_COMPRESSED_BUFFER_TOO_SMALL = 4, // The compressed buffer too small, more input data was expected. CHARLS_API_RESULT_INVALID_COMPRESSED_DATA = 5, // This error is returned when the encoded bit stream contains a general structural problem. CHARLS_API_RESULT_TOO_MUCH_COMPRESSED_DATA = 6, // Too much compressed data.The decoding process is ready but the input buffer still contains encoded data. CHARLS_API_RESULT_IMAGE_TYPE_NOT_SUPPORTED = 7, // This error is returned when the bit stream is encoded with an option that is not supported by this implementation. CHARLS_API_RESULT_UNSUPPORTED_BIT_DEPTH_FOR_TRANSFORM = 8, // The bit depth for transformation is not supported. CHARLS_API_RESULT_UNSUPPORTED_COLOR_TRANSFORM = 9, // The color transformation is not supported. CHARLS_API_RESULT_UNSUPPORTED_ENCODING = 10, // This error is returned when an encoded frame is found that is not encoded with the JPEG-LS algorithm. CHARLS_API_RESULT_UNKNOWN_JPEG_MARKER = 11, // This error is returned when an unknown JPEG marker code is detected in the encoded bit stream. CHARLS_API_RESULT_MISSING_JPEG_MARKER_START = 12, // This error is returned when the algorithm expect a 0xFF code (indicates start of a JPEG marker) but none was found. CHARLS_API_RESULT_UNSPECIFIED_FAILURE = 13, // This error is returned when the implementation detected a failure, but no specific error is available. CHARLS_API_RESULT_UNEXPECTED_FAILURE = 14, // This error is returned when the implementation encountered a failure it didn't expect. No guarantees can be given for the state after this error. }; enum CharlsInterleaveMode { CHARLS_IM_NONE = 0, CHARLS_IM_LINE = 1, CHARLS_IM_SAMPLE = 2 }; enum CharlsColorTransformation { CHARLS_COLOR_TRANSFORMATION_NONE = 0, CHARLS_COLOR_TRANSFORMATION_HP1 = 1, CHARLS_COLOR_TRANSFORMATION_HP2 = 2, CHARLS_COLOR_TRANSFORMATION_HP3 = 3, }; typedef enum CharlsApiResult CharlsApiResultType; typedef enum CharlsInterleaveMode CharlsInterleaveModeType; typedef enum CharlsColorTransformation CharlsColorTransformationType; // Defines the size of the char buffer that should be passed to the CharLS API to get the error message text. #define CHARLS_ERROR_MESSAGE_SIZE 256 #endif /// /// Defines the JPEG-LS preset coding parameters as defined in ISO/IEC 14495-1, C.2.4.1.1. /// JPEG-LS defines a default set of parameters, but custom parameters can be used. /// When used these parameters are written into the encoded bit stream as they are needed for the decoding process. /// struct JpegLSPresetCodingParameters { /// /// Maximum possible value for any image sample in a scan. /// This must be greater than or equal to the actual maximum value for the components in a scan. /// int MaximumSampleValue; /// /// First quantization threshold value for the local gradients. /// int Threshold1; /// /// Second quantization threshold value for the local gradients. /// int Threshold2; /// /// Third quantization threshold value for the local gradients. /// int Threshold3; /// /// Value at which the counters A, B, and N are halved. /// int ResetValue; }; struct JlsRect { int X; int Y; int Width; int Height; }; /// /// Defines the parameters for the JPEG File Interchange Format. /// The format is defined in the JPEG File Interchange Format v1.02 document by Eric Hamilton. /// /// /// The JPEG File Interchange Format is the de-facto standard JPEG interchange format. /// struct JfifParameters { /// /// Version of the JPEG File Interchange Format. /// Should be set to zero to not write a JFIF header or to 1.02, encoded as: (1 * 256) + 2. /// int32_t version; /// /// Defines the units for the X and Y densities. /// 0: no units, X and Y specify the pixel aspect ratio. /// 1: X and Y are dots per inch. /// 2: X and Y are dots per cm. /// int32_t units; /// /// Horizontal pixel density /// int32_t Xdensity; /// /// Vertical pixel density /// int32_t Ydensity; /// /// Thumbnail horizontal pixel count. /// int32_t Xthumbnail; /// /// Thumbnail vertical pixel count. /// int32_t Ythumbnail; /// /// Reference to a buffer with thumbnail pixels of size Xthumbnail * Ythumbnail * 3(RGB). /// This parameter is only used when creating JPEG-LS encoded images. /// void* thumbnail; }; struct JlsParameters { /// /// Width of the image in pixels. /// int width; /// /// Height of the image in pixels. /// int height; /// /// The number of valid bits per sample to encode. /// Valid range 2 - 16. When greater than 8, pixels are assumed to stored as two bytes per sample, otherwise one byte per sample is assumed. /// int bitsPerSample; /// /// The stride is the number of bytes from one row of pixels in memory to the next row of pixels in memory. /// Stride is sometimes called pitch. If padding bytes are present, the stride is wider than the width of the image. /// int stride; /// /// The number of components. /// Typical 1 for monochrome images and 3 for color images or 4 if alpha channel is present. /// int components; /// /// Defines the allowed lossy error. Value 0 defines lossless. /// int allowedLossyError; /// /// Determines the order of the color components in the compressed stream. /// CharlsInterleaveModeType interleaveMode; /// /// Color transformation used in the compressed stream. The color transformations are all lossless and /// are an HP proprietary extension of the standard. Do not use the color transformations unless /// you know the decoder is capable of decoding it. Color transform typically improve compression ratios only /// for synthetic images (non - photo-realistic computer generated images). /// CharlsColorTransformationType colorTransformation; /// /// If set to true RGB images will be decoded to BGR. BGR is the standard ordering in MS Windows bitmaps. /// char outputBgr; struct JpegLSPresetCodingParameters custom; struct JfifParameters jfif; }; #ifdef __cplusplus #include // // ByteStreamInfo & FromByteArray helper function // // ByteStreamInfo describes the stream: either set rawStream to a valid stream, or rawData/count, not both. // it's possible to decode to memory streams, but using rawData will always be faster. // // Example use: // ByteStreamInfo streamInfo = { fileStream.rdbuf() }; // or // ByteStreamInfo streamInfo = FromByteArray( bytePtr, byteCount); // struct ByteStreamInfo { std::basic_streambuf* rawStream; uint8_t* rawData; std::size_t count; }; inline ByteStreamInfo FromByteArray(void* bytes, std::size_t count) noexcept { return { nullptr, static_cast(bytes), count }; } inline ByteStreamInfo FromByteArrayConst(const void* bytes, std::size_t count) noexcept { return FromByteArray(const_cast(bytes), count); } #endif #endif dcm2niix-1.0.20181125/console/charls/scan.h000066400000000000000000000707501337661136700200320ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_SCAN #define CHARLS_SCAN #include "lookuptable.h" #include "contextrunmode.h" #include "context.h" #include "colortransform.h" #include "processline.h" #include // This file contains the code for handling a "scan". Usually an image is encoded as a single scan. #ifdef _MSC_VER #pragma warning (disable: 4127) // conditional expression is constant (caused by some template methods that are not fully specialized) [VS2017] #endif extern CTable decodingTables[16]; extern std::vector rgquant8Ll; extern std::vector rgquant10Ll; extern std::vector rgquant12Ll; extern std::vector rgquant16Ll; inline int32_t ApplySign(int32_t i, int32_t sign) noexcept { return (sign ^ i) - sign; } // Two alternatives for GetPredictedValue() (second is slightly faster due to reduced branching) #if 0 inline int32_t GetPredictedValue(int32_t Ra, int32_t Rb, int32_t Rc) { if (Ra < Rb) { if (Rc < Ra) return Rb; if (Rc > Rb) return Ra; } else { if (Rc < Rb) return Ra; if (Rc > Ra) return Rb; } return Ra + Rb - Rc; } #else inline int32_t GetPredictedValue(int32_t Ra, int32_t Rb, int32_t Rc) noexcept { // sign trick reduces the number of if statements (branches) const int32_t sgn = BitWiseSign(Rb - Ra); // is Ra between Rc and Rb? if ((sgn ^ (Rc - Ra)) < 0) { return Rb; } if ((sgn ^ (Rb - Rc)) < 0) { return Ra; } // default case, valid if Rc element of [Ra,Rb] return Ra + Rb - Rc; } #endif inline int32_t UnMapErrVal(int32_t mappedError) noexcept { const int32_t sign = static_cast(mappedError << (int32_t_bit_count-1)) >> (int32_t_bit_count-1); return sign ^ (mappedError >> 1); } inline int32_t GetMappedErrVal(int32_t Errval) noexcept { const int32_t mappedError = (Errval >> (int32_t_bit_count-2)) ^ (2 * Errval); return mappedError; } inline int32_t ComputeContextID(int32_t Q1, int32_t Q2, int32_t Q3) noexcept { return (Q1 * 9 + Q2) * 9 + Q3; } template class JlsCodec : public Strategy { public: using PIXEL = typename Traits::PIXEL; using SAMPLE = typename Traits::SAMPLE; WARNING_SUPPRESS(26495) // false warning that _contextRunmode is unintialized JlsCodec(const Traits& inTraits, const JlsParameters& params) : Strategy(params), traits(inTraits), _rect(), _width(params.width), T1(0), T2(0), T3(0), _RUNindex(0), _previousLine(), _currentLine(), _pquant(nullptr) { if (Info().interleaveMode == InterleaveMode::None) { Info().components = 1; } } WARNING_UNSUPPRESS() void SetPresets(const JpegLSPresetCodingParameters& presets) override { const JpegLSPresetCodingParameters presetDefault = ComputeDefault(traits.MAXVAL, traits.NEAR); InitParams(presets.Threshold1 != 0 ? presets.Threshold1 : presetDefault.Threshold1, presets.Threshold2 != 0 ? presets.Threshold2 : presetDefault.Threshold2, presets.Threshold3 != 0 ? presets.Threshold3 : presetDefault.Threshold3, presets.ResetValue != 0 ? presets.ResetValue : presetDefault.ResetValue); } std::unique_ptr CreateProcess(ByteStreamInfo info) override; bool IsInterleaved() noexcept { if (Info().interleaveMode == InterleaveMode::None) return false; if (Info().components == 1) return false; return true; } JlsParameters& Info() noexcept { return Strategy::_params; } signed char QuantizeGratientOrg(int32_t Di) const noexcept; FORCE_INLINE int32_t QuantizeGratient(int32_t Di) const noexcept { ASSERT(QuantizeGratientOrg(Di) == *(_pquant + Di)); return *(_pquant + Di); } void InitQuantizationLUT(); int32_t DecodeValue(int32_t k, int32_t limit, int32_t qbpp); FORCE_INLINE void EncodeMappedValue(int32_t k, int32_t mappedError, int32_t limit); void IncrementRunIndex() noexcept { _RUNindex = std::min(31, _RUNindex + 1); } void DecrementRunIndex() noexcept { _RUNindex = std::max(0, _RUNindex - 1); } int32_t DecodeRIError(CContextRunMode& ctx); Triplet DecodeRIPixel(Triplet Ra, Triplet Rb); SAMPLE DecodeRIPixel(int32_t Ra, int32_t Rb); int32_t DecodeRunPixels(PIXEL Ra, PIXEL* startPos, int32_t cpixelMac); int32_t DoRunMode(int32_t startIndex, DecoderStrategy*); void EncodeRIError(CContextRunMode& ctx, int32_t Errval); SAMPLE EncodeRIPixel(int32_t x, int32_t Ra, int32_t Rb); Triplet EncodeRIPixel(Triplet x, Triplet Ra, Triplet Rb); void EncodeRunPixels(int32_t runLength, bool endOfLine); int32_t DoRunMode(int32_t index, EncoderStrategy*); FORCE_INLINE SAMPLE DoRegular(int32_t Qs, int32_t, int32_t pred, DecoderStrategy*); FORCE_INLINE SAMPLE DoRegular(int32_t Qs, int32_t x, int32_t pred, EncoderStrategy*); void DoLine(SAMPLE* pdummy); void DoLine(Triplet* pdummy); void DoScan(); void InitParams(int32_t t1, int32_t t2, int32_t t3, int32_t nReset); #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winconsistent-missing-override" #endif // Note: depending on the base class EncodeScan OR DecodeScan will be virtual and abstract, cannot use override in all cases. size_t EncodeScan(std::unique_ptr processLine, ByteStreamInfo& compressedData); void DecodeScan(std::unique_ptr processLine, const JlsRect& rect, ByteStreamInfo& compressedData); #if defined(__clang__) #pragma clang diagnostic pop #endif protected: // codec parameters Traits traits; JlsRect _rect; int _width; int32_t T1; int32_t T2; int32_t T3; // compression context JlsContext _contexts[365]; CContextRunMode _contextRunmode[2]; int32_t _RUNindex; PIXEL* _previousLine; PIXEL* _currentLine; // quantization lookup table signed char* _pquant; std::vector _rgquant; }; // Encode/decode a single sample. Performance wise the #1 important functions template typename Traits::SAMPLE JlsCodec::DoRegular(int32_t Qs, int32_t, int32_t pred, DecoderStrategy*) { const int32_t sign = BitWiseSign(Qs); JlsContext& ctx = _contexts[ApplySign(Qs, sign)]; const int32_t k = ctx.GetGolomb(); const int32_t Px = traits.CorrectPrediction(pred + ApplySign(ctx.C, sign)); int32_t ErrVal; const Code& code = decodingTables[k].Get(Strategy::PeekByte()); if (code.GetLength() != 0) { Strategy::Skip(code.GetLength()); ErrVal = code.GetValue(); ASSERT(std::abs(ErrVal) < 65535); } else { ErrVal = UnMapErrVal(DecodeValue(k, traits.LIMIT, traits.qbpp)); if (std::abs(ErrVal) > 65535) throw charls_error(charls::ApiResult::InvalidCompressedData); } if (k == 0) { ErrVal = ErrVal ^ ctx.GetErrorCorrection(traits.NEAR); } ctx.UpdateVariables(ErrVal, traits.NEAR, traits.RESET); ErrVal = ApplySign(ErrVal, sign); return traits.ComputeReconstructedSample(Px, ErrVal); } template typename Traits::SAMPLE JlsCodec::DoRegular(int32_t Qs, int32_t x, int32_t pred, EncoderStrategy*) { const int32_t sign = BitWiseSign(Qs); JlsContext& ctx = _contexts[ApplySign(Qs, sign)]; const int32_t k = ctx.GetGolomb(); const int32_t Px = traits.CorrectPrediction(pred + ApplySign(ctx.C, sign)); const int32_t ErrVal = traits.ComputeErrVal(ApplySign(x - Px, sign)); EncodeMappedValue(k, GetMappedErrVal(ctx.GetErrorCorrection(k | traits.NEAR) ^ ErrVal), traits.LIMIT); ctx.UpdateVariables(ErrVal, traits.NEAR, traits.RESET); ASSERT(traits.IsNear(traits.ComputeReconstructedSample(Px, ApplySign(ErrVal, sign)), x)); return static_cast(traits.ComputeReconstructedSample(Px, ApplySign(ErrVal, sign))); } // Functions to build tables used to decode short Golomb codes. inline std::pair CreateEncodedValue(int32_t k, int32_t mappedError) noexcept { const int32_t highbits = mappedError >> k; return std::make_pair(highbits + k + 1, (static_cast(1) << k) | (mappedError & ((static_cast(1) << k) - 1))); } inline CTable InitTable(int32_t k) noexcept { CTable table; for (short nerr = 0; ; nerr++) { // Q is not used when k != 0 const int32_t merrval = GetMappedErrVal(nerr); const std::pair paircode = CreateEncodedValue(k, merrval); if (static_cast(paircode.first) > CTable::byte_bit_count) break; const Code code(nerr, static_cast(paircode.first)); table.AddEntry(static_cast(paircode.second), code); } for (short nerr = -1; ; nerr--) { // Q is not used when k != 0 const int32_t merrval = GetMappedErrVal(nerr); const std::pair paircode = CreateEncodedValue(k, merrval); if (static_cast(paircode.first) > CTable::byte_bit_count) break; const Code code = Code(nerr, static_cast(paircode.first)); table.AddEntry(static_cast(paircode.second), code); } return table; } // Encoding/decoding of Golomb codes template int32_t JlsCodec::DecodeValue(int32_t k, int32_t limit, int32_t qbpp) { const int32_t highbits = Strategy::ReadHighbits(); if (highbits >= limit - (qbpp + 1)) return Strategy::ReadValue(qbpp) + 1; if (k == 0) return highbits; return (highbits << k) + Strategy::ReadValue(k); } template FORCE_INLINE void JlsCodec::EncodeMappedValue(int32_t k, int32_t mappedError, int32_t limit) { int32_t highbits = mappedError >> k; if (highbits < limit - traits.qbpp - 1) { if (highbits + 1 > 31) { Strategy::AppendToBitStream(0, highbits / 2); highbits = highbits - highbits / 2; } Strategy::AppendToBitStream(1, highbits + 1); Strategy::AppendToBitStream((mappedError & ((1 << k) - 1)), k); return; } if (limit - traits.qbpp > 31) { Strategy::AppendToBitStream(0, 31); Strategy::AppendToBitStream(1, limit - traits.qbpp - 31); } else { Strategy::AppendToBitStream(1, limit - traits.qbpp); } Strategy::AppendToBitStream((mappedError - 1) & ((1 << traits.qbpp) - 1), traits.qbpp); } // Disable the Microsoft Static Analyzer warning: Potential comparison of a constant with another constant. (false warning, triggered by template construction) #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:6326) #endif // Sets up a lookup table to "Quantize" sample difference. template void JlsCodec::InitQuantizationLUT() { // for lossless mode with default parameters, we have precomputed the look up table for bit counts 8, 10, 12 and 16. if (traits.NEAR == 0 && traits.MAXVAL == (1 << traits.bpp) - 1) { const JpegLSPresetCodingParameters presets = ComputeDefault(traits.MAXVAL, traits.NEAR); if (presets.Threshold1 == T1 && presets.Threshold2 == T2 && presets.Threshold3 == T3) { if (traits.bpp == 8) { _pquant = &rgquant8Ll[rgquant8Ll.size() / 2]; return; } if (traits.bpp == 10) { _pquant = &rgquant10Ll[rgquant10Ll.size() / 2]; return; } if (traits.bpp == 12) { _pquant = &rgquant12Ll[rgquant12Ll.size() / 2]; return; } if (traits.bpp == 16) { _pquant = &rgquant16Ll[rgquant16Ll.size() / 2]; return; } } } const int32_t RANGE = 1 << traits.bpp; _rgquant.resize(static_cast(RANGE) * 2); _pquant = &_rgquant[RANGE]; for (int32_t i = -RANGE; i < RANGE; ++i) { _pquant[i] = QuantizeGratientOrg(i); } } #ifdef _PREFAST_ #pragma warning(pop) #endif template signed char JlsCodec::QuantizeGratientOrg(int32_t Di) const noexcept { if (Di <= -T3) return -4; if (Di <= -T2) return -3; if (Di <= -T1) return -2; if (Di < -traits.NEAR) return -1; if (Di <= traits.NEAR) return 0; if (Di < T1) return 1; if (Di < T2) return 2; if (Di < T3) return 3; return 4; } // RI = Run interruption: functions that handle the sample terminating a run. template int32_t JlsCodec::DecodeRIError(CContextRunMode& ctx) { const int32_t k = ctx.GetGolomb(); const int32_t EMErrval = DecodeValue(k, traits.LIMIT - J[_RUNindex]-1, traits.qbpp); const int32_t Errval = ctx.ComputeErrVal(EMErrval + ctx._nRItype, k); ctx.UpdateVariables(Errval, EMErrval); return Errval; } template void JlsCodec::EncodeRIError(CContextRunMode& ctx, int32_t Errval) { const int32_t k = ctx.GetGolomb(); const bool map = ctx.ComputeMap(Errval, k); const int32_t EMErrval = 2 * std::abs(Errval) - ctx._nRItype - static_cast(map); ASSERT(Errval == ctx.ComputeErrVal(EMErrval + ctx._nRItype, k)); EncodeMappedValue(k, EMErrval, traits.LIMIT-J[_RUNindex]-1); ctx.UpdateVariables(Errval, EMErrval); } template Triplet JlsCodec::DecodeRIPixel(Triplet Ra, Triplet Rb) { const int32_t Errval1 = DecodeRIError(_contextRunmode[0]); const int32_t Errval2 = DecodeRIError(_contextRunmode[0]); const int32_t Errval3 = DecodeRIError(_contextRunmode[0]); return Triplet(traits.ComputeReconstructedSample(Rb.v1, Errval1 * Sign(Rb.v1 - Ra.v1)), traits.ComputeReconstructedSample(Rb.v2, Errval2 * Sign(Rb.v2 - Ra.v2)), traits.ComputeReconstructedSample(Rb.v3, Errval3 * Sign(Rb.v3 - Ra.v3))); } template Triplet JlsCodec::EncodeRIPixel(Triplet x, Triplet Ra, Triplet Rb) { const int32_t errval1 = traits.ComputeErrVal(Sign(Rb.v1 - Ra.v1) * (x.v1 - Rb.v1)); EncodeRIError(_contextRunmode[0], errval1); const int32_t errval2 = traits.ComputeErrVal(Sign(Rb.v2 - Ra.v2) * (x.v2 - Rb.v2)); EncodeRIError(_contextRunmode[0], errval2); const int32_t errval3 = traits.ComputeErrVal(Sign(Rb.v3 - Ra.v3) * (x.v3 - Rb.v3)); EncodeRIError(_contextRunmode[0], errval3); return Triplet(traits.ComputeReconstructedSample(Rb.v1, errval1 * Sign(Rb.v1 - Ra.v1)), traits.ComputeReconstructedSample(Rb.v2, errval2 * Sign(Rb.v2 - Ra.v2)), traits.ComputeReconstructedSample(Rb.v3, errval3 * Sign(Rb.v3 - Ra.v3))); } template typename Traits::SAMPLE JlsCodec::DecodeRIPixel(int32_t Ra, int32_t Rb) { if (std::abs(Ra - Rb) <= traits.NEAR) { const int32_t ErrVal = DecodeRIError(_contextRunmode[1]); return static_cast(traits.ComputeReconstructedSample(Ra, ErrVal)); } const int32_t ErrVal = DecodeRIError(_contextRunmode[0]); return static_cast(traits.ComputeReconstructedSample(Rb, ErrVal * Sign(Rb - Ra))); } template typename Traits::SAMPLE JlsCodec::EncodeRIPixel(int32_t x, int32_t Ra, int32_t Rb) { if (std::abs(Ra - Rb) <= traits.NEAR) { const int32_t ErrVal = traits.ComputeErrVal(x - Ra); EncodeRIError(_contextRunmode[1], ErrVal); return static_cast(traits.ComputeReconstructedSample(Ra, ErrVal)); } const int32_t ErrVal = traits.ComputeErrVal((x - Rb) * Sign(Rb - Ra)); EncodeRIError(_contextRunmode[0], ErrVal); return static_cast(traits.ComputeReconstructedSample(Rb, ErrVal * Sign(Rb - Ra))); } // RunMode: Functions that handle run-length encoding template void JlsCodec::EncodeRunPixels(int32_t runLength, bool endOfLine) { while (runLength >= static_cast(1 << J[_RUNindex])) { Strategy::AppendOnesToBitStream(1); runLength = runLength - static_cast(1 << J[_RUNindex]); IncrementRunIndex(); } if (endOfLine) { if (runLength != 0) { Strategy::AppendOnesToBitStream(1); } } else { Strategy::AppendToBitStream(runLength, J[_RUNindex] + 1); // leading 0 + actual remaining length } } template int32_t JlsCodec::DecodeRunPixels(PIXEL Ra, PIXEL* startPos, int32_t cpixelMac) { int32_t index = 0; while (Strategy::ReadBit()) { const int count = std::min(1 << J[_RUNindex], int(cpixelMac - index)); index += count; ASSERT(index <= cpixelMac); if (count == (1 << J[_RUNindex])) { IncrementRunIndex(); } if (index == cpixelMac) break; } if (index != cpixelMac) { // incomplete run. index += (J[_RUNindex] > 0) ? Strategy::ReadValue(J[_RUNindex]) : 0; } if (index > cpixelMac) throw charls_error(charls::ApiResult::InvalidCompressedData); for (int32_t i = 0; i < index; ++i) { startPos[i] = Ra; } return index; } template int32_t JlsCodec::DoRunMode(int32_t index, EncoderStrategy*) { const int32_t ctypeRem = _width - index; PIXEL* ptypeCurX = _currentLine + index; const PIXEL* ptypePrevX = _previousLine + index; const PIXEL Ra = ptypeCurX[-1]; int32_t runLength = 0; while (traits.IsNear(ptypeCurX[runLength],Ra)) { ptypeCurX[runLength] = Ra; runLength++; if (runLength == ctypeRem) break; } EncodeRunPixels(runLength, runLength == ctypeRem); if (runLength == ctypeRem) return runLength; ptypeCurX[runLength] = EncodeRIPixel(ptypeCurX[runLength], Ra, ptypePrevX[runLength]); DecrementRunIndex(); return runLength + 1; } template int32_t JlsCodec::DoRunMode(int32_t startIndex, DecoderStrategy*) { const PIXEL Ra = _currentLine[startIndex-1]; const int32_t runLength = DecodeRunPixels(Ra, _currentLine + startIndex, _width - startIndex); const int32_t endIndex = startIndex + runLength; if (endIndex == _width) return endIndex - startIndex; // run interruption const PIXEL Rb = _previousLine[endIndex]; _currentLine[endIndex] = DecodeRIPixel(Ra, Rb); DecrementRunIndex(); return endIndex - startIndex + 1; } /// Encodes/Decodes a scan line of samples template void JlsCodec::DoLine(SAMPLE*) { int32_t index = 0; int32_t Rb = _previousLine[index-1]; int32_t Rd = _previousLine[index]; while (index < _width) { const int32_t Ra = _currentLine[index -1]; const int32_t Rc = Rb; Rb = Rd; Rd = _previousLine[index + 1]; const int32_t Qs = ComputeContextID(QuantizeGratient(Rd - Rb), QuantizeGratient(Rb - Rc), QuantizeGratient(Rc - Ra)); if (Qs != 0) { _currentLine[index] = DoRegular(Qs, _currentLine[index], GetPredictedValue(Ra, Rb, Rc), static_cast(nullptr)); index++; } else { index += DoRunMode(index, static_cast(nullptr)); Rb = _previousLine[index - 1]; Rd = _previousLine[index]; } } } /// Encodes/Decodes a scan line of triplets in ILV_SAMPLE mode template void JlsCodec::DoLine(Triplet*) { int32_t index = 0; while(index < _width) { const Triplet Ra = _currentLine[index - 1]; const Triplet Rc = _previousLine[index - 1]; const Triplet Rb = _previousLine[index]; const Triplet Rd = _previousLine[index + 1]; const int32_t Qs1 = ComputeContextID(QuantizeGratient(Rd.v1 - Rb.v1), QuantizeGratient(Rb.v1 - Rc.v1), QuantizeGratient(Rc.v1 - Ra.v1)); const int32_t Qs2 = ComputeContextID(QuantizeGratient(Rd.v2 - Rb.v2), QuantizeGratient(Rb.v2 - Rc.v2), QuantizeGratient(Rc.v2 - Ra.v2)); const int32_t Qs3 = ComputeContextID(QuantizeGratient(Rd.v3 - Rb.v3), QuantizeGratient(Rb.v3 - Rc.v3), QuantizeGratient(Rc.v3 - Ra.v3)); if (Qs1 == 0 && Qs2 == 0 && Qs3 == 0) { index += DoRunMode(index, static_cast(nullptr)); } else { Triplet Rx; Rx.v1 = DoRegular(Qs1, _currentLine[index].v1, GetPredictedValue(Ra.v1, Rb.v1, Rc.v1), static_cast(nullptr)); Rx.v2 = DoRegular(Qs2, _currentLine[index].v2, GetPredictedValue(Ra.v2, Rb.v2, Rc.v2), static_cast(nullptr)); Rx.v3 = DoRegular(Qs3, _currentLine[index].v3, GetPredictedValue(Ra.v3, Rb.v3, Rc.v3), static_cast(nullptr)); _currentLine[index] = Rx; index++; } } } // DoScan: Encodes or decodes a scan. // In ILV_SAMPLE mode, multiple components are handled in DoLine // In ILV_LINE mode, a call do DoLine is made for every component // In ILV_NONE mode, DoScan is called for each component template void JlsCodec::DoScan() { const int32_t pixelstride = _width + 4; const int components = Info().interleaveMode == charls::InterleaveMode::Line ? Info().components : 1; std::vector vectmp(static_cast(2) * components * pixelstride); std::vector rgRUNindex(components); for (int32_t line = 0; line < Info().height; ++line) { _previousLine = &vectmp[1]; _currentLine = &vectmp[1 + static_cast(components) * pixelstride]; if ((line & 1) == 1) { std::swap(_previousLine, _currentLine); } Strategy::OnLineBegin(_width, _currentLine, pixelstride); for (int component = 0; component < components; ++component) { _RUNindex = rgRUNindex[component]; // initialize edge pixels used for prediction _previousLine[_width] = _previousLine[_width - 1]; _currentLine[-1] = _previousLine[0]; DoLine(static_cast(nullptr)); // dummy argument for overload resolution rgRUNindex[component] = _RUNindex; _previousLine += pixelstride; _currentLine += pixelstride; } if (_rect.Y <= line && line < _rect.Y + _rect.Height) { Strategy::OnLineEnd(_rect.Width, _currentLine + _rect.X - (static_cast(components) * pixelstride), pixelstride); } } Strategy::EndScan(); } // Factory function for ProcessLine objects to copy/transform un encoded pixels to/from our scan line buffers. template std::unique_ptr JlsCodec::CreateProcess(ByteStreamInfo info) { if (!IsInterleaved()) { return info.rawData ? std::unique_ptr(std::make_unique(info.rawData, Info(), sizeof(typename Traits::PIXEL))) : std::unique_ptr(std::make_unique(info.rawStream, Info(), sizeof(typename Traits::PIXEL))); } if (Info().colorTransformation == ColorTransformation::None) return std::make_unique>>(info, Info(), TransformNone()); if (Info().bitsPerSample == sizeof(SAMPLE) * 8) { switch (Info().colorTransformation) { case ColorTransformation::HP1: return std::make_unique>>(info, Info(), TransformHp1()); case ColorTransformation::HP2: return std::make_unique>>(info, Info(), TransformHp2()); case ColorTransformation::HP3: return std::make_unique>>(info, Info(), TransformHp3()); default: std::ostringstream message; message << "Color transformation " << Info().colorTransformation << " is not supported."; throw charls_error(ApiResult::UnsupportedColorTransform, message.str()); } } if (Info().bitsPerSample > 8) { const int shift = 16 - Info().bitsPerSample; switch (Info().colorTransformation) { case ColorTransformation::HP1: return std::make_unique>>>(info, Info(), TransformShifted>(shift)); case ColorTransformation::HP2: return std::make_unique>>>(info, Info(), TransformShifted>(shift)); case ColorTransformation::HP3: return std::make_unique>>>(info, Info(), TransformShifted>(shift)); default: std::ostringstream message; message << "Color transformation " << Info().colorTransformation << " is not supported."; throw charls_error(ApiResult::UnsupportedColorTransform, message.str()); } } throw charls_error(ApiResult::UnsupportedBitDepthForTransform); } // Setup codec for encoding and calls DoScan WARNING_SUPPRESS(26433) template size_t JlsCodec::EncodeScan(std::unique_ptr processLine, ByteStreamInfo& compressedData) { Strategy::_processLine = std::move(processLine); Strategy::Init(compressedData); DoScan(); return Strategy::GetLength(); } // Setup codec for decoding and calls DoScan template void JlsCodec::DecodeScan(std::unique_ptr processLine, const JlsRect& rect, ByteStreamInfo& compressedData) { Strategy::_processLine = std::move(processLine); const uint8_t* compressedBytes = compressedData.rawData; _rect = rect; Strategy::Init(compressedData); DoScan(); SkipBytes(compressedData, Strategy::GetCurBytePos() - compressedBytes); } WARNING_UNSUPPRESS() // Initialize the codec data structures. Depends on JPEG-LS parameters like Threshold1-Threshold3. template void JlsCodec::InitParams(int32_t t1, int32_t t2, int32_t t3, int32_t nReset) { T1 = t1; T2 = t2; T3 = t3; InitQuantizationLUT(); const int32_t A = std::max(2, (traits.RANGE + 32) / 64); for (unsigned int Q = 0; Q < sizeof(_contexts) / sizeof(_contexts[0]); ++Q) { _contexts[Q] = JlsContext(A); } _contextRunmode[0] = CContextRunMode(std::max(2, (traits.RANGE + 32) / 64), 0, nReset); _contextRunmode[1] = CContextRunMode(std::max(2, (traits.RANGE + 32) / 64), 1, nReset); _RUNindex = 0; } #endif dcm2niix-1.0.20181125/console/charls/util.h000066400000000000000000000115551337661136700200610ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_UTIL #define CHARLS_UTIL #include "publictypes.h" #include #include #include // ReSharper disable once CppUnusedIncludeDirective #include // Use an uppercase alias for assert to make it clear that it is a pre-processor macro. #define ASSERT(t) assert(t) //https://github.com/team-charls/charls/issues/38 #if __cplusplus == 201103L template std::unique_ptr make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } #endif // Only use __forceinline for the Microsoft C++ compiler in release mode (verified scenario) // Use the build-in optimizer for all other C++ compilers. // Note: usage of FORCE_INLINE may be reduced in the future as the latest generation of C++ compilers // can handle optimization by themselves. #ifndef FORCE_INLINE # ifdef _MSC_VER # ifdef NDEBUG # define FORCE_INLINE __forceinline # else # define FORCE_INLINE # endif # else # define FORCE_INLINE # endif #endif #ifdef _MSC_VER #define WARNING_SUPPRESS(x) __pragma(warning(push)) __pragma(warning(disable : x)) #define WARNING_UNSUPPRESS() __pragma(warning(pop)) #else #define WARNING_SUPPRESS(x) #define WARNING_UNSUPPRESS() #endif constexpr size_t int32_t_bit_count = sizeof(int32_t) * 8; inline void push_back(std::vector& values, uint16_t value) { values.push_back(uint8_t(value / 0x100)); values.push_back(uint8_t(value % 0x100)); } inline int32_t log_2(int32_t n) noexcept { int32_t x = 0; while (n > (static_cast(1) << x)) { ++x; } return x; } inline int32_t Sign(int32_t n) noexcept { return (n >> (int32_t_bit_count - 1)) | 1; } inline int32_t BitWiseSign(int32_t i) noexcept { return i >> (int32_t_bit_count - 1); } template struct Triplet { Triplet() noexcept : v1(0), v2(0), v3(0) {} Triplet(int32_t x1, int32_t x2, int32_t x3) noexcept : v1(static_cast(x1)), v2(static_cast(x2)), v3(static_cast(x3)) {} union { T v1; T R; }; union { T v2; T G; }; union { T v3; T B; }; }; inline bool operator==(const Triplet& lhs, const Triplet& rhs) noexcept { return lhs.v1 == rhs.v1 && lhs.v2 == rhs.v2 && lhs.v3 == rhs.v3; } inline bool operator!=(const Triplet& lhs, const Triplet& rhs) noexcept { return !(lhs == rhs); } template struct Quad : Triplet { Quad() : v4(0) {} WARNING_SUPPRESS(26495) // false warning that v4 is unintialized Quad(Triplet triplet, int32_t alpha) noexcept : Triplet(triplet), A(static_cast(alpha)) {} WARNING_UNSUPPRESS() union { sample v4; sample A; }; }; template struct FromBigEndian { }; template<> struct FromBigEndian<4> { FORCE_INLINE static unsigned int Read(const uint8_t* pbyte) noexcept { return (pbyte[0] << 24u) + (pbyte[1] << 16u) + (pbyte[2] << 8u) + (pbyte[3] << 0u); } }; template<> struct FromBigEndian<8> { FORCE_INLINE static uint64_t Read(const uint8_t* pbyte) noexcept { return (static_cast(pbyte[0]) << 56u) + (static_cast(pbyte[1]) << 48u) + (static_cast(pbyte[2]) << 40u) + (static_cast(pbyte[3]) << 32u) + (static_cast(pbyte[4]) << 24u) + (static_cast(pbyte[5]) << 16u) + (static_cast(pbyte[6]) << 8u) + (static_cast(pbyte[7]) << 0u); } }; class charls_error : public std::system_error { public: explicit charls_error(charls::ApiResult errorCode) : system_error(static_cast(errorCode), CharLSCategoryInstance()) { } charls_error(charls::ApiResult errorCode, const std::string& message) : system_error(static_cast(errorCode), CharLSCategoryInstance(), message) { } private: static const std::error_category& CharLSCategoryInstance() noexcept; }; inline void SkipBytes(ByteStreamInfo& streamInfo, std::size_t count) noexcept { if (!streamInfo.rawData) return; streamInfo.rawData += count; streamInfo.count -= count; } template std::ostream& operator<<(typename std::enable_if::value, std::ostream>::type& stream, const T& e) { return stream << static_cast::type>(e); } #endif dcm2niix-1.0.20181125/console/jpg_0XC3.cpp000077500000000000000000000640101337661136700174750ustar00rootroot00000000000000#include //requires VS 2015 or later #include #include #include #include #include "jpg_0XC3.h" #include "print.h" unsigned char readByte(unsigned char *lRawRA, long *lRawPos, long lRawSz) { unsigned char ret = 0x00; if (*lRawPos < lRawSz) ret = lRawRA[*lRawPos]; (*lRawPos)++; return ret; }// readByte() uint16_t readWord(unsigned char *lRawRA, long *lRawPos, long lRawSz) { return ( (readByte(lRawRA, lRawPos, lRawSz) << 8) + readByte(lRawRA, lRawPos, lRawSz)); }// readWord() int readBit(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos) {//Read the next single bit int result = (lRawRA[*lRawPos] >> (7 - *lCurrentBitPos)) & 1; (*lCurrentBitPos)++; if (*lCurrentBitPos == 8) { (*lRawPos)++; *lCurrentBitPos = 0; } return result; }// readBit() int bitMask(int bits) { return ( (2 << (bits - 1)) -1); }// bitMask() int readBits (unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, int lNum) { //lNum: bits to read, not to exceed 16 int result = lRawRA[*lRawPos]; result = (result << 8) + lRawRA[(*lRawPos)+1]; result = (result << 8) + lRawRA[(*lRawPos)+2]; result = (result >> (24 - * lCurrentBitPos -lNum)) & bitMask(lNum); //lCurrentBitPos is incremented from 1, so -1 *lCurrentBitPos = *lCurrentBitPos + lNum; if (*lCurrentBitPos > 7) { *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); // div 8 *lCurrentBitPos = *lCurrentBitPos & 7; //mod 8 } return result; }// readBits() struct HufTables { uint8_t SSSSszRA[18]; uint8_t LookUpRA[256]; int DHTliRA[32]; int DHTstartRA[32]; int HufSz[32]; int HufCode[32]; int HufVal[32]; int MaxHufSi; int MaxHufVal; };// HufTables() int decodePixelDifference(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, struct HufTables l) { int lByte = (lRawRA[*lRawPos] << *lCurrentBitPos) + (lRawRA[*lRawPos+1] >> (8- *lCurrentBitPos)); lByte = lByte & 255; int lHufValSSSS = l.LookUpRA[lByte]; if (lHufValSSSS < 255) { *lCurrentBitPos = l.SSSSszRA[lHufValSSSS] + *lCurrentBitPos; *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); *lCurrentBitPos = *lCurrentBitPos & 7; } else { //full SSSS is not in the first 8-bits int lInput = lByte; int lInputBits = 8; (*lRawPos)++; // forward 8 bits = precisely 1 byte do { lInputBits++; lInput = (lInput << 1) + readBit(lRawRA, lRawPos, lCurrentBitPos); if (l.DHTliRA[lInputBits] != 0) { //if any 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 } while ((lRawPos < lRawSz) && (btMarkerType == 0)); uint16_t lSegmentLength = readWord (lRawRA, &lRawPos, lRawSz); //read marker length long lSegmentEnd = lRawPos+(lSegmentLength - 2); if (lSegmentEnd > lRawSz) { abortGoto("Segment larger than image\n"); } if (verbose) printMessage("btMarkerType %#02X length %d@%ld\n", btMarkerType, lSegmentLength, lRawPos); if ( ((btMarkerType >= 0xC0) && (btMarkerType <= 0xC3)) || ((btMarkerType >= 0xC5) && (btMarkerType <= 0xCB)) || ((btMarkerType >= 0xCD) && (btMarkerType <= 0xCF)) ) { //if Start-Of-Frame (SOF) marker SOFprecision = readByte(lRawRA, &lRawPos, lRawSz); SOFydim = readWord(lRawRA, &lRawPos, lRawSz); SOFxdim = readWord(lRawRA, &lRawPos, lRawSz); SOFnf = readByte(lRawRA, &lRawPos, lRawSz); //SOFarrayPos = lRawPos; lRawPos = (lSegmentEnd); if (verbose) printMessage(" [Precision %d X*Y %d*%d Frames %d]\n", SOFprecision, SOFxdim, SOFydim, SOFnf); if (btMarkerType != 0xC3) { //lImgTypeC3 = true; abortGoto("This JPEG decoder can only decompress lossless JPEG ITU-T81 images (SoF must be 0XC3, not %#02X)\n",btMarkerType ); } if ( (SOFprecision < 1) || (SOFprecision > 16) || (SOFnf < 1) || (SOFnf == 2) || (SOFnf > 3) || ((SOFnf == 3) && (SOFprecision > 8)) ) { abortGoto("Scalar data must be 1..16 bit, RGB data must be 8-bit (%d-bit, %d frames)\n", SOFprecision, SOFnf); } } else if (btMarkerType == 0xC4) {//if SOF marker else if define-Huffman-tables marker (DHT) if (verbose) printMessage(" [Huffman Length %d]\n", lSegmentLength); do { uint8_t DHTnLi = readByte(lRawRA, &lRawPos, lRawSz ); //we read but ignore DHTtcth. #pragma unused(DHTnLi) //we need to increment the input file position, but we do not care what the value is DHTnLi = 0; for (int lInc = 1; lInc <= 16; lInc++) { l[lFrameCount].DHTliRA[lInc] = readByte(lRawRA, &lRawPos, lRawSz); DHTnLi = DHTnLi + l[lFrameCount].DHTliRA[lInc]; if (l[lFrameCount].DHTliRA[lInc] != 0) l[lFrameCount].MaxHufSi = lInc; if (verbose) printMessage("DHT has %d combinations with %d bits\n", l[lFrameCount].DHTliRA[lInc], lInc); } if (DHTnLi > 17) { abortGoto("Huffman table corrupted.\n"); } int lIncY = 0; //frequency for (int lInc = 0; lInc <= 31; lInc++) {//lInc := 0 to 31 do begin l[lFrameCount].HufVal[lInc] = -1; l[lFrameCount].HufSz[lInc] = -1; l[lFrameCount].HufCode[lInc] = -1; } for (int lInc = 1; lInc <= 16; lInc++) {//set the huffman size values if (l[lFrameCount].DHTliRA[lInc] > 0) { l[lFrameCount].DHTstartRA[lInc] = lIncY+1; for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lInc]; lIncX++) { lIncY++; btS1 = readByte(lRawRA, &lRawPos, lRawSz); l[lFrameCount].HufVal[lIncY] = btS1; l[lFrameCount].MaxHufVal = btS1; if (verbose) printMessage("DHT combination %d has a value of %d\n", lIncY, btS1); if (btS1 <= 16) //unsigned ints ALWAYS >0, so no need for(btS1 >= 0) l[lFrameCount].HufSz[lIncY] = lInc; else { abortGoto("Huffman size array corrupted.\n"); } } } } //set huffman size values int K = 1; int Code = 0; int Si = l[lFrameCount].HufSz[K]; do { while (Si == l[lFrameCount].HufSz[K]) { l[lFrameCount].HufCode[K] = Code; Code = Code + 1; K++; } if (K <= DHTnLi) { while (l[lFrameCount].HufSz[K] > Si) { Code = Code << 1; //Shl!!! Si = Si + 1; }//while Si }//K <= 17 } while (K <= DHTnLi); //if (verbose) // for (int j = 1; j <= DHTnLi; j++) // printMessage(" [%d Sz %d Code %d Value %d]\n", j, l[lFrameCount].HufSz[j], l[lFrameCount].HufCode[j], l[lFrameCount].HufVal[j]); lFrameCount++; } while ((lSegmentEnd-lRawPos) >= 18); lnHufTables = lFrameCount - 1; lRawPos = (lSegmentEnd); if (verbose) printMessage(" [FrameCount %d]\n", lnHufTables); } else if (btMarkerType == 0xDD) { //if DHT marker else if Define restart interval (DRI) marker abortGoto("btMarkerType == 0xDD: unsupported Restart Segments\n"); //lRestartSegmentSz = ReadWord(lRawRA, &lRawPos, lRawSz); //lRawPos = lSegmentEnd; } else if (btMarkerType == 0xDA) { //if DRI marker else if read Start of Scan (SOS) marker SOSns = readByte(lRawRA, &lRawPos, lRawSz); //if Ns = 1 then NOT interleaved, else interleaved: see B.2.3 // SOSarrayPos = lRawPos; //not required... if (SOSns > 0) { for (int lInc = 1; lInc <= SOSns; lInc++) { btS1 = readByte(lRawRA, &lRawPos, lRawSz); //component identifier 1=Y,2=Cb,3=Cr,4=I,5=Q #pragma unused(btS1) //dummy value used to increment file position btS2 = readByte(lRawRA, &lRawPos, lRawSz); //horizontal and vertical sampling factors #pragma unused(btS2) //dummy value used to increment file position } } SOSss = readByte(lRawRA, &lRawPos, lRawSz); //predictor selection B.3 SOSse = readByte(lRawRA, &lRawPos, lRawSz); #pragma unused(SOSse) //dummy value used to increment file position SOSahal = readByte(lRawRA, &lRawPos, lRawSz); //lower 4bits= pointtransform SOSpttrans = SOSahal & 16; if (verbose) printMessage(" [Predictor: %d Transform %d]\n", SOSss, SOSahal); lRawPos = (lSegmentEnd); } else //if SOS marker else skip marker lRawPos = (lSegmentEnd); } while ((lRawPos < lRawSz) && (btMarkerType != 0xDA)); //0xDA=Start of scan: loop for reading header //NEXT: Huffman decoding if (lnHufTables < 1) { abortGoto("Decoding error: no Huffman tables.\n"); } //NEXT: unpad data - delete byte that follows $FF int lIsRestartSegments = 0; long lIncI = lRawPos; //input position long lIncO = lRawPos; //output position do { lRawRA[lIncO] = lRawRA[lIncI]; if (lRawRA[lIncI] == 255) { if (lRawRA[lIncI+1] == 0) lIncI = lIncI+1; else if (lRawRA[lIncI+1] == 0xD9) lIncO = -666; //end of padding else lIsRestartSegments = lRawRA[lIncI+1]; } lIncI++; lIncO++; } while (lIncO > 0); //if (lIsRestartSegments != 0) //detects both restart and corruption https://groups.google.com/forum/#!topic/comp.protocols.dicom/JUuz0B_aE5o // printWarning("Detected restart segments, decompress with dcmdjpeg or gdcmconv 0xFF%02X.\n", lIsRestartSegments); //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values //NEXT: prepare lookup table for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount ++) { for (int lInc = 0; lInc <= 17; lInc ++) l[lFrameCount].SSSSszRA[lInc] = 123; //Impossible value for SSSS, suggests 8-bits can not describe answer for (int lInc = 0; lInc <= 255; lInc ++) l[lFrameCount].LookUpRA[lInc] = 255; //Impossible value for SSSS, suggests 8-bits can not describe answer } //NEXT: fill lookuptable for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount ++) { int lIncY = 0; for (int lSz = 1; lSz <= 8; lSz ++) { //set the huffman lookup table for keys with lengths <=8 if (l[lFrameCount].DHTliRA[lSz]> 0) { for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lSz]; lIncX ++) { lIncY++; int lHufVal = l[lFrameCount].HufVal[lIncY]; //SSSS l[lFrameCount].SSSSszRA[lHufVal] = lSz; int k = (l[lFrameCount].HufCode[lIncY] << (8-lSz )) & 255; //K= most sig bits for hufman table if (lSz < 8) { //fill in all possible bits that exceed the huffman table int lInc = bitMask(8-lSz); for (int lCurrentBitPos = 0; lCurrentBitPos <= lInc; lCurrentBitPos++) { l[lFrameCount].LookUpRA[k+lCurrentBitPos] = lHufVal; } } else l[lFrameCount].LookUpRA[k] = lHufVal; //SSSS //printMessage("Frame %d SSSS %d Size %d Code %d SHL %d EmptyBits %ld\n", lFrameCount, lHufRA[lFrameCount][lIncY].HufVal, lHufRA[lFrameCount][lIncY].HufSz,lHufRA[lFrameCount][lIncY].HufCode, k, lInc); } //Set SSSS } //Length of size lInc > 0 } //for lInc := 1 to 8 } //For each frame, e.g. once each for Red/Green/Blue //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values if (lnHufTables < SOFnf) { //use single Hufman table for each frame for (int lFrameCount = lnHufTables+1; lFrameCount <= SOFnf; lFrameCount++) { l[lFrameCount] = l[lnHufTables]; } //for each frame } // if lnHufTables < SOFnf //NEXT: uncompress data: different loops for different predictors int lItems = SOFxdim*SOFydim*SOFnf; // lRawPos++;// <- only for Pascal where array is indexed from 1 not 0 first byte of data int lCurrentBitPos = 0; //read in a new byte //depending on SOSss, we see Table H.1 int lPredA = 0; int lPredB = 0; int lPredC = 0; if (SOSss == 2) //predictor selection 2: above lPredA = SOFxdim-1; else if (SOSss == 3) //predictor selection 3: above+left lPredA = SOFxdim; else if ((SOSss == 4) || (SOSss == 5)) { //these use left, above and above+left WEIGHT LEFT lPredA = 0; //Ra left lPredB = SOFxdim-1; //Rb directly above lPredC = SOFxdim; //Rc UpperLeft:above and to the left } else if (SOSss == 6) { //also use left, above and above+left, WEIGHT ABOVE lPredB = 0; lPredA = SOFxdim-1; //Rb directly above lPredC = SOFxdim; //Rc UpperLeft:above and to the left } else lPredA = 0; //Ra: directly to left) if (SOFprecision > 8) { //start - 16 bit data *bits = 16; int lPx = -1; //pixel position int lPredicted = 1 << (SOFprecision-1-SOSpttrans); lImgRA8 = (unsigned char*) malloc(lItems * 2); uint16_t *lImgRA16 = (uint16_t*) lImgRA8; for (int i = 0; i < lItems; i++) lImgRA16[i] = 0; //zero array int frame = 1; for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor lPx++; //writenext voxel if (lIncX > 1) lPredicted = lImgRA16[lPx-1]; lImgRA16[lPx] = lPredicted+ decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } for (int lIncY = 2; lIncY <= SOFydim; lIncY++) {//for all subsequent rows lPx++; //write next voxel lPredicted = lImgRA16[lPx-SOFxdim]; //use ABOVE lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); if (SOSss == 4) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA16[lPx-lPredA]+lImgRA16[lPx-lPredB]-lImgRA16[lPx-lPredC]; lPx++; //writenext voxel lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } //for lIncX } else if ((SOSss == 5) || (SOSss == 6)) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA16[lPx-lPredA]+ ((lImgRA16[lPx-lPredB]-lImgRA16[lPx-lPredC]) >> 1); lPx++; //writenext voxel lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } //for lIncX } else if (SOSss == 7) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPx++; //writenext voxel lPredicted = (lImgRA16[lPx-1]+lImgRA16[lPx-SOFxdim]) >> 1; lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } //for lIncX } else { //SOSss 1,2,3 read single values for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA16[lPx-lPredA]; lPx++; //writenext voxel lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } //for lIncX } // if..else possible predictors }//for lIncY } else if (SOFnf == 3) { //if 16-bit data; else 8-bit 3 frames *bits = 8; lImgRA8 = (unsigned char*) malloc(lItems ); int lPx[kmaxFrames+1], lPredicted[kmaxFrames+1]; //pixel position for (int f = 1; f <= SOFnf; f++) { lPx[f] = ((f-1) * (SOFxdim * SOFydim) ) -1; lPredicted[f] = 1 << (SOFprecision-1-SOSpttrans); } for (int i = 0; i < lItems; i++) lImgRA8[i] = 255; //zero array for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor for (int f = 1; f <= SOFnf; f++) { lPx[f]++; //writenext voxel if (lIncX > 1) lPredicted[f] = lImgRA8[lPx[f]-1]; lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //first row always predicted by LEFT for (int lIncY = 2; lIncY <= SOFydim; lIncY++) {//for all subsequent rows for (int f = 1; f <= SOFnf; f++) { lPx[f]++; //write next voxel lPredicted[f] = lImgRA8[lPx[f]-SOFxdim]; //use ABOVE lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); }//first column of row always predicted by ABOVE if (SOSss == 4) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { for (int f = 1; f <= SOFnf; f++) { lPredicted[f] = lImgRA8[lPx[f]-lPredA]+lImgRA8[lPx[f]-lPredB]-lImgRA8[lPx[f]-lPredC]; lPx[f]++; //writenext voxel lImgRA8[lPx[f]] = lPredicted[f]+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //for lIncX } else if ((SOSss == 5) || (SOSss == 6)) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { for (int f = 1; f <= SOFnf; f++) { lPredicted[f] = lImgRA8[lPx[f]-lPredA]+ ((lImgRA8[lPx[f]-lPredB]-lImgRA8[lPx[f]-lPredC]) >> 1); lPx[f]++; //writenext voxel lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //for lIncX } else if (SOSss == 7) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { for (int f = 1; f <= SOFnf; f++) { lPx[f]++; //writenext voxel lPredicted[f] = (lImgRA8[lPx[f]-1]+lImgRA8[lPx[f]-SOFxdim]) >> 1; lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //for lIncX } else { //SOSss 1,2,3 read single values for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { for (int f = 1; f <= SOFnf; f++) { lPredicted[f] = lImgRA8[lPx[f]-lPredA]; lPx[f]++; //writenext voxel lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //for lIncX } // if..else possible predictors }//for lIncY } else { //if 8-bit data 3frames; else 8-bit 1 frames *bits = 8; lImgRA8 = (unsigned char*) malloc(lItems ); int lPx = -1; //pixel position int lPredicted = 1 << (SOFprecision-1-SOSpttrans); for (int i = 0; i < lItems; i++) lImgRA8[i] = 0; //zero array for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor lPx++; //writenext voxel if (lIncX > 1) lPredicted = lImgRA8[lPx-1]; int dx = decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); lImgRA8[lPx] = lPredicted+dx; } for (int lIncY = 2; lIncY <= SOFydim; lIncY++) {//for all subsequent rows lPx++; //write next voxel lPredicted = lImgRA8[lPx-SOFxdim]; //use ABOVE lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); if (SOSss == 4) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA8[lPx-lPredA]+lImgRA8[lPx-lPredB]-lImgRA8[lPx-lPredC]; lPx++; //writenext voxel lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); } //for lIncX } else if ((SOSss == 5) || (SOSss == 6)) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA8[lPx-lPredA]+ ((lImgRA8[lPx-lPredB]-lImgRA8[lPx-lPredC]) >> 1); lPx++; //writenext voxel lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); } //for lIncX } else if (SOSss == 7) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPx++; //writenext voxel lPredicted = (lImgRA8[lPx-1]+lImgRA8[lPx-SOFxdim]) >> 1; lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); } //for lIncX } else { //SOSss 1,2,3 read single values for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA8[lPx-lPredA]; lPx++; //writenext voxel lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); } //for lIncX } // if..else possible predictors }//for lIncY } //if 16bit else 8bit free(lRawRA); *dimX = SOFxdim; *dimY = SOFydim; *frames = SOFnf; if (verbose) printMessage("JPEG ends %ld@%ld\n", lRawPos, lRawPos+skipBytes); return lImgRA8; }// decode_JPEG_SOF_0XC3() dcm2niix-1.0.20181125/console/jpg_0XC3.h000066400000000000000000000014361337661136700171420ustar00rootroot00000000000000//Decode DICOM Transfer Syntax 1.2.840.10008.1.2.4.70 and 1.2.840.10008.1.2.4.57 // JPEG Lossless, Nonhierarchical // see ISO/IEC 10918-1 / ITU T.81 // specifically, format with 'Start of Frame' (SOF) code 0xC3 // http://www.w3.org/Graphics/JPEG/itu-t81.pdf // This code decodes data with 1..16 bits per pixel // It appears unique to medical imaging, and is not supported by most JPEG libraries // http://www.dicomlibrary.com/dicom/transfer-syntax/ // https://en.wikipedia.org/wiki/Lossless_JPEG#Lossless_mode_of_operation #ifndef _JPEG_SOF_0XC3_ #define _JPEG_SOF_0XC3_ #ifdef __cplusplus extern "C" { #endif unsigned char * decode_JPEG_SOF_0XC3 (const char *fn, int skipBytes, bool verbose, int *dimX, int *dimY, int *bits, int *frames, int diskBytes); #ifdef __cplusplus } #endif #endifdcm2niix-1.0.20181125/console/main_console.cpp000066400000000000000000000503241337661136700206260ustar00rootroot00000000000000// main.m dcm2niix // by Chris Rorden on 3/22/14, see license.txt // Copyright (c) 2014 Chris Rorden. All rights reserved. //g++ -O3 main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -s -o dcm2niix -lz //if you do not have zlib,you can compile without it // g++ -O3 -DmyDisableZLib main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -s -o dcm2niix //or you can build your own copy: // to compile you will first want to build the Z library, then compile the project // cd zlib-1.2.8 // sudo ./configure; // sudo make //to generate combined 32-bit and 64-bit builds for OSX : // g++ -O3 -x c++ main_console.c nii_dicom.c nifti1_io_core.c nii_ortho.c nii_dicom_batch.c -s -arch x86_64 -o dcm2niix64 -lz // g++ -O3 -x c++ main_console.c nii_dicom.c nifti1_io_core.c nii_ortho.c nii_dicom_batch.c -s -arch i386 -o dcm2niix32 -lz // lipo -create dcm2niix32 dcm2niix64 -o dcm2niix //On windows with mingw you may get "fatal error: zlib.h: No such file // to remedy, run "mingw-get install libz-dev" from mingw //Alternatively, windows users with VisualStudio can compile this project // vcvarsall amd64 // cl /EHsc main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -DmyDisableOpenJPEG /o dcm2niix //#define mydebugtest //automatically process directory specified in main, ignore input arguments #include //requires VS 2015 or later #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 #if !defined(_WIN64) && !defined(_WIN32) #include #include double get_wall_time(){ struct timeval time; if (gettimeofday(&time,NULL)){ // Handle error return 0; } return (double)time.tv_sec + (double)time.tv_usec * .000001; } #endif const char* removePath(const char* path) { // "/usr/path/filename.exe" -> "filename.exe" const char* pDelimeter = strrchr (path, '\\'); if (pDelimeter) path = pDelimeter+1; pDelimeter = strrchr (path, '/'); if (pDelimeter) path = pDelimeter+1; return path; } //removePath() 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); char bidsCh = 'n'; if (opts.isCreateBIDS) bidsCh = 'y'; printf(" -b : BIDS sidecar (y/n/o [o=only: no NIfTI], default %c)\n", bidsCh); if (opts.isAnonymizeBIDS) bidsCh = 'y'; else bidsCh = 'n'; printf(" -ba : anonymize BIDS (y/n, default %c)\n", bidsCh); printf(" -c : comment stored in NIfTI aux_file (up to 24 characters)\n"); printf(" -d : directory search depth. Convert DICOMs in sub-folders of in_folder? (0..9, default %d)\n", opts.dirSearchDepth); if (opts.isSortDTIbyBVal) bidsCh = 'y'; else bidsCh = 'n'; //printf(" -d : diffusion volumes sorted by b-value (y/n, default %c)\n", bidsCh); #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, %%i=ID of patient, %%j=seriesInstanceUID, %%k=studyInstanceUID, %%m=manufacturer, %%n=name of patient, %%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) max16Ch = 'y'; printf(" -l : losslessly scale 16-bit integers to use dynamic range (y/n, default %c)\n", max16Ch); printf(" -m : merge 2D slices from same series regardless of study time, echo, coil, orientation, etc. (y/n, default n)\n"); printf(" -n : only convert this series 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"); printf(" -t : text notes includes private patient details (y/n, default n)\n"); #if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only printf(" -u : up-to-date check\n"); #endif printf(" -v : verbose (n/y or 0/1/2 [no, yes, logorrheic], default 0)\n"); printf(" -x : crop (y/n, default n)\n"); char gzCh = 'n'; if (opts.isGz) gzCh = 'y'; #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 #if defined(_WIN64) || defined(_WIN32) 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 printf(" Defaults file : %s\n", opts.optsname); printf(" Examples :\n"); printf(" %s /Users/chris/dir\n", cstr); printf(" %s -c \"my comment\" /Users/chris/dir\n", cstr); printf(" %s -o /users/cr/outdir/ -z y ~/dicomdir\n", cstr); printf(" %s -f %%p_%%s -b y -ba n ~/dicomdir\n", cstr); printf(" %s -f mystudy%%s ~/dicomdir\n", cstr); printf(" %s -o \"~/dir with spaces/dir\" ~/dicomdir\n", cstr); #endif } //showHelp() int invalidParam(int i, const char * argv[]) { if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == 'o') || (argv[i][0] == 'O') || (argv[i][0] == 'h') || (argv[i][0] == 'H') || (argv[i][0] == 'i') || (argv[i][0] == 'I') || (argv[i][0] == '0') || (argv[i][0] == '1') || (argv[i][0] == '2') || (argv[i][0] == '3') ) return 0; //if (argv[i][0] != '-') return 0; printf(" Error: invalid option '%s %s'\n", argv[i-1], argv[i]); return 1; } #if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only int checkUpToDate() { #define URL "/rordenlab/dcm2niix/releases/" #define APIURL "\"https://api.github.com/repos" URL "latest\"" #define HTMURL "https://github.com" URL #define SHELLSCRIPT "#!/usr/bin/env bash\n curl --silent " APIURL " | grep '\"tag_name\":' | sed -E 's/.*\"([^\"]+)\".*/\\1/'" //check first 13 characters, e.g. "v1.0.20171204" #define versionChars 13 FILE *pipe = popen(SHELLSCRIPT, "r"); char ch, gitvers[versionChars+1]; int n = 0; int nMatch = 0; while ((ch = fgetc(pipe)) != EOF) { if (n < versionChars) { gitvers[n] = ch; if (gitvers[n] == kDCMvers[n]) nMatch ++; n ++; } } pclose(pipe); gitvers[n] = 0; //null terminate if (n < 1) { //script reported nothing printf("Error: unable to check version with script:\n %s\n", SHELLSCRIPT); return 3; //different from EXIT_SUCCESS (0) and EXIT_FAILURE (1) } if (nMatch == versionChars) { //versions match printf("Good news: Your version is up to date: %s\n", gitvers); return EXIT_SUCCESS; } //report error char myvers[versionChars+1]; for (int i = 0; i < versionChars; i++) myvers[i] = kDCMvers[i]; myvers[versionChars] = 0; //null terminate int myv = atoi(myvers + 5); //skip "v1.0." int gitv = atoi(gitvers + 5); //skip "v1.0." if (myv > gitv) { printf("Warning: your version ('%s') more recent than stable release ('%s')\n %s\n", myvers, gitvers, HTMURL); return 2; //different from EXIT_SUCCESS (0) and EXIT_FAILURE (1) } printf("Error: your version ('%s') is not the latest release ('%s')\n %s\n", myvers, gitvers, HTMURL); return EXIT_FAILURE; } //checkUpToDate() #endif //shell script for UNIX only //#define mydebugtest int main(int argc, const char * argv[]) { struct TDCMopts opts; bool isSaveIni = false; bool isResetDefaults = false; readIniFile(&opts, argv); //set default preferences #ifdef mydebugtest //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/Philips_PARREC_Rotation/NoRotation/DBIEX_4_1.PAR"); //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/test"); strcpy(opts.indir, "e:\\t1s"); #else #if defined(__APPLE__) #define kOS "MacOS" #elif (defined(__linux) || defined(__linux__)) #define kOS "Linux" #else #define kOS "Windows" #endif printf("Chris Rorden's dcm2niiX version %s (%llu-bit %s)\n",kDCMvers, (unsigned long long) sizeof(size_t)*8, kOS); if (argc < 2) { showHelp(argv, opts); return 0; } //for (int i = 1; i < argc; i++) { printf(" argument %d= '%s'\n", i, argv[i]);} int i = 1; int lastCommandArg = 0; while (i < (argc)) { //-1 as final parameter is DICOM directory if ((strlen(argv[i]) > 1) && (argv[i][0] == '-')) { //command if (argv[i][1] == 'h') showHelp(argv, opts); else if ((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] == '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] == '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.isMaximize16BitRange = false; else opts.isMaximize16BitRange = 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 = false; else opts.isForceStackSameSeries = true; } 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)) { 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] == '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 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; } else if ((argv[i][1] == 'f') && ((i+1) < argc)) { i++; strcpy(opts.filename,argv[i]); } 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++; float seriesNumber = atof(argv[i]); if (seriesNumber < 0) opts.numSeries = -1; //report series: convert none else if ((opts.numSeries >= 0) && (opts.numSeries < MAX_NUM_SERIES)) { opts.seriesNumber[opts.numSeries] = seriesNumber; opts.numSeries += 1; } else { printf("Warning: too many series specified, ignoring -n %s\n", argv[i]); } } else printf(" Error: invalid option '%s %s'\n", argv[i], argv[i+1]);; lastCommandArg = i; } //if parameter is a command i ++; //read next parameter } //while parameters to read #ifndef myDisableZLib if ((opts.isGz) && (opts.dirSearchDepth < 1) && (strlen(opts.pigzname)>0)) { strcpy(opts.pigzname,""); printf("n.b. Setting directory search depth of zero invokes internal gz (network mode)\n"); } #endif if (isSaveIni) saveIniFile(opts); //printf("%d %d",argc,lastCommandArg); if (argc == (lastCommandArg+1)) { //+1 as array indexed from 0 //the user did not provide an input filename, report filename structure char niiFilename[1024]; strcpy(opts.outdir,"");//no input supplied nii_createDummyFilename(niiFilename, opts); printf("%s\n",niiFilename); return EXIT_SUCCESS; } #endif #ifndef myEnableMultipleInputs if ((argc-lastCommandArg-1) > 1) { printf("Warning: only processing last of %d input files (recompile with 'myEnableMultipleInputs' to recursively process multiple files)\n", argc-lastCommandArg-1); lastCommandArg = argc - 2; } #endif #if !defined(_WIN64) && !defined(_WIN32) double startWall = get_wall_time(); #endif clock_t start = clock(); for (i = (lastCommandArg+1); i < argc; i++) { strcpy(opts.indir,argv[i]); // [argc-1] int ret = nii_loadDir(&opts); if (ret != EXIT_SUCCESS) return ret; } #if !defined(_WIN64) && !defined(_WIN32) printf ("Conversion required %f seconds (%f for core code).\n",get_wall_time() - startWall, ((float)(clock()-start))/CLOCKS_PER_SEC); #else printf ("Conversion required %f seconds.\n",((float)(clock()-start))/CLOCKS_PER_SEC); #endif //if (isSaveIni) //we now save defaults earlier, in case of early termination. // saveIniFile(opts); return EXIT_SUCCESS; } dcm2niix-1.0.20181125/console/main_console_batch.cpp000066400000000000000000000107661337661136700217750ustar00rootroot00000000000000// 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 #include //requires VS 2015 or later #ifdef _MSC_VER #include //access() #ifndef F_OK #define F_OK 0 /* existence check */ #endif #else #include //access() #endif #include #include #include #include #include #include #include // clock_t, clock, CLOCKS_PER_SEC #include #include "nii_dicom.h" #include "nii_dicom_batch.h" const char* removePath(const char* path) { // "/usr/path/filename.exe" -> "filename.exe" const char* pDelimeter = strrchr (path, '\\'); if (pDelimeter) path = pDelimeter+1; pDelimeter = strrchr (path, '/'); if (pDelimeter) path = pDelimeter+1; return path; } //removePath() int rmainbatch(TDCMopts opts) { clock_t start = clock(); nii_loadDir(&opts); printf ("Conversion required %f seconds.\n",((float)(clock()-start))/CLOCKS_PER_SEC); return EXIT_SUCCESS; } //rmainbatch() void showHelp(const char * argv[]) { const char *cstr = removePath(argv[0]); printf("Usage: %s \n", cstr); printf("\n"); printf("The configuration file must be in yaml format as shown below\n"); printf("\n"); printf("### START YAML FILE ###\n"); printf("Options:\n"); printf(" isGz: false\n"); printf(" isFlipY: false\n"); printf(" isVerbose: false\n"); printf(" isCreateBIDS: false\n"); printf(" isOnlySingleFile: false\n"); printf("Files:\n"); printf(" -\n"); printf(" in_dir: /path/to/first/folder\n"); printf(" out_dir: /path/to/output/folder\n"); printf(" filename: dcemri\n"); printf(" -\n"); printf(" in_dir: /path/to/second/folder\n"); printf(" out_dir: /path/to/output/folder\n"); printf(" filename: fa3\n"); printf("### END YAML FILE ###\n"); printf("\n"); #if defined(_WIN64) || defined(_WIN32) printf(" Example :\n"); printf(" %s c:\\dir\\yaml.yml\n", cstr); printf(" %s \"c:\\dir with spaces\\yaml.yml\"\n", cstr); #else printf(" Examples :\n"); printf(" %s /Users/chris/yaml.yml\n", cstr); printf(" %s \"/Users/dir with spaces/yaml.yml\"\n", cstr); #endif } //showHelp() int main(int argc, const char * argv[]) { #if defined(__APPLE__) #define kOS "MacOS" #elif (defined(__linux) || defined(__linux__)) #define kOS "Linux" #else #define kOS "Windows" #endif printf("dcm2niibatch using Chris Rorden's dcm2niiX version %s (%llu-bit %s)\n",kDCMvers, (unsigned long long) sizeof(size_t)*8, kOS); if (argc != 2) { if (argc < 2) printf(" Please provide location of config file\n"); else printf(" Do not include additional inputs with a config file\n"); printf("\n"); showHelp(argv); return EXIT_FAILURE; } if( access( argv[1], F_OK ) == -1 ) { printf(" Please provide location of config file\n"); printf("\n"); showHelp(argv); return EXIT_FAILURE; } // Process it all via a yaml file std::string yaml_file = argv[1]; std::cout << "yaml_path: " << yaml_file << std::endl; YAML::Node config = YAML::LoadFile(yaml_file); struct TDCMopts opts; readIniFile(&opts, argv); //setup defaults, e.g. path to pigz opts.isCreateBIDS = config["Options"]["isCreateBIDS"].as(); opts.isOnlySingleFile = config["Options"]["isOnlySingleFile"].as(); opts.isFlipY = config["Options"]["isFlipY"].as(); opts.isCreateText = false; opts.isVerbose = 0; opts.isGz = config["Options"]["isGz"].as(); //save data as compressed (.nii.gz) or raw (.nii) /*bool isInternalGz = config["Options"]["isInternalGz"].as(); if (isInternalGz) { strcpy(opts.pigzname, "”); //do NOT use pigz: force internal compressor //in general, pigz is faster unless you have a very slow network, in which case the internal compressor is better }*/ for (auto i: config["Files"]) { std::string indir = i["in_dir"].as(); strcpy(opts.indir, indir.c_str()); std::string outdir = i["out_dir"].as(); strcpy(opts.outdir, outdir.c_str()); std::string filename = i["filename"].as(); strcpy(opts.filename, filename.c_str()); rmainbatch(opts); } return EXIT_SUCCESS; } // main() dcm2niix-1.0.20181125/console/makefile000066400000000000000000000011521337661136700171470ustar00rootroot00000000000000# Regular use CFLAGS=-s -O3 # Debugging #CFLAGS=-g #run "make" for default build #run "JPEGLS=1 make" for JPEGLS build JFLAGS= ifeq "$(JPEGLS)" "1" JFLAGS=-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp endif ifneq ($(OS),Windows_NT) OS = $(shell uname) ifeq "$(OS)" "Darwin" CFLAGS=-dead_strip -O3 endif endif all: g++ $(CFLAGS) -I. $(JFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG dcm2niix-1.0.20181125/console/miniz.c000066400000000000000000007035561337661136700167620ustar00rootroot00000000000000/*miniz.c 2.08 is available. In April 2018 Martin Rainer (who created the 2.0 release) notes: The main change is that it supports ZIP64. If you do not need that because your use case does not run into the ZIP limitations, you should stick with the old version. Since DICOM images are inherently limited to 2gb, dcm2niix will keep using v1.15 */ /* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing See "unlicense" statement at the end of this file. Rich Geldreich , last updated Oct. 13, 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). * Change History 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!): - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug would only have 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() (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti - Merged MZ_FORCEINLINE fix from hdeanclark - Fix include before config #ifdef, thanks emil.brink - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can set it to 1 for real-time compression). - Merged in some compiler fixes from paulharris's github repro. - Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. - Added example6.c, which dumps an image of the mandelbrot set to a PNG file. - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). 4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. 5/28/11 v1.11 - Added statement from unlicense.org 5/27/11 v1.10 - Substantial compressor optimizations: - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. - Refactored the compression code for better readability and maintainability. - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large drop in throughput on some files). 5/15/11 v1.09 - Initial stable release. * Low-level Deflate/Inflate implementation notes: Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses approximately as well as zlib. Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory block large enough to hold the entire file. The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. * zlib-style API notes: miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in zlib replacement in many apps: The z_stream struct, optional memory allocation callbacks deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound inflateInit/inflateInit2/inflate/inflateEnd compress, compress2, compressBound, uncompress CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. Supports raw deflate streams or standard zlib streams with adler-32 checking. Limitations: The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but there are no guarantees that miniz.c pulls this off perfectly. * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by Alex Evans. Supports 1-4 bytes/pixel images. * ZIP archive API notes: The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to get the job done with minimal fuss. There are simple API's to retrieve file information, read files from existing archives, create new archives, append new files to existing archives, or clone archive data from one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), or you can specify custom file read/write callbacks. - Archive reading: Just call this function to read a single file from a disk archive: void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); The locate operation can optionally check file comments too, which (as one example) can be used to identify multiple versions of the same file in an archive. This function uses a simple linear search through the central directory, so it's not very fast. Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and retrieve detailed info on each file by calling mz_zip_reader_file_stat(). - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data to disk and builds an exact image of the central directory in memory. The central directory image is written all at once at the end of the archive file when the archive is finalized. The archive writer can optionally align each file's local header and file data to any power of 2 alignment, which can be useful when the archive will be read from optical media. Also, the writer supports placing arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still readable by any ZIP tool. - Archive appending: The simple way to add a single file to an archive is to call this function: mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); The archive will be created if it doesn't already exist, otherwise it'll be appended to. Note the appending is done in-place and is not an atomic operation, so if something goes wrong during the operation it's possible the archive could be left without a central directory (although the local file headers and file data will be fine, so the archive will be recoverable). For more complex archive modification scenarios: 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and you're done. This is safe but requires a bunch of temporary disk space or heap memory. 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), append new files as needed, then finalize the archive which will write an updated central directory to the original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a possibility that the archive's central directory could be lost with this method if anything goes wrong, though. - ZIP archive support limitations: No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. Requires streams capable of seeking. * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. * Important: For best perf. be sure to customize the below macros for your target platform: #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define MINIZ_LITTLE_ENDIAN 1 #define MINIZ_HAS_64BIT_REGISTERS 1 * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). */ #ifndef MINIZ_HEADER_INCLUDED #define MINIZ_HEADER_INCLUDED #include // Defines to completely disable specific portions of miniz.c: // If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. // Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. //#define MINIZ_NO_STDIO // If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or // get/set file times, and the C run-time funcs that get/set times won't be called. // The current downside is the times written to your archives will be from 1979. //#define MINIZ_NO_TIME // Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. //#define MINIZ_NO_ARCHIVE_APIS // Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's. //#define MINIZ_NO_ARCHIVE_WRITING_APIS // Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. //#define MINIZ_NO_ZLIB_APIS // Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. //#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES // Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. // Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc // callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user // functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. //#define MINIZ_NO_MALLOC #if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux #define MINIZ_NO_TIME #endif #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) #include #endif #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) // MINIZ_X86_OR_X64_CPU is only used to help set the below macros. #define MINIZ_X86_OR_X64_CPU 1 #endif #if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU // Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. #define MINIZ_LITTLE_ENDIAN 1 #endif #if MINIZ_X86_OR_X64_CPU // Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #endif #if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) // Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). #define MINIZ_HAS_64BIT_REGISTERS 1 #endif #ifdef __cplusplus extern "C" { #endif // ------------------- zlib-style API Definitions. // For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! typedef unsigned long mz_ulong; // mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. void mz_free(void *p); #define MZ_ADLER32_INIT (1) // mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); #define MZ_CRC32_INIT (0) // mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); // Compression strategies. enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; // Method #define MZ_DEFLATED 8 #ifndef MINIZ_NO_ZLIB_APIS // Heap allocation callbacks. // Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); typedef void (*mz_free_func)(void *opaque, void *address); typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); #define MZ_VERSION "9.1.15" #define MZ_VERNUM 0x91F0 #define MZ_VER_MAJOR 9 #define MZ_VER_MINOR 1 #define MZ_VER_REVISION 15 #define MZ_VER_SUBREVISION 0 // Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; // Return status codes. MZ_PARAM_ERROR is non-standard. enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; // Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; // Window bits #define MZ_DEFAULT_WINDOW_BITS 15 struct mz_internal_state; // Compression/decompression stream struct. typedef struct mz_stream_s { const unsigned char *next_in; // pointer to next byte to read unsigned int avail_in; // number of bytes available at next_in mz_ulong total_in; // total number of bytes consumed so far unsigned char *next_out; // pointer to next byte to write unsigned int avail_out; // number of bytes that can be written to next_out mz_ulong total_out; // total number of bytes produced so far char *msg; // error msg (unused) struct mz_internal_state *state; // internal state, allocated by zalloc/zfree mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) mz_free_func zfree; // optional heap free function (defaults to free) void *opaque; // heap alloc function user pointer int data_type; // data_type (unused) mz_ulong adler; // adler32 of the source or uncompressed data mz_ulong reserved; // not used } mz_stream; typedef mz_stream *mz_streamp; // Returns the version string of miniz.c. const char *mz_version(void); // mz_deflateInit() initializes a compressor with default options: // Parameters: // pStream must point to an initialized mz_stream struct. // level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. // level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. // (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) // Return values: // MZ_OK on success. // MZ_STREAM_ERROR if the stream is bogus. // MZ_PARAM_ERROR if the input parameters are bogus. // MZ_MEM_ERROR on out of memory. int mz_deflateInit(mz_streamp pStream, int level); // mz_deflateInit2() is like mz_deflate(), except with more control: // Additional parameters: // method must be MZ_DEFLATED // window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) // mem_level must be between [1, 9] (it's checked but ignored by miniz.c) int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); // Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). int mz_deflateReset(mz_streamp pStream); // mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. // Parameters: // pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. // flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. // Return values: // MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). // MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. // MZ_STREAM_ERROR if the stream is bogus. // MZ_PARAM_ERROR if one of the parameters is invalid. // MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) int mz_deflate(mz_streamp pStream, int flush); // mz_deflateEnd() deinitializes a compressor: // Return values: // MZ_OK on success. // MZ_STREAM_ERROR if the stream is bogus. int mz_deflateEnd(mz_streamp pStream); // mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); // Single-call compression functions mz_compress() and mz_compress2(): // Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); // mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). mz_ulong mz_compressBound(mz_ulong source_len); // Initializes a decompressor. int mz_inflateInit(mz_streamp pStream); // mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: // window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). int mz_inflateInit2(mz_streamp pStream, int window_bits); // Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. // Parameters: // pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. // flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. // On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). // MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. // Return values: // MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. // MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. // MZ_STREAM_ERROR if the stream is bogus. // MZ_DATA_ERROR if the deflate stream is invalid. // MZ_PARAM_ERROR if one of the parameters is invalid. // MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again // with more input data, or with more room in the output buffer (except when using single call decompression, described above). int mz_inflate(mz_streamp pStream, int flush); // Deinitializes a decompressor. int mz_inflateEnd(mz_streamp pStream); // Single-call decompression. // Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); // Returns a string description of the specified error code, or NULL if the error code is invalid. const char *mz_error(int err); // Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. // Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES typedef unsigned char Byte; typedef unsigned int uInt; typedef mz_ulong uLong; typedef Byte Bytef; typedef uInt uIntf; typedef char charf; typedef int intf; typedef void *voidpf; typedef uLong uLongf; typedef void *voidp; typedef void *const voidpc; #define Z_NULL 0 #define Z_NO_FLUSH MZ_NO_FLUSH #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH #define Z_SYNC_FLUSH MZ_SYNC_FLUSH #define Z_FULL_FLUSH MZ_FULL_FLUSH #define Z_FINISH MZ_FINISH #define Z_BLOCK MZ_BLOCK #define Z_OK MZ_OK #define Z_STREAM_END MZ_STREAM_END #define Z_NEED_DICT MZ_NEED_DICT #define Z_ERRNO MZ_ERRNO #define Z_STREAM_ERROR MZ_STREAM_ERROR #define Z_DATA_ERROR MZ_DATA_ERROR #define Z_MEM_ERROR MZ_MEM_ERROR #define Z_BUF_ERROR MZ_BUF_ERROR #define Z_VERSION_ERROR MZ_VERSION_ERROR #define Z_PARAM_ERROR MZ_PARAM_ERROR #define Z_NO_COMPRESSION MZ_NO_COMPRESSION #define Z_BEST_SPEED MZ_BEST_SPEED #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY #define Z_FILTERED MZ_FILTERED #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY #define Z_RLE MZ_RLE #define Z_FIXED MZ_FIXED #define Z_DEFLATED MZ_DEFLATED #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS #define alloc_func mz_alloc_func #define free_func mz_free_func #define internal_state mz_internal_state #define z_stream mz_stream #define deflateInit mz_deflateInit #define deflateInit2 mz_deflateInit2 #define deflateReset mz_deflateReset #define deflate mz_deflate #define deflateEnd mz_deflateEnd #define deflateBound mz_deflateBound #define compress mz_compress #define compress2 mz_compress2 #define compressBound mz_compressBound #define inflateInit mz_inflateInit #define inflateInit2 mz_inflateInit2 #define inflate mz_inflate #define inflateEnd mz_inflateEnd #define uncompress mz_uncompress #define crc32 mz_crc32 #define adler32 mz_adler32 #define MAX_WBITS 15 #define MAX_MEM_LEVEL 9 #define zError mz_error #define ZLIB_VERSION MZ_VERSION #define ZLIB_VERNUM MZ_VERNUM #define ZLIB_VER_MAJOR MZ_VER_MAJOR #define ZLIB_VER_MINOR MZ_VER_MINOR #define ZLIB_VER_REVISION MZ_VER_REVISION #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION #define zlibVersion mz_version #define zlib_version mz_version() #endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES #endif // MINIZ_NO_ZLIB_APIS // ------------------- Types and macros typedef unsigned char mz_uint8; typedef signed short mz_int16; typedef unsigned short mz_uint16; typedef unsigned int mz_uint32; typedef unsigned int mz_uint; typedef long long mz_int64; typedef unsigned long long mz_uint64; typedef int mz_bool; #define MZ_FALSE (0) #define MZ_TRUE (1) // An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. #ifdef _MSC_VER #define MZ_MACRO_END while (0, 0) #else #define MZ_MACRO_END while (0) #endif // ------------------- ZIP archive reading/writing #ifndef MINIZ_NO_ARCHIVE_APIS enum { MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 }; typedef struct { mz_uint32 m_file_index; mz_uint32 m_central_dir_ofs; mz_uint16 m_version_made_by; mz_uint16 m_version_needed; mz_uint16 m_bit_flag; mz_uint16 m_method; #ifndef MINIZ_NO_TIME time_t m_time; #endif mz_uint32 m_crc32; mz_uint64 m_comp_size; mz_uint64 m_uncomp_size; mz_uint16 m_internal_attr; mz_uint32 m_external_attr; mz_uint64 m_local_header_ofs; mz_uint32 m_comment_size; char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; } mz_zip_archive_file_stat; typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); struct mz_zip_internal_state_tag; typedef struct mz_zip_internal_state_tag mz_zip_internal_state; typedef enum { MZ_ZIP_MODE_INVALID = 0, MZ_ZIP_MODE_READING = 1, MZ_ZIP_MODE_WRITING = 2, MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 } mz_zip_mode; typedef struct mz_zip_archive_tag { mz_uint64 m_archive_size; mz_uint64 m_central_directory_file_ofs; mz_uint m_total_files; mz_zip_mode m_zip_mode; mz_uint m_file_offset_alignment; mz_alloc_func m_pAlloc; mz_free_func m_pFree; mz_realloc_func m_pRealloc; void *m_pAlloc_opaque; mz_file_read_func m_pRead; mz_file_write_func m_pWrite; void *m_pIO_opaque; mz_zip_internal_state *m_pState; } mz_zip_archive; typedef enum { MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 } mz_zip_flags; // ZIP archive reading // Inits a ZIP archive reader. // These functions read and validate the archive's central directory. mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); #ifndef MINIZ_NO_STDIO mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); #endif // Returns the total number of files in the archive. mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); // Returns detailed information about an archive file entry. mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); // Determines if an archive file entry is a directory entry. mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); // Retrieves the filename of an archive file entry. // Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); // Attempts to locates a file in the archive's central directory. // Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH // Returns -1 if the file cannot be found. int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); // Extracts a archive file to a memory buffer using no memory allocation. mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); // Extracts a archive file to a memory buffer. mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); // Extracts a archive file to a dynamically allocated heap buffer. void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); // Extracts a archive file using a callback function to output the file's data. mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); #ifndef MINIZ_NO_STDIO // Extracts a archive file to a disk file and sets its last accessed and modified times. // This function only extracts files, not archive directory records. mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); #endif // Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. mz_bool mz_zip_reader_end(mz_zip_archive *pZip); // ZIP archive writing #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS // Inits a ZIP archive writer. mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); #ifndef MINIZ_NO_STDIO mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); #endif // Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. // For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. // For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). // Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. // Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before // the archive is finalized the file's central directory will be hosed. mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); // Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. // To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. // level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); #ifndef MINIZ_NO_STDIO // Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. // level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); #endif // Adds a file to an archive by fully cloning the data from another archive. // This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); // Finalizes the archive by writing the central directory records followed by the end of central directory record. // After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). // An archive must be manually finalized by calling this function for it to be valid. mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); // Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. // Note for the archive to be valid, it must have been finalized before ending. mz_bool mz_zip_writer_end(mz_zip_archive *pZip); // Misc. high-level helper functions: // mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. // level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); // Reads a single file from an archive into a heap block. // Returns NULL on failure. void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); #endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS #endif // #ifndef MINIZ_NO_ARCHIVE_APIS // ------------------- Low-level Decompression API Definitions // Decompression flags used by tinfl_decompress(). // TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. // TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. // TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). // TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. enum { TINFL_FLAG_PARSE_ZLIB_HEADER = 1, TINFL_FLAG_HAS_MORE_INPUT = 2, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, TINFL_FLAG_COMPUTE_ADLER32 = 8 }; // High level decompression functions: // tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). // On entry: // pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. // On return: // Function returns a pointer to the decompressed data, or NULL on failure. // *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. // The caller must call mz_free() on the returned block when it's no longer needed. void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); // tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. // Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); // tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. // Returns 1 on success or 0 on failure. typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; // Max size of LZ dictionary. #define TINFL_LZ_DICT_SIZE 32768 // Return status. typedef enum { TINFL_STATUS_BAD_PARAM = -3, TINFL_STATUS_ADLER32_MISMATCH = -2, TINFL_STATUS_FAILED = -1, TINFL_STATUS_DONE = 0, TINFL_STATUS_NEEDS_MORE_INPUT = 1, TINFL_STATUS_HAS_MORE_OUTPUT = 2 } tinfl_status; // Initializes the decompressor to its initial state. #define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END #define tinfl_get_adler32(r) (r)->m_check_adler32 // Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. // This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); // Internal/private bits follow. enum { TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS }; typedef struct { mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; } tinfl_huff_table; #if MINIZ_HAS_64BIT_REGISTERS #define TINFL_USE_64BIT_BITBUF 1 #endif #if TINFL_USE_64BIT_BITBUF typedef mz_uint64 tinfl_bit_buf_t; #define TINFL_BITBUF_SIZE (64) #else typedef mz_uint32 tinfl_bit_buf_t; #define TINFL_BITBUF_SIZE (32) #endif struct tinfl_decompressor_tag { mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; tinfl_bit_buf_t m_bit_buf; size_t m_dist_from_out_buf_start; tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; }; // ------------------- Low-level Compression API Definitions // Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). #define TDEFL_LESS_MEMORY 0 // tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): // TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). enum { TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF }; // TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. // TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). // TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. // TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). // TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) // TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. // TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. // TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. // The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). enum { TDEFL_WRITE_ZLIB_HEADER = 0x01000, TDEFL_COMPUTE_ADLER32 = 0x02000, TDEFL_GREEDY_PARSING_FLAG = 0x04000, TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, TDEFL_RLE_MATCHES = 0x10000, TDEFL_FILTER_MATCHES = 0x20000, TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 }; // High level compression functions: // tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). // On entry: // pSrc_buf, src_buf_len: Pointer and size of source block to compress. // flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. // On return: // Function returns a pointer to the compressed data, or NULL on failure. // *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. // The caller must free() the returned block when it's no longer needed. void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); // tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. // Returns 0 on failure. size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); // Compresses an image to a compressed PNG file in memory. // On entry: // pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. // The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. // level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL // If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). // On return: // Function returns a pointer to the compressed data, or NULL on failure. // *pLen_out will be set to the size of the PNG image file. // The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); // Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); // tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; // TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). #if TDEFL_LESS_MEMORY enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #else enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #endif // The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. typedef enum { TDEFL_STATUS_BAD_PARAM = -2, TDEFL_STATUS_PUT_BUF_FAILED = -1, TDEFL_STATUS_OKAY = 0, TDEFL_STATUS_DONE = 1, } tdefl_status; // Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums typedef enum { TDEFL_NO_FLUSH = 0, TDEFL_SYNC_FLUSH = 2, TDEFL_FULL_FLUSH = 3, TDEFL_FINISH = 4 } tdefl_flush; // tdefl's compression state structure. typedef struct { tdefl_put_buf_func_ptr m_pPut_buf_func; void *m_pPut_buf_user; mz_uint m_flags, m_max_probes[2]; int m_greedy_parsing; mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; tdefl_status m_prev_return_status; const void *m_pIn_buf; void *m_pOut_buf; size_t *m_pIn_buf_size, *m_pOut_buf_size; tdefl_flush m_flush; const mz_uint8 *m_pSrc; size_t m_src_buf_left, m_out_buf_ofs; mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; } tdefl_compressor; // Initializes the compressor. // There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. // pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. // If pBut_buf_func is NULL the user should always call the tdefl_compress() API. // flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); // Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); // tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. // tdefl_compress_buffer() always consumes the entire input buffer. tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); mz_uint32 tdefl_get_adler32(tdefl_compressor *d); // Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. #ifndef MINIZ_NO_ZLIB_APIS // Create tdefl_compress() flags given zlib-style compression parameters. // level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) // window_bits may be -15 (raw deflate) or 15 (zlib) // strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #endif // #ifndef MINIZ_NO_ZLIB_APIS #ifdef __cplusplus } #endif #endif // MINIZ_HEADER_INCLUDED // ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) #ifndef MINIZ_HEADER_FILE_ONLY typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1]; typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1]; typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1]; #include #include #define MZ_ASSERT(x) assert(x) #ifdef MINIZ_NO_MALLOC #define MZ_MALLOC(x) NULL #define MZ_FREE(x) (void)x, ((void)0) #define MZ_REALLOC(p, x) NULL #else #define MZ_MALLOC(x) malloc(x) #define MZ_FREE(x) free(x) #define MZ_REALLOC(p, x) realloc(p, x) #endif #define MZ_MAX(a,b) (((a)>(b))?(a):(b)) #define MZ_MIN(a,b) (((a)<(b))?(a):(b)) #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) #else #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) #endif #ifdef _MSC_VER #define MZ_FORCEINLINE __forceinline #elif defined(__GNUC__) #define MZ_FORCEINLINE inline __attribute__((__always_inline__)) #else #define MZ_FORCEINLINE inline #endif #ifdef __cplusplus extern "C" { #endif // ------------------- zlib-style API's mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) { mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; if (!ptr) return MZ_ADLER32_INIT; while (buf_len) { for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; } for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; } return (s2 << 16) + s1; } // Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; mz_uint32 crcu32 = (mz_uint32)crc; if (!ptr) return MZ_CRC32_INIT; crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } return ~crcu32; } void mz_free(void *p) { MZ_FREE(p); } #ifndef MINIZ_NO_ZLIB_APIS static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } const char *mz_version(void) { return MZ_VERSION; } int mz_deflateInit(mz_streamp pStream, int level) { return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); } int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) { tdefl_compressor *pComp; mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); if (!pStream) return MZ_STREAM_ERROR; if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; pStream->data_type = 0; pStream->adler = MZ_ADLER32_INIT; pStream->msg = NULL; pStream->reserved = 0; pStream->total_in = 0; pStream->total_out = 0; if (!pStream->zalloc) pStream->zalloc = def_alloc_func; if (!pStream->zfree) pStream->zfree = def_free_func; pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); if (!pComp) return MZ_MEM_ERROR; pStream->state = (struct mz_internal_state *)pComp; if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) { mz_deflateEnd(pStream); return MZ_PARAM_ERROR; } return MZ_OK; } int mz_deflateReset(mz_streamp pStream) { if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; pStream->total_in = pStream->total_out = 0; tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags); return MZ_OK; } int mz_deflate(mz_streamp pStream, int flush) { size_t in_bytes, out_bytes; mz_ulong orig_total_in, orig_total_out; int mz_status = MZ_OK; if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; if (!pStream->avail_out) return MZ_BUF_ERROR; if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; for ( ; ; ) { tdefl_status defl_status; in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; if (defl_status < 0) { mz_status = MZ_STREAM_ERROR; break; } else if (defl_status == TDEFL_STATUS_DONE) { mz_status = MZ_STREAM_END; break; } else if (!pStream->avail_out) break; else if ((!pStream->avail_in) && (flush != MZ_FINISH)) { if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) break; return MZ_BUF_ERROR; // Can't make forward progress without some input. } } return mz_status; } int mz_deflateEnd(mz_streamp pStream) { if (!pStream) return MZ_STREAM_ERROR; if (pStream->state) { pStream->zfree(pStream->opaque, pStream->state); pStream->state = NULL; } return MZ_OK; } mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { (void)pStream; // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); } int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) { int status; mz_stream stream; memset(&stream, 0, sizeof(stream)); // In case mz_ulong is 64-bits (argh I hate longs). if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)source_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_deflateInit(&stream, level); if (status != MZ_OK) return status; status = mz_deflate(&stream, MZ_FINISH); if (status != MZ_STREAM_END) { mz_deflateEnd(&stream); return (status == MZ_OK) ? MZ_BUF_ERROR : status; } *pDest_len = stream.total_out; return mz_deflateEnd(&stream); } int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); } mz_ulong mz_compressBound(mz_ulong source_len) { return mz_deflateBound(NULL, source_len); } typedef struct { tinfl_decompressor m_decomp; mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; tinfl_status m_last_status; } inflate_state; int mz_inflateInit2(mz_streamp pStream, int window_bits) { inflate_state *pDecomp; if (!pStream) return MZ_STREAM_ERROR; if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; pStream->data_type = 0; pStream->adler = 0; pStream->msg = NULL; pStream->total_in = 0; pStream->total_out = 0; pStream->reserved = 0; if (!pStream->zalloc) pStream->zalloc = def_alloc_func; if (!pStream->zfree) pStream->zfree = def_free_func; pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); if (!pDecomp) return MZ_MEM_ERROR; pStream->state = (struct mz_internal_state *)pDecomp; tinfl_init(&pDecomp->m_decomp); pDecomp->m_dict_ofs = 0; pDecomp->m_dict_avail = 0; pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; pDecomp->m_first_call = 1; pDecomp->m_has_flushed = 0; pDecomp->m_window_bits = window_bits; return MZ_OK; } int mz_inflateInit(mz_streamp pStream) { return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); } int mz_inflate(mz_streamp pStream, int flush) { inflate_state* pState; mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; size_t in_bytes, out_bytes, orig_avail_in; tinfl_status status; if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; pState = (inflate_state*)pStream->state; if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; orig_avail_in = pStream->avail_in; first_call = pState->m_first_call; pState->m_first_call = 0; if (pState->m_last_status < 0) return MZ_DATA_ERROR; if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; pState->m_has_flushed |= (flush == MZ_FINISH); if ((flush == MZ_FINISH) && (first_call)) { // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); pState->m_last_status = status; pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; if (status < 0) return MZ_DATA_ERROR; else if (status != TINFL_STATUS_DONE) { pState->m_last_status = TINFL_STATUS_FAILED; return MZ_BUF_ERROR; } return MZ_STREAM_END; } // flush != MZ_FINISH then we must assume there's more input. if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; if (pState->m_dict_avail) { n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; } for ( ; ; ) { in_bytes = pStream->avail_in; out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); pState->m_last_status = status; pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); pState->m_dict_avail = (mz_uint)out_bytes; n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); if (status < 0) return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. else if (flush == MZ_FINISH) { // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. if (status == TINFL_STATUS_DONE) return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. else if (!pStream->avail_out) return MZ_BUF_ERROR; } else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) break; } return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; } int mz_inflateEnd(mz_streamp pStream) { if (!pStream) return MZ_STREAM_ERROR; if (pStream->state) { pStream->zfree(pStream->opaque, pStream->state); pStream->state = NULL; } return MZ_OK; } int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { mz_stream stream; int status; memset(&stream, 0, sizeof(stream)); // In case mz_ulong is 64-bits (argh I hate longs). if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)source_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_inflateInit(&stream); if (status != MZ_OK) return status; status = mz_inflate(&stream, MZ_FINISH); if (status != MZ_STREAM_END) { mz_inflateEnd(&stream); return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; } *pDest_len = stream.total_out; return mz_inflateEnd(&stream); } const char *mz_error(int err) { static struct { int m_err; const char *m_pDesc; } s_error_descs[] = { { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } }; mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; return NULL; } #endif //MINIZ_NO_ZLIB_APIS // ------------------- Low-level Decompression (completely independent from all compression API's) #define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) #define TINFL_MEMSET(p, c, l) memset(p, c, l) #define TINFL_CR_BEGIN switch(r->m_state) { case 0: #define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END #define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END #define TINFL_CR_FINISH } // TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never // reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. #define TINFL_GET_BYTE(state_index, c) do { \ if (pIn_buf_cur >= pIn_buf_end) { \ for ( ; ; ) { \ if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ if (pIn_buf_cur < pIn_buf_end) { \ c = *pIn_buf_cur++; \ break; \ } \ } else { \ c = 0; \ break; \ } \ } \ } else c = *pIn_buf_cur++; } MZ_MACRO_END #define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) #define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END #define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END // TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. // It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a // Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the // bit buffer contains >=15 bits (deflate's max. Huffman code size). #define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ do { \ temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ if (temp >= 0) { \ code_len = temp >> 9; \ if ((code_len) && (num_bits >= code_len)) \ break; \ } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ code_len = TINFL_FAST_LOOKUP_BITS; \ do { \ temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ } while (num_bits < 15); // TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read // beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully // decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. // The slow path is only executed at the very end of the input buffer. #define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ int temp; mz_uint code_len, c; \ if (num_bits < 15) { \ if ((pIn_buf_end - pIn_buf_cur) < 2) { \ TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ } else { \ bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ } \ } \ if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ code_len = temp >> 9, temp &= 511; \ else { \ code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) { static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; static const int s_min_table_sizes[3] = { 257, 1, 4 }; tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; TINFL_CR_BEGIN bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } } do { TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; if (r->m_type == 0) { TINFL_SKIP_BITS(5, num_bits & 7); for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } while ((counter) && (num_bits)) { TINFL_GET_BITS(51, dist, 8); while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = (mz_uint8)dist; counter--; } while (counter) { size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } while (pIn_buf_cur >= pIn_buf_end) { if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); } else { TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); } } n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; } } else if (r->m_type == 3) { TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); } else { if (r->m_type == 1) { mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; } else { for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } r->m_table_sizes[2] = 19; } for ( ; (int)r->m_type >= 0; r->m_type--) { int tree_next, tree_cur; tinfl_huff_table *pTable; mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } if ((65536 != total) && (used_syms > 1)) { TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); } for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) { mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { tree_cur -= ((rev_code >>= 1) & 1); if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; } tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; } if (r->m_type == 2) { for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) { mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } if ((dist == 16) && (!counter)) { TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); } num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; } if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) { TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); } TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); } } for ( ; ; ) { mz_uint8 *pSrc; for ( ; ; ) { if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) { TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); if (counter >= 256) break; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = (mz_uint8)counter; } else { int sym2; mz_uint code_len; #if TINFL_USE_64BIT_BITBUF if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } #else if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } #endif if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } counter = sym2; bit_buf >>= code_len; num_bits -= code_len; if (counter & 256) break; #if !TINFL_USE_64BIT_BITBUF if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } #endif if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } bit_buf >>= code_len; num_bits -= code_len; pOut_buf_cur[0] = (mz_uint8)counter; if (sym2 & 256) { pOut_buf_cur++; counter = sym2; break; } pOut_buf_cur[1] = (mz_uint8)sym2; pOut_buf_cur += 2; } } if ((counter &= 511) == 256) break; num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); } pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) { while (counter--) { while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; } continue; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES else if ((counter >= 9) && (counter <= dist)) { const mz_uint8 *pSrc_end = pSrc + (counter & ~7); do { ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; pOut_buf_cur += 8; } while ((pSrc += 8) < pSrc_end); if ((counter &= 7) < 3) { if (counter) { pOut_buf_cur[0] = pSrc[0]; if (counter > 1) pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur += counter; } continue; } } #endif do { pOut_buf_cur[0] = pSrc[0]; pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur[2] = pSrc[2]; pOut_buf_cur += 3; pSrc += 3; } while ((int)(counter -= 3) > 2); if ((int)counter > 0) { pOut_buf_cur[0] = pSrc[0]; if ((int)counter > 1) pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur += counter; } } } } while (!(r->m_final & 1)); if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } } TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); TINFL_CR_FINISH common_exit: r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) { const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; while (buf_len) { for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; } for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; } r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; } return status; } // Higher level helper functions. void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; *pOut_len = 0; tinfl_init(&decomp); for ( ; ; ) { size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } src_buf_ofs += src_buf_size; *pOut_len += dst_buf_size; if (status == TINFL_STATUS_DONE) break; new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); if (!pNew_buf) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; } return pBuf; } size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; } int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { int result = 0; tinfl_decompressor decomp; mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; if (!pDict) return TINFL_STATUS_FAILED; tinfl_init(&decomp); for ( ; ; ) { size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); in_buf_ofs += in_buf_size; if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) break; if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { result = (status == TINFL_STATUS_DONE); break; } dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); } MZ_FREE(pDict); *pIn_buf_size = in_buf_ofs; return result; } // ------------------- Low-level Compression (independent from all decompression API's) // Purposely making these tables static for faster init and thread safety. static const mz_uint16 s_tdefl_len_sym[256] = { 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; static const mz_uint8 s_tdefl_len_extra[256] = { 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; static const mz_uint8 s_tdefl_small_dist_sym[512] = { 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; static const mz_uint8 s_tdefl_small_dist_extra[512] = { 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7 }; static const mz_uint8 s_tdefl_large_dist_sym[128] = { 0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26, 26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28, 28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 }; static const mz_uint8 s_tdefl_large_dist_extra[128] = { 0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 }; // Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1) { mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) { const mz_uint32* pHist = &hist[pass << 8]; mz_uint offsets[256], cur_ofs = 0; for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } } return pCur_syms; } // tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) { int root, leaf, next, avbl, used, dpth; if (n==0) return; else if (n==1) { A[0].m_key = 1; return; } A[0].m_key += A[1].m_key; root = 0; leaf = 2; for (next=1; next < n-1; next++) { if (leaf>=n || A[root].m_key=n || (root=0; next--) A[next].m_key = A[A[next].m_key].m_key+1; avbl = 1; used = dpth = 0; root = n-2; next = n-1; while (avbl>0) { while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; } while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } avbl = 2*used; dpth++; used = 0; } } // Limits canonical Huffman code table's max code size. enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) { int i; mz_uint32 total = 0; if (code_list_len <= 1) return; for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); while (total != (1UL << max_code_size)) { pNum_codes[max_code_size]--; for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } total--; } } static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) { int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); if (static_table) { for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; } else { tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; int num_used_syms = 0; const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); for (i = 1, j = num_used_syms; i <= code_size_limit; i++) for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); } next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); for (i = 0; i < table_len; i++) { mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; } } #define TDEFL_PUT_BITS(b, l) do { \ mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \ d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \ while (d->m_bits_in >= 8) { \ if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ d->m_bit_buffer >>= 8; \ d->m_bits_in -= 8; \ } \ } MZ_MACRO_END #define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ if (rle_repeat_count < 3) { \ d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ } else { \ d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ } rle_repeat_count = 0; } } #define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ if (rle_z_count < 3) { \ d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ } else if (rle_z_count <= 10) { \ d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ } else { \ d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ } rle_z_count = 0; } } static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static void tdefl_start_dynamic_block(tdefl_compressor *d) { int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; d->m_huff_count[0][256] = 1; tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); for (i = 0; i < total_code_sizes_to_pack; i++) { mz_uint8 code_size = code_sizes_to_pack[i]; if (!code_size) { TDEFL_RLE_PREV_CODE_SIZE(); if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } } else { TDEFL_RLE_ZERO_CODE_SIZE(); if (code_size != prev_code_size) { TDEFL_RLE_PREV_CODE_SIZE(); d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; } else if (++rle_repeat_count == 6) { TDEFL_RLE_PREV_CODE_SIZE(); } } prev_code_size = code_size; } if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); TDEFL_PUT_BITS(2, 2); TDEFL_PUT_BITS(num_lit_codes - 257, 5); TDEFL_PUT_BITS(num_dist_codes - 1, 5); for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) { mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); } } static void tdefl_start_static_block(tdefl_compressor *d) { mz_uint i; mz_uint8 *p = &d->m_huff_code_sizes[0][0]; for (i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; memset(d->m_huff_code_sizes[1], 5, 32); tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); TDEFL_PUT_BITS(1, 2); } static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; mz_uint8 *pOutput_buf = d->m_pOutput_buf; mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; mz_uint64 bit_buffer = d->m_bit_buffer; mz_uint bits_in = d->m_bits_in; #define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); } flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint s0, s1, n0, n1, sym, num_extra_bits; mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); // This sequence coaxes MSVC into using cmov's vs. jmp's. s0 = s_tdefl_small_dist_sym[match_dist & 511]; n0 = s_tdefl_small_dist_extra[match_dist & 511]; s1 = s_tdefl_large_dist_sym[match_dist >> 8]; n1 = s_tdefl_large_dist_extra[match_dist >> 8]; sym = (match_dist < 512) ? s0 : s1; num_extra_bits = (match_dist < 512) ? n0 : n1; MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } } if (pOutput_buf >= d->m_pOutput_buf_end) return MZ_FALSE; *(mz_uint64*)pOutput_buf = bit_buffer; pOutput_buf += (bits_in >> 3); bit_buffer >>= (bits_in & ~7); bits_in &= 7; } #undef TDEFL_PUT_BITS_FAST d->m_pOutput_buf = pOutput_buf; d->m_bits_in = 0; d->m_bit_buffer = 0; while (bits_in) { mz_uint32 n = MZ_MIN(bits_in, 16); TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); bit_buffer >>= n; bits_in -= n; } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #else static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint sym, num_extra_bits; mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); if (match_dist < 512) { sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; } else { sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; } MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) { if (static_block) tdefl_start_static_block(d); else tdefl_start_dynamic_block(d); return tdefl_compress_lz_codes(d); } static int tdefl_flush_block(tdefl_compressor *d, int flush) { mz_uint saved_bit_buf, saved_bits_in; mz_uint8 *pSaved_output_buf; mz_bool comp_block_succeeded = MZ_FALSE; int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; d->m_pOutput_buf = pOutput_buf_start; d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; MZ_ASSERT(!d->m_output_flush_remaining); d->m_output_flush_ofs = 0; d->m_output_flush_remaining = 0; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); } TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; if (!use_raw_block) comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) ) { mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; TDEFL_PUT_BITS(0, 2); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) { TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); } for (i = 0; i < d->m_total_lz_bytes; ++i) { TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); } } // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. else if (!comp_block_succeeded) { d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; tdefl_compress_block(d, MZ_TRUE); } if (flush) { if (flush == TDEFL_FINISH) { if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } } else { mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } } } MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) { if (d->m_pPut_buf_func) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); } else if (pOutput_buf_start == d->m_output_buf) { int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); d->m_out_buf_ofs += bytes_to_copy; if ((n -= bytes_to_copy) != 0) { d->m_output_flush_ofs = bytes_to_copy; d->m_output_flush_remaining = n; } } else { d->m_out_buf_ofs += n; } } return d->m_output_flush_remaining; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES #define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p) static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s); MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for ( ; ; ) { for ( ; ; ) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32; do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); if (!probe_len) { *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break; } else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); } } } #else static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint8 *s = d->m_dict + pos, *p, *q; mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for ( ; ; ) { for ( ; ; ) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; if (probe_len > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; } } } #endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN static mz_bool tdefl_compress_fast(tdefl_compressor *d) { // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) { const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); d->m_src_buf_left -= num_bytes_to_process; lookahead_size += num_bytes_to_process; while (num_bytes_to_process) { mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); memcpy(d->m_dict + dst_pos, d->m_pSrc, n); if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); d->m_pSrc += n; dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; num_bytes_to_process -= n; } dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; while (lookahead_size >= 4) { mz_uint cur_match_dist, cur_match_len = 1; mz_uint8 *pCur_dict = d->m_dict + cur_pos; mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; mz_uint probe_pos = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)lookahead_pos; if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) { const mz_uint16 *p = (const mz_uint16 *)pCur_dict; const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); mz_uint32 probe_len = 32; do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); if (!probe_len) cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U))) { cur_match_len = 1; *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } else { mz_uint32 s0, s1; cur_match_len = MZ_MIN(cur_match_len, lookahead_size); MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); cur_match_dist--; pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; pLZ_code_buf += 3; *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; } } else { *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } total_lz_bytes += cur_match_len; lookahead_pos += cur_match_len; dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; MZ_ASSERT(lookahead_size >= cur_match_len); lookahead_size -= cur_match_len; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } while (lookahead_size) { mz_uint8 lit = d->m_dict[cur_pos]; total_lz_bytes++; *pLZ_code_buf++ = lit; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } d->m_huff_count[0][lit]++; lookahead_pos++; dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; lookahead_size--; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } } d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; return MZ_TRUE; } #endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) { d->m_total_lz_bytes++; *d->m_pLZ_code_buf++ = lit; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } d->m_huff_count[0][lit]++; } static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) { mz_uint32 s0, s1; MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); d->m_total_lz_bytes += match_len; d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); match_dist -= 1; d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; } static mz_bool tdefl_compress_normal(tdefl_compressor *d) { const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; tdefl_flush flush = d->m_flush; while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) { mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) { mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; src_buf_left -= num_bytes_to_process; d->m_lookahead_size += num_bytes_to_process; while (pSrc != pSrc_end) { mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; } } else { while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) { mz_uint8 c = *pSrc++; mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; src_buf_left--; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) { mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); } } } d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) break; // Simple lazy/greedy parsing state machine. len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) { if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) { mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; } } else { tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); } if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) { cur_match_dist = cur_match_len = 0; } if (d->m_saved_match_len) { if (cur_match_len > d->m_saved_match_len) { tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); if (cur_match_len >= 128) { tdefl_record_match(d, cur_match_len, cur_match_dist); d->m_saved_match_len = 0; len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } } else { tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; } } else if (!cur_match_dist) tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) { tdefl_record_match(d, cur_match_len, cur_match_dist); len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } // Move the lookahead forward by len_to_move bytes. d->m_lookahead_pos += len_to_move; MZ_ASSERT(d->m_lookahead_size >= len_to_move); d->m_lookahead_size -= len_to_move; d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); // Check if it's time to flush the current LZ codes to the internal output buffer. if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) ) { int n; d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; } } d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; return MZ_TRUE; } static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) { if (d->m_pIn_buf_size) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; } if (d->m_pOut_buf_size) { size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); d->m_output_flush_ofs += (mz_uint)n; d->m_output_flush_remaining -= (mz_uint)n; d->m_out_buf_ofs += n; *d->m_pOut_buf_size = d->m_out_buf_ofs; } return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; } tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) { if (!d) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return TDEFL_STATUS_BAD_PARAM; } d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; d->m_out_buf_ofs = 0; d->m_flush = flush; if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) ) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); } d->m_wants_to_finish |= (flush == TDEFL_FINISH); if ((d->m_output_flush_remaining) || (d->m_finished)) return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) { if (!tdefl_compress_fast(d)) return d->m_prev_return_status; } else #endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN { if (!tdefl_compress_normal(d)) return d->m_prev_return_status; } if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) { if (tdefl_flush_block(d, flush) < 0) return d->m_prev_return_status; d->m_finished = (flush == TDEFL_FINISH); if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } } return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); } tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) { MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); } tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); return TDEFL_STATUS_OKAY; } tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) { return d->m_prev_return_status; } mz_uint32 tdefl_get_adler32(tdefl_compressor *d) { return d->m_adler32; } mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); MZ_FREE(pComp); return succeeded; } typedef struct { size_t m_size, m_capacity; mz_uint8 *m_pBuf; mz_bool m_expandable; } tdefl_output_buffer; static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) { tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; size_t new_size = p->m_size + len; if (new_size > p->m_capacity) { size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; } memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; return MZ_TRUE; } void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; out_buf.m_expandable = MZ_TRUE; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; *pOut_len = out_buf.m_size; return out_buf.m_pBuf; } size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_buf) return 0; out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; return out_buf.m_size; } #ifndef MINIZ_NO_ZLIB_APIS static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; // level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) { mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; return comp_flags; } #endif //MINIZ_NO_ZLIB_APIS #ifdef _MSC_VER #pragma warning (push) #pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) #endif // Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at // http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. // This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) { // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; if (!pComp) return NULL; MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } // write dummy header for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); // compress image data tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } // write real header *pLen_out = out_buf.m_size-41; { static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, 0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0, (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54}; c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24); memcpy(out_buf.m_pBuf, pnghdr, 41); } // write footer (IDAT CRC-32, followed by IEND chunk) if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24); // compute final size of file, grab compressed data buffer and return *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; } void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) { // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); } #ifdef _MSC_VER #pragma warning (pop) #endif // ------------------- .ZIP archive reading #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef MINIZ_NO_STDIO #define MZ_FILE void * #else #include #include #if defined(_MSC_VER) || defined(__MINGW64__) static FILE *mz_fopen(const char *pFilename, const char *pMode) { FILE* pFile = NULL; fopen_s(&pFile, pFilename, pMode); return pFile; } static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { FILE* pFile = NULL; if (freopen_s(&pFile, pPath, pMode, pStream)) return NULL; return pFile; } #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN mz_fopen #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 _ftelli64 #define MZ_FSEEK64 _fseeki64 #define MZ_FILE_STAT_STRUCT _stat #define MZ_FILE_STAT _stat #define MZ_FFLUSH fflush #define MZ_FREOPEN mz_freopen #define MZ_DELETE_FILE remove #elif defined(__MINGW32__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello64 #define MZ_FSEEK64 fseeko64 #define MZ_FILE_STAT_STRUCT _stat #define MZ_FILE_STAT _stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__TINYC__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftell #define MZ_FSEEK64 fseek #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__GNUC__) && _LARGEFILE64_SOURCE #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN(f, m) fopen64(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello64 #define MZ_FSEEK64 fseeko64 #define MZ_FILE_STAT_STRUCT stat64 #define MZ_FILE_STAT stat64 #define MZ_FFLUSH fflush #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) #define MZ_DELETE_FILE remove #else #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello #define MZ_FSEEK64 fseeko #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #endif // #ifdef _MSC_VER #endif // #ifdef MINIZ_NO_STDIO #define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) // Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. enum { // ZIP archive identifiers and record sizes MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, // Central directory header record offsets MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, // Local directory header offsets MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, // End of central directory offsets MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, }; typedef struct { void *m_p; size_t m_size, m_capacity; mz_uint m_element_size; } mz_zip_array; struct mz_zip_internal_state_tag { mz_zip_array m_central_dir; mz_zip_array m_central_dir_offsets; mz_zip_array m_sorted_central_dir_offsets; MZ_FILE *m_pFile; void *m_pMem; size_t m_mem_size; size_t m_mem_capacity; }; #define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size #define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) { pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); memset(pArray, 0, sizeof(mz_zip_array)); } static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) { void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) { if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) { if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } pArray->m_size = new_size; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) { return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); } static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) { size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); return MZ_TRUE; } #ifndef MINIZ_NO_TIME static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; return mktime(&tm); } static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) { #ifdef _MSC_VER struct tm tm_struct; struct tm *tm = &tm_struct; errno_t err = localtime_s(tm, &time); if (err) { *pDOS_date = 0; *pDOS_time = 0; return; } #else struct tm *tm = localtime(&time); #endif *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); } #endif #ifndef MINIZ_NO_STDIO static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) { #ifdef MINIZ_NO_TIME (void)pFilename; *pDOS_date = *pDOS_time = 0; #else struct MZ_FILE_STAT_STRUCT file_stat; // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. if (MZ_FILE_STAT(pFilename, &file_stat) != 0) return MZ_FALSE; mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); #endif // #ifdef MINIZ_NO_TIME return MZ_TRUE; } #ifndef MINIZ_NO_TIME static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time) { struct utimbuf t; t.actime = access_time; t.modtime = modified_time; return !utime(pFilename, &t); } #endif // #ifndef MINIZ_NO_TIME #endif // #ifndef MINIZ_NO_STDIO static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) { (void)flags; if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return MZ_FALSE; if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; pZip->m_zip_mode = MZ_ZIP_MODE_READING; pZip->m_archive_size = 0; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return MZ_FALSE; memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (l_len < r_len) : (l < r); } #define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END // Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); const int size = pZip->m_total_files; int start = (size - 2) >> 1, end; while (start >= 0) { int child, root = start; for ( ; ; ) { if ((child = (root << 1) + 1) >= size) break; child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } start--; } end = size - 1; while (end > 0) { int child, root = 0; MZ_SWAP_UINT32(pIndices[end], pIndices[0]); for ( ; ; ) { if ((child = (root << 1) + 1) >= end) break; child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } end--; } } static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags) { mz_uint cdir_size, num_this_disk, cdir_disk_index; mz_uint64 cdir_ofs; mz_int64 cur_file_ofs; const mz_uint8 *p; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return MZ_FALSE; // Find the end of central directory record by scanning the file from the end towards the beginning. cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); for ( ; ; ) { int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) return MZ_FALSE; for (i = n - 4; i >= 0; --i) if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) break; if (i >= 0) { cur_file_ofs += i; break; } if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) return MZ_FALSE; cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); } // Read and verify the end of central directory record. if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return MZ_FALSE; if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) return MZ_FALSE; num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) return MZ_FALSE; if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) return MZ_FALSE; cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) return MZ_FALSE; pZip->m_central_directory_file_ofs = cdir_ofs; if (pZip->m_total_files) { mz_uint i, n; // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) return MZ_FALSE; if (sort_central_dir) { if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) return MZ_FALSE; } if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) return MZ_FALSE; // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { mz_uint total_header_size, comp_size, decomp_size, disk_index; if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) return MZ_FALSE; MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); if (sort_central_dir) MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) return MZ_FALSE; disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); if ((disk_index != num_this_disk) && (disk_index != 1)) return MZ_FALSE; if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) return MZ_FALSE; if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) return MZ_FALSE; n -= total_header_size; p += total_header_size; } } if (sort_central_dir) mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); return MZ_TRUE; } mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags) { if ((!pZip) || (!pZip->m_pRead)) return MZ_FALSE; if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_archive_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end(pZip); return MZ_FALSE; } return MZ_TRUE; } static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); return s; } mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags) { if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_archive_size = size; pZip->m_pRead = mz_zip_mem_read_func; pZip->m_pIO_opaque = pZip; #ifdef __cplusplus pZip->m_pState->m_pMem = const_cast(pMem); #else pZip->m_pState->m_pMem = (void *)pMem; #endif pZip->m_pState->m_mem_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end(pZip); return MZ_FALSE; } return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) { mz_uint64 file_size; MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); if (!pFile) return MZ_FALSE; if (MZ_FSEEK64(pFile, 0, SEEK_END)) { MZ_FCLOSE(pFile); return MZ_FALSE; } file_size = MZ_FTELL64(pFile); if (!mz_zip_reader_init_internal(pZip, flags)) { MZ_FCLOSE(pFile); return MZ_FALSE; } pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pState->m_pFile = pFile; pZip->m_archive_size = file_size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end(pZip); return MZ_FALSE; } return MZ_TRUE; } #endif // #ifndef MINIZ_NO_STDIO mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) { return pZip ? pZip->m_total_files : 0; } static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) { if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return NULL; return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); } mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) { mz_uint m_bit_flag; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); if (!p) return MZ_FALSE; m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); return (m_bit_flag & 1); } mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) { mz_uint filename_len, external_attr; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); if (!p) return MZ_FALSE; // First see if the filename ends with a '/' character. filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_len) { if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') return MZ_TRUE; } // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. // FIXME: Remove this check? Is it necessary - we already check the filename. external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); if ((external_attr & 0x10) != 0) return MZ_TRUE; return MZ_FALSE; } mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) { mz_uint n; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); if ((!p) || (!pStat)) return MZ_FALSE; // Unpack the central directory record. pStat->m_file_index = file_index; pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); #ifndef MINIZ_NO_TIME pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); #endif pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); // Copy as much of the filename and comment as possible. n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); pStat->m_comment_size = n; memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; return MZ_TRUE; } mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) { mz_uint n; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_buf_size) { n = MZ_MIN(n, filename_buf_size - 1); memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pFilename[n] = '\0'; } return n + 1; } static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) { mz_uint i; if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) return 0 == memcmp(pA, pB, len); for (i = 0; i < len; ++i) if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) return MZ_FALSE; return MZ_TRUE; } static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (int)(l_len - r_len) : (l - r); } static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); const int size = pZip->m_total_files; const mz_uint filename_len = (mz_uint)strlen(pFilename); int l = 0, h = size - 1; while (l <= h) { int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); if (!comp) return file_index; else if (comp < 0) l = m + 1; else h = m - 1; } return -1; } int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) { mz_uint file_index; size_t name_len, comment_len; if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return -1; if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) return mz_zip_reader_locate_file_binary_search(pZip, pName); name_len = strlen(pName); if (name_len > 0xFFFF) return -1; comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1; for (file_index = 0; file_index < pZip->m_total_files; file_index++) { const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; if (filename_len < name_len) continue; if (comment_len) { mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); const char *pFile_comment = pFilename + filename_len + file_extra_len; if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) continue; } if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) { int ofs = filename_len - 1; do { if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) break; } while (--ofs >= 0); ofs++; pFilename += ofs; filename_len -= ofs; } if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) return file_index; } return -1; } mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { int status = TINFL_STATUS_DONE; mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; mz_zip_archive_file_stat file_stat; void *pRead_buf; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; tinfl_decompressor inflator; if ((buf_size) && (!pBuf)) return MZ_FALSE; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) if (!file_stat.m_comp_size) return MZ_TRUE; // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). // I'm torn how to handle this case - should it fail instead? if (mz_zip_reader_is_file_a_directory(pZip, file_index)) return MZ_TRUE; // Encryption and patch files are not supported. if (file_stat.m_bit_flag & (1 | 32)) return MZ_FALSE; // This function only supports stored and deflate. if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return MZ_FALSE; // Ensure supplied output buffer is large enough. needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; if (buf_size < needed_size) return MZ_FALSE; // Read and parse the local directory entry. cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return MZ_FALSE; if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return MZ_FALSE; cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return MZ_FALSE; if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { // The file is stored or the caller has requested the compressed data. if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) return MZ_FALSE; return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); } // Decompress the file either directly from memory or from a file input buffer. tinfl_init(&inflator); if (pZip->m_pState->m_pMem) { // Read directly from the archive in memory. pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else if (pUser_read_buf) { // Use a user provided read buffer. if (!user_read_buf_size) return MZ_FALSE; pRead_buf = (mz_uint8 *)pUser_read_buf; read_buf_size = user_read_buf_size; read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } else { // Temporarily allocate a read buffer. read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); #ifdef _MSC_VER if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) #else if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) #endif return MZ_FALSE; if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return MZ_FALSE; read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } do { size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; out_buf_ofs += out_buf_size; } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); if (status == TINFL_STATUS_DONE) { // Make sure the entire file was decompressed, and check its CRC. if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) status = TINFL_STATUS_FAILED; } if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); if (file_index < 0) return MZ_FALSE; return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); } mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); } mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); } void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) { mz_uint64 comp_size, uncomp_size, alloc_size; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); void *pBuf; if (pSize) *pSize = 0; if (!p) return NULL; comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; #ifdef _MSC_VER if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) #else if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) #endif return NULL; if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) return NULL; if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return NULL; } if (pSize) *pSize = (size_t)alloc_size; return pBuf; } void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) { int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); if (file_index < 0) { if (pSize) *pSize = 0; return MZ_FALSE; } return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); } mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf = NULL; void *pWrite_buf = NULL; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) if (!file_stat.m_comp_size) return MZ_TRUE; // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). // I'm torn how to handle this case - should it fail instead? if (mz_zip_reader_is_file_a_directory(pZip, file_index)) return MZ_TRUE; // Encryption and patch files are not supported. if (file_stat.m_bit_flag & (1 | 32)) return MZ_FALSE; // This function only supports stored and deflate. if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return MZ_FALSE; // Read and parse the local directory entry. cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return MZ_FALSE; if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return MZ_FALSE; cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return MZ_FALSE; // Decompress the file either directly from memory or from a file input buffer. if (pZip->m_pState->m_pMem) { pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else { read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return MZ_FALSE; read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { // The file is stored or the caller has requested the compressed data. if (pZip->m_pState->m_pMem) { #ifdef _MSC_VER if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) #else if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) #endif return MZ_FALSE; if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) status = TINFL_STATUS_FAILED; else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); cur_file_ofs += file_stat.m_comp_size; #pragma unused(cur_file_ofs) out_buf_ofs += file_stat.m_comp_size; comp_remaining = 0; #pragma unused(comp_remaining) } else { while (comp_remaining) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; break; } if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; out_buf_ofs += read_buf_avail; comp_remaining -= read_buf_avail; } } } else { tinfl_decompressor inflator; tinfl_init(&inflator); if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) status = TINFL_STATUS_FAILED; else { do { mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; if (out_buf_size) { if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) { status = TINFL_STATUS_FAILED; break; } file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) { status = TINFL_STATUS_FAILED; break; } } } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); } } if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { // Make sure the entire file was decompressed, and check its CRC. if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) status = TINFL_STATUS_FAILED; } if (!pZip->m_pState->m_pMem) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); if (pWrite_buf) pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); if (file_index < 0) return MZ_FALSE; return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) { (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); } mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) { mz_bool status; mz_zip_archive_file_stat file_stat; MZ_FILE *pFile; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; pFile = MZ_FOPEN(pDst_filename, "wb"); if (!pFile) return MZ_FALSE; status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); if (MZ_FCLOSE(pFile) == EOF) return MZ_FALSE; #ifndef MINIZ_NO_TIME if (status) mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); #endif return status; } #endif // #ifndef MINIZ_NO_STDIO mz_bool mz_zip_reader_end(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return MZ_FALSE; if (pZip->m_pState) { mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { MZ_FCLOSE(pState->m_pFile); pState->m_pFile = NULL; } #endif // #ifndef MINIZ_NO_STDIO pZip->m_pFree(pZip->m_pAlloc_opaque, pState); } pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return MZ_TRUE; } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) { int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); if (file_index < 0) return MZ_FALSE; return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); } #endif // ------------------- .ZIP archive writing #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } #define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) #define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) { if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return MZ_FALSE; if (pZip->m_file_offset_alignment) { // Ensure user specified file offset alignment is a power of 2. if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) return MZ_FALSE; } if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; pZip->m_archive_size = existing_size; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return MZ_FALSE; memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); return MZ_TRUE; } static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_zip_internal_state *pState = pZip->m_pState; mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); #ifdef _MSC_VER if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) #else if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) #endif return 0; if (new_size > pState->m_mem_capacity) { void *pNew_block; size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) return 0; pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; } memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); pState->m_mem_size = (size_t)new_size; return n; } mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) { pZip->m_pWrite = mz_zip_heap_write_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) return MZ_FALSE; if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) { if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) { mz_zip_writer_end(pZip); return MZ_FALSE; } pZip->m_pState->m_mem_capacity = initial_allocation_size; } return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) { MZ_FILE *pFile; pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) return MZ_FALSE; if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) { mz_zip_writer_end(pZip); return MZ_FALSE; } pZip->m_pState->m_pFile = pFile; if (size_to_reserve_at_beginning) { mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); do { size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) { mz_zip_writer_end(pZip); return MZ_FALSE; } cur_ofs += n; size_to_reserve_at_beginning -= n; } while (size_to_reserve_at_beginning); } return MZ_TRUE; } #endif // #ifndef MINIZ_NO_STDIO mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) { mz_zip_internal_state *pState; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return MZ_FALSE; // No sense in trying to write to an archive that's already at the support max size if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) return MZ_FALSE; pState = pZip->m_pState; if (pState->m_pFile) { #ifdef MINIZ_NO_STDIO pFilename; return MZ_FALSE; #else // Archive is being read from stdio - try to reopen as writable. if (pZip->m_pIO_opaque != pZip) return MZ_FALSE; if (!pFilename) return MZ_FALSE; pZip->m_pWrite = mz_zip_file_write_func; if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) { // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. mz_zip_reader_end(pZip); return MZ_FALSE; } #endif // #ifdef MINIZ_NO_STDIO } else if (pState->m_pMem) { // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. if (pZip->m_pIO_opaque != pZip) return MZ_FALSE; pState->m_mem_capacity = pState->m_mem_size; pZip->m_pWrite = mz_zip_heap_write_func; } // Archive is being read via a user provided read function - make sure the user has specified a write function too. else if (!pZip->m_pWrite) return MZ_FALSE; // Start writing new files at the archive's current central directory location. pZip->m_archive_size = pZip->m_central_directory_file_ofs; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; pZip->m_central_directory_file_ofs = 0; return MZ_TRUE; } mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) { return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); } typedef struct { mz_zip_archive *m_pZip; mz_uint64 m_cur_archive_file_ofs; mz_uint64 m_comp_size; } mz_zip_writer_add_state; static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser) { mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) return MZ_FALSE; pState->m_cur_archive_file_ofs += len; pState->m_comp_size += len; return MZ_TRUE; } static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) { (void)pZip; memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); return MZ_TRUE; } static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { (void)pZip; memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); return MZ_TRUE; } static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { mz_zip_internal_state *pState = pZip->m_pState; mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; size_t orig_central_dir_size = pState->m_central_dir.m_size; mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; // No zip64 support yet if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) return MZ_FALSE; if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) { // Try to push the central directory array back into its original state. mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) { // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. if (*pArchive_name == '/') return MZ_FALSE; while (*pArchive_name) { if ((*pArchive_name == '\\') || (*pArchive_name == ':')) return MZ_FALSE; pArchive_name++; } return MZ_TRUE; } static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) { mz_uint32 n; if (!pZip->m_file_offset_alignment) return 0; n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); } static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) { char buf[4096]; memset(buf, 0, MZ_MIN(sizeof(buf), n)); while (n) { mz_uint32 s = MZ_MIN(sizeof(buf), n); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) return MZ_FALSE; cur_file_ofs += s; n -= s; } return MZ_TRUE; } mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) { mz_uint16 method = 0, dos_time = 0, dos_date = 0; mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; tdefl_compressor *pComp = NULL; mz_bool store_data_uncompressed; mz_zip_internal_state *pState; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; level = level_and_flags & 0xF; store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) return MZ_FALSE; pState = pZip->m_pState; if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) return MZ_FALSE; // No zip64 support yet if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_writer_validate_archive_name(pArchive_name)) return MZ_FALSE; #ifndef MINIZ_NO_TIME { time_t cur_time; time(&cur_time); mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); } #endif // #ifndef MINIZ_NO_TIME archive_name_size = strlen(pArchive_name); if (archive_name_size > 0xFFFF) return MZ_FALSE; num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); // no zip64 support yet if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) return MZ_FALSE; if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) { // Set DOS Subdirectory attribute bit. ext_attributes |= 0x10; // Subdirectories cannot contain data. if ((buf_size) || (uncomp_size)) return MZ_FALSE; } // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) return MZ_FALSE; if ((!store_data_uncompressed) && (buf_size)) { if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) return MZ_FALSE; } if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } local_dir_header_ofs += num_alignment_padding_bytes; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); MZ_CLEAR_OBJ(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } cur_archive_file_ofs += archive_name_size; if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); uncomp_size = buf_size; if (uncomp_size <= 3) { level = 0; store_data_uncompressed = MZ_TRUE; } } if (store_data_uncompressed) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } cur_archive_file_ofs += buf_size; comp_size = buf_size; if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) method = MZ_DEFLATED; } else if (buf_size) { mz_zip_writer_add_state state; state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; method = MZ_DEFLATED; } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pComp = NULL; // no zip64 support yet if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) return MZ_FALSE; if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return MZ_FALSE; if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; MZ_FILE *pSrc_file = NULL; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; level = level_and_flags & 0xF; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) return MZ_FALSE; if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) return MZ_FALSE; if (!mz_zip_writer_validate_archive_name(pArchive_name)) return MZ_FALSE; archive_name_size = strlen(pArchive_name); if (archive_name_size > 0xFFFF) return MZ_FALSE; num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); // no zip64 support yet if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) return MZ_FALSE; pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); if (!pSrc_file) return MZ_FALSE; MZ_FSEEK64(pSrc_file, 0, SEEK_END); uncomp_size = MZ_FTELL64(pSrc_file); MZ_FSEEK64(pSrc_file, 0, SEEK_SET); if (uncomp_size > 0xFFFFFFFF) { // No zip64 support yet MZ_FCLOSE(pSrc_file); return MZ_FALSE; } if (uncomp_size <= 3) level = 0; if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) { MZ_FCLOSE(pSrc_file); return MZ_FALSE; } local_dir_header_ofs += num_alignment_padding_bytes; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); MZ_CLEAR_OBJ(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { MZ_FCLOSE(pSrc_file); return MZ_FALSE; } cur_archive_file_ofs += archive_name_size; if (uncomp_size) { mz_uint64 uncomp_remaining = uncomp_size; void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); if (!pRead_buf) { MZ_FCLOSE(pSrc_file); return MZ_FALSE; } if (!level) { while (uncomp_remaining) { mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); MZ_FCLOSE(pSrc_file); return MZ_FALSE; } uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); uncomp_remaining -= n; cur_archive_file_ofs += n; } comp_size = uncomp_size; } else { mz_bool result = MZ_FALSE; mz_zip_writer_add_state state; tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); if (!pComp) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); MZ_FCLOSE(pSrc_file); return MZ_FALSE; } state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); MZ_FCLOSE(pSrc_file); return MZ_FALSE; } for ( ; ; ) { size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); tdefl_status status; if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) break; uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); uncomp_remaining -= in_buf_size; status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); if (status == TDEFL_STATUS_DONE) { result = MZ_TRUE; break; } else if (status != TDEFL_STATUS_OKAY) break; } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); if (!result) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); MZ_FCLOSE(pSrc_file); return MZ_FALSE; } comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; method = MZ_DEFLATED; } pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); } MZ_FCLOSE(pSrc_file); pSrc_file = NULL; // no zip64 support yet if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) return MZ_FALSE; if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return MZ_FALSE; if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } #endif // #ifndef MINIZ_NO_STDIO mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index) { mz_uint n, bit_flags, num_alignment_padding_bytes; mz_uint64 comp_bytes_remaining, local_dir_header_ofs; mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; size_t orig_central_dir_size; mz_zip_internal_state *pState; void *pBuf; const mz_uint8 *pSrc_central_header; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) return MZ_FALSE; if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) return MZ_FALSE; pState = pZip->m_pState; num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); // no zip64 support yet if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) return MZ_FALSE; cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); cur_dst_file_ofs = pZip->m_archive_size; if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return MZ_FALSE; if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return MZ_FALSE; cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) return MZ_FALSE; cur_dst_file_ofs += num_alignment_padding_bytes; local_dir_header_ofs = cur_dst_file_ofs; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return MZ_FALSE; cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) return MZ_FALSE; while (comp_bytes_remaining) { n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return MZ_FALSE; } cur_src_file_ofs += n; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return MZ_FALSE; } cur_dst_file_ofs += n; comp_bytes_remaining -= n; } bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); if (bit_flags & 8) { // Copy data descriptor if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return MZ_FALSE; } n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return MZ_FALSE; } cur_src_file_ofs += n; #pragma unused(cur_src_file_ofs) cur_dst_file_ofs += n; } pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); // no zip64 support yet if (cur_dst_file_ofs > 0xFFFFFFFF) return MZ_FALSE; orig_central_dir_size = pState->m_central_dir.m_size; memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) return MZ_FALSE; n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return MZ_FALSE; } if (pState->m_central_dir.m_size > 0xFFFFFFFF) return MZ_FALSE; n = (mz_uint32)orig_central_dir_size; if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return MZ_FALSE; } pZip->m_total_files++; pZip->m_archive_size = cur_dst_file_ofs; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) { mz_zip_internal_state *pState; mz_uint64 central_dir_ofs, central_dir_size; mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) return MZ_FALSE; pState = pZip->m_pState; // no zip64 support yet if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) return MZ_FALSE; central_dir_ofs = 0; central_dir_size = 0; if (pZip->m_total_files) { // Write central directory central_dir_ofs = pZip->m_archive_size; central_dir_size = pState->m_central_dir.m_size; pZip->m_central_directory_file_ofs = central_dir_ofs; if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) return MZ_FALSE; pZip->m_archive_size += central_dir_size; } // Write end of central directory record MZ_CLEAR_OBJ(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) return MZ_FALSE; #ifndef MINIZ_NO_STDIO if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) return MZ_FALSE; #endif // #ifndef MINIZ_NO_STDIO pZip->m_archive_size += sizeof(hdr); pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize) { if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) return MZ_FALSE; if (pZip->m_pWrite != mz_zip_heap_write_func) return MZ_FALSE; if (!mz_zip_writer_finalize_archive(pZip)) return MZ_FALSE; *pBuf = pZip->m_pState->m_pMem; *pSize = pZip->m_pState->m_mem_size; pZip->m_pState->m_pMem = NULL; pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; return MZ_TRUE; } mz_bool mz_zip_writer_end(mz_zip_archive *pZip) { mz_zip_internal_state *pState; mz_bool status = MZ_TRUE; if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) return MZ_FALSE; pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { MZ_FCLOSE(pState->m_pFile); pState->m_pFile = NULL; } #endif // #ifndef MINIZ_NO_STDIO if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); pState->m_pMem = NULL; } pZip->m_pFree(pZip->m_pAlloc_opaque, pState); pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return status; } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { mz_bool status, created_new_archive = MZ_FALSE; mz_zip_archive zip_archive; struct MZ_FILE_STAT_STRUCT file_stat; MZ_CLEAR_OBJ(zip_archive); if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) return MZ_FALSE; if (!mz_zip_writer_validate_archive_name(pArchive_name)) return MZ_FALSE; if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) { // Create a new archive. if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) return MZ_FALSE; created_new_archive = MZ_TRUE; } else { // Append to an existing archive. if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) return MZ_FALSE; if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) { mz_zip_reader_end(&zip_archive); return MZ_FALSE; } } status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) if (!mz_zip_writer_finalize_archive(&zip_archive)) status = MZ_FALSE; if (!mz_zip_writer_end(&zip_archive)) status = MZ_FALSE; if ((!status) && (created_new_archive)) { // It's a new archive and something went wrong, so just delete it. int ignoredStatus = MZ_DELETE_FILE(pZip_filename); (void)ignoredStatus; } return status; } void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) { int file_index; mz_zip_archive zip_archive; void *p = NULL; if (pSize) *pSize = 0; if ((!pZip_filename) || (!pArchive_name)) return NULL; MZ_CLEAR_OBJ(zip_archive); if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) return NULL; if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); mz_zip_reader_end(&zip_archive); return p; } #endif // #ifndef MINIZ_NO_STDIO #endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS #endif // #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef __cplusplus } #endif #endif // MINIZ_HEADER_FILE_ONLY /* This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to */ dcm2niix-1.0.20181125/console/nifti1.h000066400000000000000000002073671337661136700170320ustar00rootroot00000000000000/** \file nifti1.h \brief Official definition of the nifti1 header. Written by Bob Cox, SSCC, NIMH. HISTORY: 29 Nov 2007 [rickr] - added DT_RGBA32 and NIFTI_TYPE_RGBA32 - added NIFTI_INTENT codes: TIME_SERIES, NODE_INDEX, RGB_VECTOR, RGBA_VECTOR, SHAPE */ #ifndef _NIFTI_HEADER_ #define _NIFTI_HEADER_ /***************************************************************************** ** This file defines the "NIFTI-1" header format. ** ** It is derived from 2 meetings at the NIH (31 Mar 2003 and ** ** 02 Sep 2003) of the Data Format Working Group (DFWG), ** ** chartered by the NIfTI (Neuroimaging Informatics Technology ** ** Initiative) at the National Institutes of Health (NIH). ** **--------------------------------------------------------------** ** Neither the National Institutes of Health (NIH), the DFWG, ** ** nor any of the members or employees of these institutions ** ** imply any warranty of usefulness of this material for any ** ** purpose, and do not assume any liability for damages, ** ** incidental or otherwise, caused by any use of this document. ** ** If these conditions are not acceptable, do not use this! ** **--------------------------------------------------------------** ** Author: Robert W Cox (NIMH, Bethesda) ** ** Advisors: John Ashburner (FIL, London), ** ** Stephen Smith (FMRIB, Oxford), ** ** Mark Jenkinson (FMRIB, Oxford) ** ******************************************************************************/ /*---------------------------------------------------------------------------*/ /* Note that the ANALYZE 7.5 file header (dbh.h) is (c) Copyright 1986-1995 Biomedical Imaging Resource Mayo Foundation Incorporation of components of dbh.h are by permission of the Mayo Foundation. Changes from the ANALYZE 7.5 file header in this file are released to the public domain, including the functional comments and any amusing asides. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /*! INTRODUCTION TO NIFTI-1: ------------------------ The twin (and somewhat conflicting) goals of this modified ANALYZE 7.5 format are: (a) To add information to the header that will be useful for functional neuroimaging data analysis and display. These additions include: - More basic data types. - Two affine transformations to specify voxel coordinates. - "Intent" codes and parameters to describe the meaning of the data. - Affine scaling of the stored data values to their "true" values. - Optional storage of the header and image data in one file (.nii). (b) To maintain compatibility with non-NIFTI-aware ANALYZE 7.5 compatible software (i.e., such a program should be able to do something useful with a NIFTI-1 dataset -- at least, with one stored in a traditional .img/.hdr file pair). Most of the unused fields in the ANALYZE 7.5 header have been taken, and some of the lesser-used fields have been co-opted for other purposes. Notably, most of the data_history substructure has been co-opted for other purposes, since the ANALYZE 7.5 format describes this substructure as "not required". NIFTI-1 FLAG (MAGIC STRINGS): ---------------------------- To flag such a struct as being conformant to the NIFTI-1 spec, the last 4 bytes of the header must be either the C String "ni1" or "n+1"; in hexadecimal, the 4 bytes 6E 69 31 00 or 6E 2B 31 00 (in any future version of this format, the '1' will be upgraded to '2', etc.). Normally, such a "magic number" or flag goes at the start of the file, but trying to avoid clobbering widely-used ANALYZE 7.5 fields led to putting this marker last. However, recall that "the last shall be first" (Matthew 20:16). If a NIFTI-aware program reads a header file that is NOT marked with a NIFTI magic string, then it should treat the header as an ANALYZE 7.5 structure. NIFTI-1 FILE STORAGE: -------------------- "ni1" means that the image data is stored in the ".img" file corresponding to the header file (starting at file offset 0). "n+1" means that the image data is stored in the same file as the header information. We recommend that the combined header+data filename suffix be ".nii". When the dataset is stored in one file, the first byte of image data is stored at byte location (int)vox_offset in this combined file. The minimum allowed value of vox_offset is 352; for compatibility with some software, vox_offset should be an integral multiple of 16. GRACE UNDER FIRE: ---------------- Most NIFTI-aware programs will only be able to handle a subset of the full range of datasets possible with this format. All NIFTI-aware programs should take care to check if an input dataset conforms to the program's needs and expectations (e.g., check datatype, intent_code, etc.). If the input dataset can't be handled by the program, the program should fail gracefully (e.g., print a useful warning; not crash). SAMPLE CODES: ------------ The associated files nifti1_io.h and nifti1_io.c provide a sample implementation in C of a set of functions to read, write, and manipulate NIFTI-1 files. The file nifti1_test.c is a sample program that uses the nifti1_io.c functions. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* HEADER STRUCT DECLARATION: ------------------------- In the comments below for each field, only NIFTI-1 specific requirements or changes from the ANALYZE 7.5 format are described. For convenience, the 348 byte header is described as a single struct, rather than as the ANALYZE 7.5 group of 3 substructs. Further comments about the interpretation of various elements of this header are after the data type definition itself. Fields that are marked as ++UNUSED++ have no particular interpretation in this standard. (Also see the UNUSED FIELDS comment section, far below.) The presumption below is that the various C types have particular sizes: sizeof(int) = sizeof(float) = 4 ; sizeof(short) = 2 -----------------------------------------------------------------------------*/ /*=================*/ #ifdef __cplusplus extern "C" { #endif /*=================*/ /*! \struct nifti_1_header \brief Data structure defining the fields in the nifti1 header. This binary header should be found at the beginning of a valid NIFTI-1 header file. */ /*************************/ /************************/ struct nifti_1_header { /* NIFTI-1 usage */ /* ANALYZE 7.5 field(s) */ //typedef struct __attribute__((packed)) /*************************/ /************************/ /*--- was header_key substruct ---*/ int sizeof_hdr; /*!< MUST be 348 */ /* int sizeof_hdr; */ char data_type[10]; /*!< ++UNUSED++ */ /* char data_type[10]; */ char db_name[18]; /*!< ++UNUSED++ */ /* char db_name[18]; */ int extents; /*!< ++UNUSED++ */ /* int extents; */ short session_error; /*!< ++UNUSED++ */ /* short session_error; */ char regular; /*!< ++UNUSED++ */ /* char regular; */ char dim_info; /*!< MRI slice ordering. */ /* char hkey_un0; */ /*--- was image_dimension substruct ---*/ short dim[8]; /*!< Data array dimensions.*/ /* short dim[8]; */ float intent_p1 ; /*!< 1st intent parameter. */ /* short unused8; */ /* short unused9; */ float intent_p2 ; /*!< 2nd intent parameter. */ /* short unused10; */ /* short unused11; */ float intent_p3 ; /*!< 3rd intent parameter. */ /* short unused12; */ /* short unused13; */ short intent_code ; /*!< NIFTI_INTENT_* code. */ /* short unused14; */ short datatype; /*!< Defines data type! */ /* short datatype; */ short bitpix; /*!< Number bits/voxel. */ /* short bitpix; */ short slice_start; /*!< First slice index. */ /* short dim_un0; */ float pixdim[8]; /*!< Grid spacings. */ /* float pixdim[8]; */ float vox_offset; /*!< Offset into .nii file */ /* float vox_offset; */ float scl_slope ; /*!< Data scaling: slope. */ /* float funused1; */ float scl_inter ; /*!< Data scaling: offset. */ /* float funused2; */ short slice_end; /*!< Last slice index. */ /* float funused3; */ char slice_code ; /*!< Slice timing order. */ char xyzt_units ; /*!< Units of pixdim[1..4] */ float cal_max; /*!< Max display intensity */ /* float cal_max; */ float cal_min; /*!< Min display intensity */ /* float cal_min; */ float slice_duration;/*!< Time for 1 slice. */ /* float compressed; */ float toffset; /*!< Time axis shift. */ /* float verified; */ int glmax; /*!< ++UNUSED++ */ /* int glmax; */ int glmin; /*!< ++UNUSED++ */ /* int glmin; */ /*--- was data_history substruct ---*/ char descrip[80]; /*!< any text you like. */ /* char descrip[80]; */ char aux_file[24]; /*!< auxiliary filename. */ /* char aux_file[24]; */ short qform_code ; /*!< NIFTI_XFORM_* code. */ /*-- all ANALYZE 7.5 ---*/ short sform_code ; /*!< NIFTI_XFORM_* code. */ /* fields below here */ /* are replaced */ float quatern_b ; /*!< Quaternion b param. */ float quatern_c ; /*!< Quaternion c param. */ float quatern_d ; /*!< Quaternion d param. */ float qoffset_x ; /*!< Quaternion x shift. */ float qoffset_y ; /*!< Quaternion y shift. */ float qoffset_z ; /*!< Quaternion z shift. */ float srow_x[4] ; /*!< 1st row affine transform. */ float srow_y[4] ; /*!< 2nd row affine transform. */ float srow_z[4] ; /*!< 3rd row affine transform. */ char intent_name[16];/*!< 'name' or meaning of data. */ char magic[4] ; /*!< MUST be "ni1\0" or "n+1\0". */ } ; /**** 348 bytes total ****/ typedef struct nifti_1_header nifti_1_header ; /*---------------------------------------------------------------------------*/ /* HEADER EXTENSIONS: ----------------- After the end of the 348 byte header (e.g., after the magic field), the next 4 bytes are a char array field named "extension". By default, all 4 bytes of this array should be set to zero. In a .nii file, these 4 bytes will always be present, since the earliest start point for the image data is byte #352. In a separate .hdr file, these bytes may or may not be present. If not present (i.e., if the length of the .hdr file is 348 bytes), then a NIfTI-1 compliant program should use the default value of extension={0,0,0,0}. The first byte (extension[0]) is the only value of this array that is specified at present. The other 3 bytes are reserved for future use. If extension[0] is nonzero, it indicates that extended header information is present in the bytes following the extension array. In a .nii file, this extended header data is before the image data (and vox_offset must be set correctly to allow for this). In a .hdr file, this extended data follows extension and proceeds (potentially) to the end of the file. The format of extended header data is weakly specified. Each extension must be an integer multiple of 16 bytes long. The first 8 bytes of each extension comprise 2 integers: int esize , ecode ; These values may need to be byte-swapped, as indicated by dim[0] for the rest of the header. * esize is the number of bytes that form the extended header data + esize must be a positive integral multiple of 16 + this length includes the 8 bytes of esize and ecode themselves * ecode is a non-negative integer that indicates the format of the extended header data that follows + different ecode values are assigned to different developer groups + at present, the "registered" values for code are = 0 = unknown private format (not recommended!) = 2 = DICOM format (i.e., attribute tags and values) = 4 = AFNI group (i.e., ASCII XML-ish elements) In the interests of interoperability (a primary rationale for NIfTI), groups developing software that uses this extension mechanism are encouraged to document and publicize the format of their extensions. To this end, the NIfTI DFWG will assign even numbered codes upon request to groups submitting at least rudimentary documentation for the format of their extension; at present, the contact is mailto:rwcox@nih.gov. The assigned codes and documentation will be posted on the NIfTI website. All odd values of ecode (and 0) will remain unassigned; at least, until the even ones are used up, when we get to 2,147,483,646. Note that the other contents of the extended header data section are totally unspecified by the NIfTI-1 standard. In particular, if binary data is stored in such a section, its byte order is not necessarily the same as that given by examining dim[0]; it is incumbent on the programs dealing with such data to determine the byte order of binary extended header data. Multiple extended header sections are allowed, each starting with an esize,ecode value pair. The first esize value, as described above, is at bytes #352-355 in the .hdr or .nii file (files start at byte #0). If this value is positive, then the second (esize2) will be found starting at byte #352+esize1 , the third (esize3) at byte #352+esize1+esize2, et cetera. Of course, in a .nii file, the value of vox_offset must be compatible with these extensions. If a malformed file indicates that an extended header data section would run past vox_offset, then the entire extended header section should be ignored. In a .hdr file, if an extended header data section would run past the end-of-file, that extended header data should also be ignored. With the above scheme, a program can successively examine the esize and ecode values, and skip over each extended header section if the program doesn't know how to interpret the data within. Of course, any program can simply ignore all extended header sections simply by jumping straight to the image data using vox_offset. -----------------------------------------------------------------------------*/ /*! \struct nifti1_extender \brief This structure represents a 4-byte string that should follow the binary nifti_1_header data in a NIFTI-1 header file. If the char values are {1,0,0,0}, the file is expected to contain extensions, values of {0,0,0,0} imply the file does not contain extensions. Other sequences of values are not currently defined. */ struct nifti1_extender { char extension[4] ; } ; typedef struct nifti1_extender nifti1_extender ; /*! \struct nifti1_extension \brief Data structure defining the fields of a header extension. */ struct nifti1_extension { int esize ; /*!< size of extension, in bytes (must be multiple of 16) */ int ecode ; /*!< extension code, one of the NIFTI_ECODE_ values */ char * edata ; /*!< raw data, with no byte swapping (length is esize-8) */ } ; typedef struct nifti1_extension nifti1_extension ; /*---------------------------------------------------------------------------*/ /* DATA DIMENSIONALITY (as in ANALYZE 7.5): --------------------------------------- dim[0] = number of dimensions; - if dim[0] is outside range 1..7, then the header information needs to be byte swapped appropriately - ANALYZE supports dim[0] up to 7, but NIFTI-1 reserves dimensions 1,2,3 for space (x,y,z), 4 for time (t), and 5,6,7 for anything else needed. dim[i] = length of dimension #i, for i=1..dim[0] (must be positive) - also see the discussion of intent_code, far below pixdim[i] = voxel width along dimension #i, i=1..dim[0] (positive) - cf. ORIENTATION section below for use of pixdim[0] - the units of pixdim can be specified with the xyzt_units field (also described far below). Number of bits per voxel value is in bitpix, which MUST correspond with the datatype field. The total number of bytes in the image data is dim[1] * ... * dim[dim[0]] * bitpix / 8 In NIFTI-1 files, dimensions 1,2,3 are for space, dimension 4 is for time, and dimension 5 is for storing multiple values at each spatiotemporal voxel. Some examples: - A typical whole-brain FMRI experiment's time series: - dim[0] = 4 - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC - dim[3] = 20 pixdim[3] = 5.0 - dim[4] = 120 pixdim[4] = 2.0 - A typical T1-weighted anatomical volume: - dim[0] = 3 - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM - dim[2] = 256 pixdim[2] = 1.0 - dim[3] = 128 pixdim[3] = 1.1 - A single slice EPI time series: - dim[0] = 4 - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC - dim[3] = 1 pixdim[3] = 5.0 - dim[4] = 1200 pixdim[4] = 0.2 - A 3-vector stored at each point in a 3D volume: - dim[0] = 5 - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM - dim[2] = 256 pixdim[2] = 1.0 - dim[3] = 128 pixdim[3] = 1.1 - dim[4] = 1 pixdim[4] = 0.0 - dim[5] = 3 intent_code = NIFTI_INTENT_VECTOR - A single time series with a 3x3 matrix at each point: - dim[0] = 5 - dim[1] = 1 xyzt_units = NIFTI_UNITS_SEC - dim[2] = 1 - dim[3] = 1 - dim[4] = 1200 pixdim[4] = 0.2 - dim[5] = 9 intent_code = NIFTI_INTENT_GENMATRIX - intent_p1 = intent_p2 = 3.0 (indicates matrix dimensions) -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* DATA STORAGE: ------------ If the magic field is "n+1", then the voxel data is stored in the same file as the header. In this case, the voxel data starts at offset (int)vox_offset into the header file. Thus, vox_offset=352.0 means that the data starts immediately after the NIFTI-1 header. If vox_offset is greater than 352, the NIFTI-1 format does not say much about the contents of the dataset file between the end of the header and the start of the data. FILES: ----- If the magic field is "ni1", then the voxel data is stored in the associated ".img" file, starting at offset 0 (i.e., vox_offset is not used in this case, and should be set to 0.0). When storing NIFTI-1 datasets in pairs of files, it is customary to name the files in the pattern "name.hdr" and "name.img", as in ANALYZE 7.5. When storing in a single file ("n+1"), the file name should be in the form "name.nii" (the ".nft" and ".nif" suffixes are already taken; cf. http://www.icdatamaster.com/n.html ). BYTE ORDERING: ------------- The byte order of the data arrays is presumed to be the same as the byte order of the header (which is determined by examining dim[0]). Floating point types are presumed to be stored in IEEE-754 format. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* DETAILS ABOUT vox_offset: ------------------------ In a .nii file, the vox_offset field value is interpreted as the start location of the image data bytes in that file. In a .hdr/.img file pair, the vox_offset field value is the start location of the image data bytes in the .img file. * If vox_offset is less than 352 in a .nii file, it is equivalent to 352 (i.e., image data never starts before byte #352 in a .nii file). * The default value for vox_offset in a .nii file is 352. * In a .hdr file, the default value for vox_offset is 0. * vox_offset should be an integer multiple of 16; otherwise, some programs may not work properly (e.g., SPM). This is to allow memory-mapped input to be properly byte-aligned. Note that since vox_offset is an IEEE-754 32 bit float (for compatibility with the ANALYZE-7.5 format), it effectively has a 24 bit mantissa. All integers from 0 to 2^24 can be represented exactly in this format, but not all larger integers are exactly storable as IEEE-754 32 bit floats. However, unless you plan to have vox_offset be potentially larger than 16 MB, this should not be an issue. (Actually, any integral multiple of 16 up to 2^27 can be represented exactly in this format, which allows for up to 128 MB of random information before the image data. If that isn't enough, then perhaps this format isn't right for you.) In a .img file (i.e., image data stored separately from the NIfTI-1 header), data bytes between #0 and #vox_offset-1 (inclusive) are completely undefined and unregulated by the NIfTI-1 standard. One potential use of having vox_offset > 0 in the .hdr/.img file pair storage method is to make the .img file be a copy of (or link to) a pre-existing image file in some other format, such as DICOM; then vox_offset would be set to the offset of the image data in this file. (It may not be possible to follow the "multiple-of-16 rule" with an arbitrary external file; using the NIfTI-1 format in such a case may lead to a file that is incompatible with software that relies on vox_offset being a multiple of 16.) In a .nii file, data bytes between #348 and #vox_offset-1 (inclusive) may be used to store user-defined extra information; similarly, in a .hdr file, any data bytes after byte #347 are available for user-defined extra information. The (very weak) regulation of this extra header data is described elsewhere. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* DATA SCALING: ------------ If the scl_slope field is nonzero, then each voxel value in the dataset should be scaled as y = scl_slope * x + scl_inter where x = voxel value stored y = "true" voxel value Normally, we would expect this scaling to be used to store "true" floating values in a smaller integer datatype, but that is not required. That is, it is legal to use scaling even if the datatype is a float type (crazy, perhaps, but legal). - However, the scaling is to be ignored if datatype is DT_RGB24. - If datatype is a complex type, then the scaling is to be applied to both the real and imaginary parts. The cal_min and cal_max fields (if nonzero) are used for mapping (possibly scaled) dataset values to display colors: - Minimum display intensity (black) corresponds to dataset value cal_min. - Maximum display intensity (white) corresponds to dataset value cal_max. - Dataset values below cal_min should display as black also, and values above cal_max as white. - Colors "black" and "white", of course, may refer to any scalar display scheme (e.g., a color lookup table specified via aux_file). - cal_min and cal_max only make sense when applied to scalar-valued datasets (i.e., dim[0] < 5 or dim[5] = 1). -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* TYPE OF DATA (acceptable values for datatype field): --------------------------------------------------- Values of datatype smaller than 256 are ANALYZE 7.5 compatible. Larger values are NIFTI-1 additions. These are all multiples of 256, so that no bits below position 8 are set in datatype. But there is no need to use only powers-of-2, as the original ANALYZE 7.5 datatype codes do. The additional codes are intended to include a complete list of basic scalar types, including signed and unsigned integers from 8 to 64 bits, floats from 32 to 128 bits, and complex (float pairs) from 64 to 256 bits. Note that most programs will support only a few of these datatypes! A NIFTI-1 program should fail gracefully (e.g., print a warning message) when it encounters a dataset with a type it doesn't like. -----------------------------------------------------------------------------*/ //#undef DT_UNKNOWN /* defined in dirent.h on some Unix systems */ //https://www.mail-archive.com/fltk-bugs@easysw.com/msg05850.html /*! \defgroup NIFTI1_DATATYPES \brief nifti1 datatype codes @{ */ /*--- the original ANALYZE 7.5 type codes ---*/ #define DT_NONE 0 #define DT_UNKNOWN_DT 0 //3/3/2014 CR modified to avoid conflict /usr/include/dirent #define DT_BINARY 1 /* binary (1 bit/voxel) */ #define DT_UNSIGNED_CHAR 2 /* unsigned char (8 bits/voxel) */ #define DT_SIGNED_SHORT 4 /* signed short (16 bits/voxel) */ #define DT_SIGNED_INT 8 /* signed int (32 bits/voxel) */ #define DT_FLOAT 16 /* float (32 bits/voxel) */ #define DT_COMPLEX 32 /* complex (64 bits/voxel) */ #define DT_DOUBLE 64 /* double (64 bits/voxel) */ #define DT_RGB 128 /* RGB triple (24 bits/voxel) */ #define DT_ALL 255 /* not very useful (?) */ /*----- another set of names for the same ---*/ #define DT_UINT8 2 #define DT_INT16 4 #define DT_INT32 8 #define DT_FLOAT32 16 #define DT_COMPLEX64 32 #define DT_FLOAT64 64 #define DT_RGB24 128 /*------------------- new codes for NIFTI ---*/ #define DT_INT8 256 /* signed char (8 bits) */ #define DT_UINT16 512 /* unsigned short (16 bits) */ #define DT_UINT32 768 /* unsigned int (32 bits) */ #define DT_INT64 1024 /* long long (64 bits) */ #define DT_UINT64 1280 /* unsigned long long (64 bits) */ #define DT_FLOAT128 1536 /* long double (128 bits) */ #define DT_COMPLEX128 1792 /* double pair (128 bits) */ #define DT_COMPLEX256 2048 /* long double pair (256 bits) */ #define DT_RGBA32 2304 /* 4 byte RGBA (32 bits/voxel) */ /* @} */ /*------- aliases for all the above codes ---*/ /*! \defgroup NIFTI1_DATATYPE_ALIASES \brief aliases for the nifti1 datatype codes @{ */ /*! unsigned char. */ #define NIFTI_TYPE_UINT8 2 /*! signed short. */ #define NIFTI_TYPE_INT16 4 /*! signed int. */ #define NIFTI_TYPE_INT32 8 /*! 32 bit float. */ #define NIFTI_TYPE_FLOAT32 16 /*! 64 bit complex = 2 32 bit floats. */ #define NIFTI_TYPE_COMPLEX64 32 /*! 64 bit float = double. */ #define NIFTI_TYPE_FLOAT64 64 /*! 3 8 bit bytes. */ #define NIFTI_TYPE_RGB24 128 /*! signed char. */ #define NIFTI_TYPE_INT8 256 /*! unsigned short. */ #define NIFTI_TYPE_UINT16 512 /*! unsigned int. */ #define NIFTI_TYPE_UINT32 768 /*! signed long long. */ #define NIFTI_TYPE_INT64 1024 /*! unsigned long long. */ #define NIFTI_TYPE_UINT64 1280 /*! 128 bit float = long double. */ #define NIFTI_TYPE_FLOAT128 1536 /*! 128 bit complex = 2 64 bit floats. */ #define NIFTI_TYPE_COMPLEX128 1792 /*! 256 bit complex = 2 128 bit floats */ #define NIFTI_TYPE_COMPLEX256 2048 /*! 4 8 bit bytes. */ #define NIFTI_TYPE_RGBA32 2304 /* @} */ /*-------- sample typedefs for complicated types ---*/ #if 0 typedef struct { float r,i; } complex_float ; typedef struct { double r,i; } complex_double ; typedef struct { long double r,i; } complex_longdouble ; typedef struct { unsigned char r,g,b; } rgb_byte ; #endif /*---------------------------------------------------------------------------*/ /* INTERPRETATION OF VOXEL DATA: ---------------------------- The intent_code field can be used to indicate that the voxel data has some particular meaning. In particular, a large number of codes is given to indicate that the the voxel data should be interpreted as being drawn from a given probability distribution. VECTOR-VALUED DATASETS: ---------------------- The 5th dimension of the dataset, if present (i.e., dim[0]=5 and dim[5] > 1), contains multiple values (e.g., a vector) to be stored at each spatiotemporal location. For example, the header values - dim[0] = 5 - dim[1] = 64 - dim[2] = 64 - dim[3] = 20 - dim[4] = 1 (indicates no time axis) - dim[5] = 3 - datatype = DT_FLOAT - intent_code = NIFTI_INTENT_VECTOR mean that this dataset should be interpreted as a 3D volume (64x64x20), with a 3-vector of floats defined at each point in the 3D grid. A program reading a dataset with a 5th dimension may want to reformat the image data to store each voxels' set of values together in a struct or array. This programming detail, however, is beyond the scope of the NIFTI-1 file specification! Uses of dimensions 6 and 7 are also not specified here. STATISTICAL PARAMETRIC DATASETS (i.e., SPMs): -------------------------------------------- Values of intent_code from NIFTI_FIRST_STATCODE to NIFTI_LAST_STATCODE (inclusive) indicate that the numbers in the dataset should be interpreted as being drawn from a given distribution. Most such distributions have auxiliary parameters (e.g., NIFTI_INTENT_TTEST has 1 DOF parameter). If the dataset DOES NOT have a 5th dimension, then the auxiliary parameters are the same for each voxel, and are given in header fields intent_p1, intent_p2, and intent_p3. If the dataset DOES have a 5th dimension, then the auxiliary parameters are different for each voxel. For example, the header values - dim[0] = 5 - dim[1] = 128 - dim[2] = 128 - dim[3] = 1 (indicates a single slice) - dim[4] = 1 (indicates no time axis) - dim[5] = 2 - datatype = DT_FLOAT - intent_code = NIFTI_INTENT_TTEST mean that this is a 2D dataset (128x128) of t-statistics, with the t-statistic being in the first "plane" of data and the degrees-of-freedom parameter being in the second "plane" of data. If the dataset 5th dimension is used to store the voxel-wise statistical parameters, then dim[5] must be 1 plus the number of parameters required by that distribution (e.g., intent_code=NIFTI_INTENT_TTEST implies dim[5] must be 2, as in the example just above). Note: intent_code values 2..10 are compatible with AFNI 1.5x (which is why there is no code with value=1, which is obsolescent in AFNI). OTHER INTENTIONS: ---------------- The purpose of the intent_* fields is to help interpret the values stored in the dataset. Some non-statistical values for intent_code and conventions are provided for storing other complex data types. The intent_name field provides space for a 15 character (plus 0 byte) 'name' string for the type of data stored. Examples: - intent_code = NIFTI_INTENT_ESTIMATE; intent_name = "T1"; could be used to signify that the voxel values are estimates of the NMR parameter T1. - intent_code = NIFTI_INTENT_TTEST; intent_name = "House"; could be used to signify that the voxel values are t-statistics for the significance of 'activation' response to a House stimulus. - intent_code = NIFTI_INTENT_DISPVECT; intent_name = "ToMNI152"; could be used to signify that the voxel values are a displacement vector that transforms each voxel (x,y,z) location to the corresponding location in the MNI152 standard brain. - intent_code = NIFTI_INTENT_SYMMATRIX; intent_name = "DTI"; could be used to signify that the voxel values comprise a diffusion tensor image. If no data name is implied or needed, intent_name[0] should be set to 0. -----------------------------------------------------------------------------*/ /*! default: no intention is indicated in the header. */ #define NIFTI_INTENT_NONE 0 /*-------- These codes are for probability distributions ---------------*/ /* Most distributions have a number of parameters, below denoted by p1, p2, and p3, and stored in - intent_p1, intent_p2, intent_p3 if dataset doesn't have 5th dimension - image data array if dataset does have 5th dimension Functions to compute with many of the distributions below can be found in the CDF library from U Texas. Formulas for and discussions of these distributions can be found in the following books: [U] Univariate Discrete Distributions, NL Johnson, S Kotz, AW Kemp. [C1] Continuous Univariate Distributions, vol. 1, NL Johnson, S Kotz, N Balakrishnan. [C2] Continuous Univariate Distributions, vol. 2, NL Johnson, S Kotz, N Balakrishnan. */ /*----------------------------------------------------------------------*/ /*! [C2, chap 32] Correlation coefficient R (1 param): p1 = degrees of freedom R/sqrt(1-R*R) is t-distributed with p1 DOF. */ /*! \defgroup NIFTI1_INTENT_CODES \brief nifti1 intent codes, to describe intended meaning of dataset contents @{ */ #define NIFTI_INTENT_CORREL 2 /*! [C2, chap 28] Student t statistic (1 param): p1 = DOF. */ #define NIFTI_INTENT_TTEST 3 /*! [C2, chap 27] Fisher F statistic (2 params): p1 = numerator DOF, p2 = denominator DOF. */ #define NIFTI_INTENT_FTEST 4 /*! [C1, chap 13] Standard normal (0 params): Density = N(0,1). */ #define NIFTI_INTENT_ZSCORE 5 /*! [C1, chap 18] Chi-squared (1 param): p1 = DOF. Density(x) proportional to exp(-x/2) * x^(p1/2-1). */ #define NIFTI_INTENT_CHISQ 6 /*! [C2, chap 25] Beta distribution (2 params): p1=a, p2=b. Density(x) proportional to x^(a-1) * (1-x)^(b-1). */ #define NIFTI_INTENT_BETA 7 /*! [U, chap 3] Binomial distribution (2 params): p1 = number of trials, p2 = probability per trial. Prob(x) = (p1 choose x) * p2^x * (1-p2)^(p1-x), for x=0,1,...,p1. */ #define NIFTI_INTENT_BINOM 8 /*! [C1, chap 17] Gamma distribution (2 params): p1 = shape, p2 = scale. Density(x) proportional to x^(p1-1) * exp(-p2*x). */ #define NIFTI_INTENT_GAMMA 9 /*! [U, chap 4] Poisson distribution (1 param): p1 = mean. Prob(x) = exp(-p1) * p1^x / x! , for x=0,1,2,.... */ #define NIFTI_INTENT_POISSON 10 /*! [C1, chap 13] Normal distribution (2 params): p1 = mean, p2 = standard deviation. */ #define NIFTI_INTENT_NORMAL 11 /*! [C2, chap 30] Noncentral F statistic (3 params): p1 = numerator DOF, p2 = denominator DOF, p3 = numerator noncentrality parameter. */ #define NIFTI_INTENT_FTEST_NONC 12 /*! [C2, chap 29] Noncentral chi-squared statistic (2 params): p1 = DOF, p2 = noncentrality parameter. */ #define NIFTI_INTENT_CHISQ_NONC 13 /*! [C2, chap 23] Logistic distribution (2 params): p1 = location, p2 = scale. Density(x) proportional to sech^2((x-p1)/(2*p2)). */ #define NIFTI_INTENT_LOGISTIC 14 /*! [C2, chap 24] Laplace distribution (2 params): p1 = location, p2 = scale. Density(x) proportional to exp(-abs(x-p1)/p2). */ #define NIFTI_INTENT_LAPLACE 15 /*! [C2, chap 26] Uniform distribution: p1 = lower end, p2 = upper end. */ #define NIFTI_INTENT_UNIFORM 16 /*! [C2, chap 31] Noncentral t statistic (2 params): p1 = DOF, p2 = noncentrality parameter. */ #define NIFTI_INTENT_TTEST_NONC 17 /*! [C1, chap 21] Weibull distribution (3 params): p1 = location, p2 = scale, p3 = power. Density(x) proportional to ((x-p1)/p2)^(p3-1) * exp(-((x-p1)/p2)^p3) for x > p1. */ #define NIFTI_INTENT_WEIBULL 18 /*! [C1, chap 18] Chi distribution (1 param): p1 = DOF. Density(x) proportional to x^(p1-1) * exp(-x^2/2) for x > 0. p1 = 1 = 'half normal' distribution p1 = 2 = Rayleigh distribution p1 = 3 = Maxwell-Boltzmann distribution. */ #define NIFTI_INTENT_CHI 19 /*! [C1, chap 15] Inverse Gaussian (2 params): p1 = mu, p2 = lambda Density(x) proportional to exp(-p2*(x-p1)^2/(2*p1^2*x)) / x^3 for x > 0. */ #define NIFTI_INTENT_INVGAUSS 20 /*! [C2, chap 22] Extreme value type I (2 params): p1 = location, p2 = scale cdf(x) = exp(-exp(-(x-p1)/p2)). */ #define NIFTI_INTENT_EXTVAL 21 /*! Data is a 'p-value' (no params). */ #define NIFTI_INTENT_PVAL 22 /*! Data is ln(p-value) (no params). To be safe, a program should compute p = exp(-abs(this_value)). The nifti_stats.c library returns this_value as positive, so that this_value = -log(p). */ #define NIFTI_INTENT_LOGPVAL 23 /*! Data is log10(p-value) (no params). To be safe, a program should compute p = pow(10.,-abs(this_value)). The nifti_stats.c library returns this_value as positive, so that this_value = -log10(p). */ #define NIFTI_INTENT_LOG10PVAL 24 /*! Smallest intent_code that indicates a statistic. */ #define NIFTI_FIRST_STATCODE 2 /*! Largest intent_code that indicates a statistic. */ #define NIFTI_LAST_STATCODE 24 /*---------- these values for intent_code aren't for statistics ----------*/ /*! To signify that the value at each voxel is an estimate of some parameter, set intent_code = NIFTI_INTENT_ESTIMATE. The name of the parameter may be stored in intent_name. */ #define NIFTI_INTENT_ESTIMATE 1001 /*! To signify that the value at each voxel is an index into some set of labels, set intent_code = NIFTI_INTENT_LABEL. The filename with the labels may stored in aux_file. */ #define NIFTI_INTENT_LABEL 1002 /*! To signify that the value at each voxel is an index into the NeuroNames labels set, set intent_code = NIFTI_INTENT_NEURONAME. */ #define NIFTI_INTENT_NEURONAME 1003 /*! To store an M x N matrix at each voxel: - dataset must have a 5th dimension (dim[0]=5 and dim[5]>1) - intent_code must be NIFTI_INTENT_GENMATRIX - dim[5] must be M*N - intent_p1 must be M (in float format) - intent_p2 must be N (ditto) - the matrix values A[i][[j] are stored in row-order: - A[0][0] A[0][1] ... A[0][N-1] - A[1][0] A[1][1] ... A[1][N-1] - etc., until - A[M-1][0] A[M-1][1] ... A[M-1][N-1] */ #define NIFTI_INTENT_GENMATRIX 1004 /*! To store an NxN symmetric matrix at each voxel: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_SYMMATRIX - dim[5] must be N*(N+1)/2 - intent_p1 must be N (in float format) - the matrix values A[i][[j] are stored in row-order: - A[0][0] - A[1][0] A[1][1] - A[2][0] A[2][1] A[2][2] - etc.: row-by-row */ #define NIFTI_INTENT_SYMMATRIX 1005 /*! To signify that the vector value at each voxel is to be taken as a displacement field or vector: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_DISPVECT - dim[5] must be the dimensionality of the displacment vector (e.g., 3 for spatial displacement, 2 for in-plane) */ #define NIFTI_INTENT_DISPVECT 1006 /* specifically for displacements */ #define NIFTI_INTENT_VECTOR 1007 /* for any other type of vector */ /*! To signify that the vector value at each voxel is really a spatial coordinate (e.g., the vertices or nodes of a surface mesh): - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_POINTSET - dim[0] = 5 - dim[1] = number of points - dim[2] = dim[3] = dim[4] = 1 - dim[5] must be the dimensionality of space (e.g., 3 => 3D space). - intent_name may describe the object these points come from (e.g., "pial", "gray/white" , "EEG", "MEG"). */ #define NIFTI_INTENT_POINTSET 1008 /*! To signify that the vector value at each voxel is really a triple of indexes (e.g., forming a triangle) from a pointset dataset: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_TRIANGLE - dim[0] = 5 - dim[1] = number of triangles - dim[2] = dim[3] = dim[4] = 1 - dim[5] = 3 - datatype should be an integer type (preferably DT_INT32) - the data values are indexes (0,1,...) into a pointset dataset. */ #define NIFTI_INTENT_TRIANGLE 1009 /*! To signify that the vector value at each voxel is a quaternion: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_QUATERNION - dim[0] = 5 - dim[5] = 4 - datatype should be a floating point type */ #define NIFTI_INTENT_QUATERNION 1010 /*! Dimensionless value - no params - although, as in _ESTIMATE the name of the parameter may be stored in intent_name. */ #define NIFTI_INTENT_DIMLESS 1011 /*---------- these values apply to GIFTI datasets ----------*/ /*! To signify that the value at each location is from a time series. */ #define NIFTI_INTENT_TIME_SERIES 2001 /*! To signify that the value at each location is a node index, from a complete surface dataset. */ #define NIFTI_INTENT_NODE_INDEX 2002 /*! To signify that the vector value at each location is an RGB triplet, of whatever type. - dataset must have a 5th dimension - dim[0] = 5 - dim[1] = number of nodes - dim[2] = dim[3] = dim[4] = 1 - dim[5] = 3 */ #define NIFTI_INTENT_RGB_VECTOR 2003 /*! To signify that the vector value at each location is a 4 valued RGBA vector, of whatever type. - dataset must have a 5th dimension - dim[0] = 5 - dim[1] = number of nodes - dim[2] = dim[3] = dim[4] = 1 - dim[5] = 4 */ #define NIFTI_INTENT_RGBA_VECTOR 2004 /*! To signify that the value at each location is a shape value, such as the curvature. */ #define NIFTI_INTENT_SHAPE 2005 /* @} */ /*---------------------------------------------------------------------------*/ /* 3D IMAGE (VOLUME) ORIENTATION AND LOCATION IN SPACE: --------------------------------------------------- There are 3 different methods by which continuous coordinates can attached to voxels. The discussion below emphasizes 3D volumes, and the continuous coordinates are referred to as (x,y,z). The voxel index coordinates (i.e., the array indexes) are referred to as (i,j,k), with valid ranges: i = 0 .. dim[1]-1 j = 0 .. dim[2]-1 (if dim[0] >= 2) k = 0 .. dim[3]-1 (if dim[0] >= 3) The (x,y,z) coordinates refer to the CENTER of a voxel. In methods 2 and 3, the (x,y,z) axes refer to a subject-based coordinate system, with +x = Right +y = Anterior +z = Superior. This is a right-handed coordinate system. However, the exact direction these axes point with respect to the subject depends on qform_code (Method 2) and sform_code (Method 3). N.B.: The i index varies most rapidly, j index next, k index slowest. Thus, voxel (i,j,k) is stored starting at location (i + j*dim[1] + k*dim[1]*dim[2]) * (bitpix/8) into the dataset array. N.B.: The ANALYZE 7.5 coordinate system is +x = Left +y = Anterior +z = Superior which is a left-handed coordinate system. This backwardness is too difficult to tolerate, so this NIFTI-1 standard specifies the coordinate order which is most common in functional neuroimaging. N.B.: The 3 methods below all give the locations of the voxel centers in the (x,y,z) coordinate system. In many cases, programs will wish to display image data on some other grid. In such a case, the program will need to convert its desired (x,y,z) values into (i,j,k) values in order to extract (or interpolate) the image data. This operation would be done with the inverse transformation to those described below. N.B.: Method 2 uses a factor 'qfac' which is either -1 or 1; qfac is stored in the otherwise unused pixdim[0]. If pixdim[0]=0.0 (which should not occur), we take qfac=1. Of course, pixdim[0] is only used when reading a NIFTI-1 header, not when reading an ANALYZE 7.5 header. N.B.: The units of (x,y,z) can be specified using the xyzt_units field. METHOD 1 (the "old" way, used only when qform_code = 0): ------------------------------------------------------- The coordinate mapping from (i,j,k) to (x,y,z) is the ANALYZE 7.5 way. This is a simple scaling relationship: x = pixdim[1] * i y = pixdim[2] * j z = pixdim[3] * k No particular spatial orientation is attached to these (x,y,z) coordinates. (NIFTI-1 does not have the ANALYZE 7.5 orient field, which is not general and is often not set properly.) This method is not recommended, and is present mainly for compatibility with ANALYZE 7.5 files. METHOD 2 (used when qform_code > 0, which should be the "normal" case): --------------------------------------------------------------------- The (x,y,z) coordinates are given by the pixdim[] scales, a rotation matrix, and a shift. This method is intended to represent "scanner-anatomical" coordinates, which are often embedded in the image header (e.g., DICOM fields (0020,0032), (0020,0037), (0028,0030), and (0018,0050)), and represent the nominal orientation and location of the data. This method can also be used to represent "aligned" coordinates, which would typically result from some post-acquisition alignment of the volume to a standard orientation (e.g., the same subject on another day, or a rigid rotation to true anatomical orientation from the tilted position of the subject in the scanner). The formula for (x,y,z) in terms of header parameters and (i,j,k) is: [ x ] [ R11 R12 R13 ] [ pixdim[1] * i ] [ qoffset_x ] [ y ] = [ R21 R22 R23 ] [ pixdim[2] * j ] + [ qoffset_y ] [ z ] [ R31 R32 R33 ] [ qfac * pixdim[3] * k ] [ qoffset_z ] The qoffset_* shifts are in the NIFTI-1 header. Note that the center of the (i,j,k)=(0,0,0) voxel (first value in the dataset array) is just (x,y,z)=(qoffset_x,qoffset_y,qoffset_z). The rotation matrix R is calculated from the quatern_* parameters. This calculation is described below. The scaling factor qfac is either 1 or -1. The rotation matrix R defined by the quaternion parameters is "proper" (has determinant 1). This may not fit the needs of the data; for example, if the image grid is i increases from Left-to-Right j increases from Anterior-to-Posterior k increases from Inferior-to-Superior Then (i,j,k) is a left-handed triple. In this example, if qfac=1, the R matrix would have to be [ 1 0 0 ] [ 0 -1 0 ] which is "improper" (determinant = -1). [ 0 0 1 ] If we set qfac=-1, then the R matrix would be [ 1 0 0 ] [ 0 -1 0 ] which is proper. [ 0 0 -1 ] This R matrix is represented by quaternion [a,b,c,d] = [0,1,0,0] (which encodes a 180 degree rotation about the x-axis). METHOD 3 (used when sform_code > 0): ----------------------------------- The (x,y,z) coordinates are given by a general affine transformation of the (i,j,k) indexes: x = srow_x[0] * i + srow_x[1] * j + srow_x[2] * k + srow_x[3] y = srow_y[0] * i + srow_y[1] * j + srow_y[2] * k + srow_y[3] z = srow_z[0] * i + srow_z[1] * j + srow_z[2] * k + srow_z[3] The srow_* vectors are in the NIFTI_1 header. Note that no use is made of pixdim[] in this method. WHY 3 METHODS? -------------- Method 1 is provided only for backwards compatibility. The intention is that Method 2 (qform_code > 0) represents the nominal voxel locations as reported by the scanner, or as rotated to some fiducial orientation and location. Method 3, if present (sform_code > 0), is to be used to give the location of the voxels in some standard space. The sform_code indicates which standard space is present. Both methods 2 and 3 can be present, and be useful in different contexts (method 2 for displaying the data on its original grid; method 3 for displaying it on a standard grid). In this scheme, a dataset would originally be set up so that the Method 2 coordinates represent what the scanner reported. Later, a registration to some standard space can be computed and inserted in the header. Image display software can use either transform, depending on its purposes and needs. In Method 2, the origin of coordinates would generally be whatever the scanner origin is; for example, in MRI, (0,0,0) is the center of the gradient coil. In Method 3, the origin of coordinates would depend on the value of sform_code; for example, for the Talairach coordinate system, (0,0,0) corresponds to the Anterior Commissure. QUATERNION REPRESENTATION OF ROTATION MATRIX (METHOD 2) ------------------------------------------------------- The orientation of the (x,y,z) axes relative to the (i,j,k) axes in 3D space is specified using a unit quaternion [a,b,c,d], where a*a+b*b+c*c+d*d=1. The (b,c,d) values are all that is needed, since we require that a = sqrt(1.0-(b*b+c*c+d*d)) be nonnegative. The (b,c,d) values are stored in the (quatern_b,quatern_c,quatern_d) fields. The quaternion representation is chosen for its compactness in representing rotations. The (proper) 3x3 rotation matrix that corresponds to [a,b,c,d] is [ a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c ] R = [ 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b ] [ 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b ] [ R11 R12 R13 ] = [ R21 R22 R23 ] [ R31 R32 R33 ] If (p,q,r) is a unit 3-vector, then rotation of angle h about that direction is represented by the quaternion [a,b,c,d] = [cos(h/2), p*sin(h/2), q*sin(h/2), r*sin(h/2)]. Requiring a >= 0 is equivalent to requiring -Pi <= h <= Pi. (Note that [-a,-b,-c,-d] represents the same rotation as [a,b,c,d]; there are 2 quaternions that can be used to represent a given rotation matrix R.) To rotate a 3-vector (x,y,z) using quaternions, we compute the quaternion product [0,x',y',z'] = [a,b,c,d] * [0,x,y,z] * [a,-b,-c,-d] which is equivalent to the matrix-vector multiply [ x' ] [ x ] [ y' ] = R [ y ] (equivalence depends on a*a+b*b+c*c+d*d=1) [ z' ] [ z ] Multiplication of 2 quaternions is defined by the following: [a,b,c,d] = a*1 + b*I + c*J + d*K where I*I = J*J = K*K = -1 (I,J,K are square roots of -1) I*J = K J*K = I K*I = J J*I = -K K*J = -I I*K = -J (not commutative!) For example [a,b,0,0] * [0,0,0,1] = [0,0,-b,a] since this expands to (a+b*I)*(K) = (a*K+b*I*K) = (a*K-b*J). The above formula shows how to go from quaternion (b,c,d) to rotation matrix and direction cosines. Conversely, given R, we can compute the fields for the NIFTI-1 header by a = 0.5 * sqrt(1+R11+R22+R33) (not stored) b = 0.25 * (R32-R23) / a => quatern_b c = 0.25 * (R13-R31) / a => quatern_c d = 0.25 * (R21-R12) / a => quatern_d If a=0 (a 180 degree rotation), alternative formulas are needed. See the nifti1_io.c function mat44_to_quatern() for an implementation of the various cases in converting R to [a,b,c,d]. Note that R-transpose (= R-inverse) would lead to the quaternion [a,-b,-c,-d]. The choice to specify the qoffset_x (etc.) values in the final coordinate system is partly to make it easy to convert DICOM images to this format. The DICOM attribute "Image Position (Patient)" (0020,0032) stores the (Xd,Yd,Zd) coordinates of the center of the first voxel. Here, (Xd,Yd,Zd) refer to DICOM coordinates, and Xd=-x, Yd=-y, Zd=z, where (x,y,z) refers to the NIFTI coordinate system discussed above. (i.e., DICOM +Xd is Left, +Yd is Posterior, +Zd is Superior, whereas +x is Right, +y is Anterior , +z is Superior. ) Thus, if the (0020,0032) DICOM attribute is extracted into (px,py,pz), then qoffset_x = -px qoffset_y = -py qoffset_z = pz is a reasonable setting when qform_code=NIFTI_XFORM_SCANNER_ANAT. That is, DICOM's coordinate system is 180 degrees rotated about the z-axis from the neuroscience/NIFTI coordinate system. To transform between DICOM and NIFTI, you just have to negate the x- and y-coordinates. The DICOM attribute (0020,0037) "Image Orientation (Patient)" gives the orientation of the x- and y-axes of the image data in terms of 2 3-vectors. The first vector is a unit vector along the x-axis, and the second is along the y-axis. If the (0020,0037) attribute is extracted into the value (xa,xb,xc,ya,yb,yc), then the first two columns of the R matrix would be [ -xa -ya ] [ -xb -yb ] [ xc yc ] The negations are because DICOM's x- and y-axes are reversed relative to NIFTI's. The third column of the R matrix gives the direction of displacement (relative to the subject) along the slice-wise direction. This orientation is not encoded in the DICOM standard in a simple way; DICOM is mostly concerned with 2D images. The third column of R will be either the cross-product of the first 2 columns or its negative. It is possible to infer the sign of the 3rd column by examining the coordinates in DICOM attribute (0020,0032) "Image Position (Patient)" for successive slices. However, this method occasionally fails for reasons that I (RW Cox) do not understand. -----------------------------------------------------------------------------*/ /* [qs]form_code value: */ /* x,y,z coordinate system refers to: */ /*-----------------------*/ /*---------------------------------------*/ /*! \defgroup NIFTI1_XFORM_CODES \brief nifti1 xform codes to describe the "standard" coordinate system @{ */ /*! Arbitrary coordinates (Method 1). */ #define NIFTI_XFORM_UNKNOWN 0 /*! Scanner-based anatomical coordinates */ #define NIFTI_XFORM_SCANNER_ANAT 1 /*! Coordinates aligned to another file's, or to anatomical "truth". */ #define NIFTI_XFORM_ALIGNED_ANAT 2 /*! Coordinates aligned to Talairach- Tournoux Atlas; (0,0,0)=AC, etc. */ #define NIFTI_XFORM_TALAIRACH 3 /*! MNI 152 normalized coordinates. */ #define NIFTI_XFORM_MNI_152 4 /* @} */ /*---------------------------------------------------------------------------*/ /* UNITS OF SPATIAL AND TEMPORAL DIMENSIONS: ---------------------------------------- The codes below can be used in xyzt_units to indicate the units of pixdim. As noted earlier, dimensions 1,2,3 are for x,y,z; dimension 4 is for time (t). - If dim[4]=1 or dim[0] < 4, there is no time axis. - A single time series (no space) would be specified with - dim[0] = 4 (for scalar data) or dim[0] = 5 (for vector data) - dim[1] = dim[2] = dim[3] = 1 - dim[4] = number of time points - pixdim[4] = time step - xyzt_units indicates units of pixdim[4] - dim[5] = number of values stored at each time point Bits 0..2 of xyzt_units specify the units of pixdim[1..3] (e.g., spatial units are values 1..7). Bits 3..5 of xyzt_units specify the units of pixdim[4] (e.g., temporal units are multiples of 8). This compression of 2 distinct concepts into 1 byte is due to the limited space available in the 348 byte ANALYZE 7.5 header. The macros XYZT_TO_SPACE and XYZT_TO_TIME can be used to mask off the undesired bits from the xyzt_units fields, leaving "pure" space and time codes. Inversely, the macro SPACE_TIME_TO_XYZT can be used to assemble a space code (0,1,2,...,7) with a time code (0,8,16,32,...,56) into the combined value for xyzt_units. Note that codes are provided to indicate the "time" axis units are actually frequency in Hertz (_HZ), in part-per-million (_PPM) or in radians-per-second (_RADS). The toffset field can be used to indicate a nonzero start point for the time axis. That is, time point #m is at t=toffset+m*pixdim[4] for m=0..dim[4]-1. -----------------------------------------------------------------------------*/ /*! \defgroup NIFTI1_UNITS \brief nifti1 units codes to describe the unit of measurement for each dimension of the dataset @{ */ /*! NIFTI code for unspecified units. */ #define NIFTI_UNITS_UNKNOWN 0 /** Space codes are multiples of 1. **/ /*! NIFTI code for meters. */ #define NIFTI_UNITS_METER 1 /*! NIFTI code for millimeters. */ #define NIFTI_UNITS_MM 2 /*! NIFTI code for micrometers. */ #define NIFTI_UNITS_MICRON 3 /** Time codes are multiples of 8. **/ /*! NIFTI code for seconds. */ #define NIFTI_UNITS_SEC 8 /*! NIFTI code for milliseconds. */ #define NIFTI_UNITS_MSEC 16 /*! NIFTI code for microseconds. */ #define NIFTI_UNITS_USEC 24 /*** These units are for spectral data: ***/ /*! NIFTI code for Hertz. */ #define NIFTI_UNITS_HZ 32 /*! NIFTI code for ppm. */ #define NIFTI_UNITS_PPM 40 /*! NIFTI code for radians per second. */ #define NIFTI_UNITS_RADS 48 /* @} */ #undef XYZT_TO_SPACE #undef XYZT_TO_TIME #define XYZT_TO_SPACE(xyzt) ( (xyzt) & 0x07 ) #define XYZT_TO_TIME(xyzt) ( (xyzt) & 0x38 ) #undef SPACE_TIME_TO_XYZT #define SPACE_TIME_TO_XYZT(ss,tt) ( (((char)(ss)) & 0x07) \ | (((char)(tt)) & 0x38) ) /*---------------------------------------------------------------------------*/ /* MRI-SPECIFIC SPATIAL AND TEMPORAL INFORMATION: --------------------------------------------- A few fields are provided to store some extra information that is sometimes important when storing the image data from an FMRI time series experiment. (After processing such data into statistical images, these fields are not likely to be useful.) { freq_dim } = These fields encode which spatial dimension (1,2, or 3) { phase_dim } = corresponds to which acquisition dimension for MRI data. { slice_dim } = Examples: Rectangular scan multi-slice EPI: freq_dim = 1 phase_dim = 2 slice_dim = 3 (or some permutation) Spiral scan multi-slice EPI: freq_dim = phase_dim = 0 slice_dim = 3 since the concepts of frequency- and phase-encoding directions don't apply to spiral scan slice_duration = If this is positive, AND if slice_dim is nonzero, indicates the amount of time used to acquire 1 slice. slice_duration*dim[slice_dim] can be less than pixdim[4] with a clustered acquisition method, for example. slice_code = If this is nonzero, AND if slice_dim is nonzero, AND if slice_duration is positive, indicates the timing pattern of the slice acquisition. The following codes are defined: NIFTI_SLICE_SEQ_INC == sequential increasing NIFTI_SLICE_SEQ_DEC == sequential decreasing NIFTI_SLICE_ALT_INC == alternating increasing NIFTI_SLICE_ALT_DEC == alternating decreasing NIFTI_SLICE_ALT_INC2 == alternating increasing #2 NIFTI_SLICE_ALT_DEC2 == alternating decreasing #2 { slice_start } = Indicates the start and end of the slice acquisition { slice_end } = pattern, when slice_code is nonzero. These values are present to allow for the possible addition of "padded" slices at either end of the volume, which don't fit into the slice timing pattern. If there are no padding slices, then slice_start=0 and slice_end=dim[slice_dim]-1 are the correct values. For these values to be meaningful, slice_start must be non-negative and slice_end must be greater than slice_start. Otherwise, they should be ignored. The following table indicates the slice timing pattern, relative to time=0 for the first slice acquired, for some sample cases. Here, dim[slice_dim]=7 (there are 7 slices, labeled 0..6), slice_duration=0.1, and slice_start=1, slice_end=5 (1 padded slice on each end). slice index SEQ_INC SEQ_DEC ALT_INC ALT_DEC ALT_INC2 ALT_DEC2 6 : n/a n/a n/a n/a n/a n/a n/a = not applicable 5 : 0.4 0.0 0.2 0.0 0.4 0.2 (slice time offset 4 : 0.3 0.1 0.4 0.3 0.1 0.0 doesn't apply to 3 : 0.2 0.2 0.1 0.1 0.3 0.3 slices outside 2 : 0.1 0.3 0.3 0.4 0.0 0.1 the range 1 : 0.0 0.4 0.0 0.2 0.2 0.4 slice_start .. 0 : n/a n/a n/a n/a n/a n/a slice_end) The SEQ slice_codes are sequential ordering (uncommon but not unknown), either increasing in slice number or decreasing (INC or DEC), as illustrated above. The ALT slice codes are alternating ordering. The 'standard' way for these to operate (without the '2' on the end) is for the slice timing to start at the edge of the slice_start .. slice_end group (at slice_start for INC and at slice_end for DEC). For the 'ALT_*2' slice_codes, the slice timing instead starts at the first slice in from the edge (at slice_start+1 for INC2 and at slice_end-1 for DEC2). This latter acquisition scheme is found on some Siemens scanners. The fields freq_dim, phase_dim, slice_dim are all squished into the single byte field dim_info (2 bits each, since the values for each field are limited to the range 0..3). This unpleasantness is due to lack of space in the 348 byte allowance. The macros DIM_INFO_TO_FREQ_DIM, DIM_INFO_TO_PHASE_DIM, and DIM_INFO_TO_SLICE_DIM can be used to extract these values from the dim_info byte. The macro FPS_INTO_DIM_INFO can be used to put these 3 values into the dim_info byte. -----------------------------------------------------------------------------*/ #undef DIM_INFO_TO_FREQ_DIM #undef DIM_INFO_TO_PHASE_DIM #undef DIM_INFO_TO_SLICE_DIM #define DIM_INFO_TO_FREQ_DIM(di) ( ((di) ) & 0x03 ) #define DIM_INFO_TO_PHASE_DIM(di) ( ((di) >> 2) & 0x03 ) #define DIM_INFO_TO_SLICE_DIM(di) ( ((di) >> 4) & 0x03 ) #undef FPS_INTO_DIM_INFO #define FPS_INTO_DIM_INFO(fd,pd,sd) ( ( ( ((char)(fd)) & 0x03) ) | \ ( ( ((char)(pd)) & 0x03) << 2 ) | \ ( ( ((char)(sd)) & 0x03) << 4 ) ) /*! \defgroup NIFTI1_SLICE_ORDER \brief nifti1 slice order codes, describing the acquisition order of the slices @{ */ #define NIFTI_SLICE_UNKNOWN 0 #define NIFTI_SLICE_SEQ_INC 1 #define NIFTI_SLICE_SEQ_DEC 2 #define NIFTI_SLICE_ALT_INC 3 #define NIFTI_SLICE_ALT_DEC 4 #define NIFTI_SLICE_ALT_INC2 5 /* 05 May 2005: RWCox */ #define NIFTI_SLICE_ALT_DEC2 6 /* 05 May 2005: RWCox */ /* @} */ /*---------------------------------------------------------------------------*/ /* UNUSED FIELDS: ------------- Some of the ANALYZE 7.5 fields marked as ++UNUSED++ may need to be set to particular values for compatibility with other programs. The issue of interoperability of ANALYZE 7.5 files is a murky one -- not all programs require exactly the same set of fields. (Unobscuring this murkiness is a principal motivation behind NIFTI-1.) Some of the fields that may need to be set for other (non-NIFTI aware) software to be happy are: extents dbh.h says this should be 16384 regular dbh.h says this should be the character 'r' glmin, } dbh.h says these values should be the min and max voxel glmax } values for the entire dataset It is best to initialize ALL fields in the NIFTI-1 header to 0 (e.g., with calloc()), then fill in what is needed. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* MISCELLANEOUS C MACROS -----------------------------------------------------------------------------*/ /*.................*/ /*! Given a nifti_1_header struct, check if it has a good magic number. Returns NIFTI version number (1..9) if magic is good, 0 if it is not. */ #define NIFTI_VERSION(h) \ ( ( (h).magic[0]=='n' && (h).magic[3]=='\0' && \ ( (h).magic[1]=='i' || (h).magic[1]=='+' ) && \ ( (h).magic[2]>='1' && (h).magic[2]<='9' ) ) \ ? (h).magic[2]-'0' : 0 ) /*.................*/ /*! Check if a nifti_1_header struct says if the data is stored in the same file or in a separate file. Returns 1 if the data is in the same file as the header, 0 if it is not. */ #define NIFTI_ONEFILE(h) ( (h).magic[1] == '+' ) /*.................*/ /*! Check if a nifti_1_header struct needs to be byte swapped. Returns 1 if it needs to be swapped, 0 if it does not. */ #define NIFTI_NEEDS_SWAP(h) ( (h).dim[0] < 0 || (h).dim[0] > 7 ) /*.................*/ /*! Check if a nifti_1_header struct contains a 5th (vector) dimension. Returns size of 5th dimension if > 1, returns 0 otherwise. */ #define NIFTI_5TH_DIM(h) ( ((h).dim[0]>4 && (h).dim[5]>1) ? (h).dim[5] : 0 ) /*****************************************************************************/ /*=================*/ #ifdef __cplusplus } #endif /*=================*/ #endif /* _NIFTI_HEADER_ */ dcm2niix-1.0.20181125/console/nifti1_io_core.cpp000066400000000000000000000442251337661136700210540ustar00rootroot00000000000000//This unit uses a subset of the functions from the nifti1_io available from // https://sourceforge.net/projects/niftilib/files/nifticlib/ //These functions were extended by Chris Rorden (2014) and maintain the same license /*****===================================================================*****/ /***** Sample functions to deal with NIFTI-1 and ANALYZE files *****/ /*****...................................................................*****/ /***** This code is released to the public domain. *****/ /*****...................................................................*****/ /***** Author: Robert W Cox, SSCC/DIRP/NIMH/NIH/DHHS/USA/EARTH *****/ /***** Date: August 2003 *****/ /*****...................................................................*****/ /***** Neither the National Institutes of Health (NIH), nor any of its *****/ /***** employees imply any warranty of usefulness of this software for *****/ /***** any purpose, and do not assume any liability for damages, *****/ /***** incidental or otherwise, caused by any use of this document. *****/ /*****===================================================================*****/ #include //requires VS 2015 or later #include "nifti1_io_core.h" #include #include #include #include #include #include #include #include #ifndef _MSC_VER #include #endif #include "print.h" #ifndef USING_R void nifti_swap_8bytes( size_t n , void *ar ) // 4 bytes at a time { size_t ii ; unsigned char * cp0 = (unsigned char *)ar, * cp1, * cp2 ; unsigned char tval ; for( ii=0 ; ii < n ; ii++ ){ cp1 = cp0; cp2 = cp0+7; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1++; cp2--; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1++; cp2--; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1++; cp2--; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp0 += 8; } return ; } void nifti_swap_4bytes( size_t n , void *ar ) // 4 bytes at a time { size_t ii ; unsigned char * cp0 = (unsigned char *)ar, * cp1, * cp2 ; unsigned char tval ; for( ii=0 ; ii < n ; ii++ ){ cp1 = cp0; cp2 = cp0+3; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1++; cp2--; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp0 += 4; } return ; } void nifti_swap_2bytes( size_t n , void *ar ) // 2 bytes at a time { size_t ii ; unsigned char * cp1 = (unsigned char *)ar, * cp2 ; unsigned char tval; for( ii=0 ; ii < n ; ii++ ){ cp2 = cp1 + 1; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1 += 2; } return ; } #endif int isSameFloat (float a, float b) { return (fabs (a - b) <= FLT_EPSILON); } int isSameDouble (double a, double b) { return (fabs (a - b) <= DBL_EPSILON); } ivec3 setiVec3(int x, int y, int z) { ivec3 v = {{x, y, z}}; return v; } vec3 setVec3(float x, float y, float z) { vec3 v = {{x, y, z}}; return v; } vec4 setVec4(float x, float y, float z) { vec4 v= {{x, y, z, 1}}; return v; } vec3 crossProduct(vec3 u, vec3 v) { return setVec3(u.v[1]*v.v[2] - v.v[1]*u.v[2], -u.v[0]*v.v[2] + v.v[0]*u.v[2], u.v[0]*v.v[1] - v.v[0]*u.v[1]); } float dotProduct(vec3 u, vec3 v) { return (u.v[0]*v.v[0] + v.v[1]*u.v[1] + v.v[2]*u.v[2]); } vec3 nifti_vect33_norm (vec3 v) { //normalize vector length vec3 vO = v; float vLen = sqrt( (v.v[0]*v.v[0]) + (v.v[1]*v.v[1]) + (v.v[2]*v.v[2])); if (vLen <= FLT_EPSILON) return vO; //avoid divide by zero for (int i = 0; i < 3; i++) vO.v[i] = v.v[i]/vLen; return vO; } vec3 nifti_vect33mat33_mul(vec3 v, mat33 m ) { //multiply vector * 3x3matrix vec3 vO; for (int i=0; i<3; i++) { //multiply Pcrs * m vO.v[i] = 0; for(int j=0; j<3; j++) vO.v[i] += m.m[i][j]*v.v[j]; } return vO; } vec4 nifti_vect44mat44_mul(vec4 v, mat44 m ) { //multiply vector * 4x4matrix vec4 vO; for (int i=0; i<4; i++) { //multiply Pcrs * m vO.v[i] = 0; for(int j=0; j<4; j++) vO.v[i] += m.m[i][j]*v.v[j]; } return vO; } mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]) { //create NIfTI header based on values from DICOM header //note orient has 6 values, indexed from 1, patient position and xyzMM have 3 values indexed from 1 mat33 Q, diagVox; Q.m[0][0] = orient[1]; Q.m[0][1] = orient[2] ; Q.m[0][2] = orient[3] ; // load Q Q.m[1][0] = orient[4]; Q.m[1][1] = orient[5] ; Q.m[1][2] = orient[6]; //printMessage("Orient %g %g %g %g %g %g\n",orient[1],orient[2],orient[3],orient[4],orient[5],orient[6] ); /* normalize row 1 */ double val = Q.m[0][0]*Q.m[0][0] + Q.m[0][1]*Q.m[0][1] + Q.m[0][2]*Q.m[0][2] ; if( val > 0.0l ){ val = 1.0l / sqrt(val) ; Q.m[0][0] *= (float)val ; Q.m[0][1] *= (float)val ; Q.m[0][2] *= (float)val ; } else { Q.m[0][0] = 1.0l ; Q.m[0][1] = 0.0l ; Q.m[0][2] = 0.0l ; } /* normalize row 2 */ val = Q.m[1][0]*Q.m[1][0] + Q.m[1][1]*Q.m[1][1] + Q.m[1][2]*Q.m[1][2] ; if( val > 0.0l ){ val = 1.0l / sqrt(val) ; Q.m[1][0] *= (float)val ; Q.m[1][1] *= (float)val ; Q.m[1][2] *= (float)val ; } else { Q.m[1][0] = 0.0l ; Q.m[1][1] = 1.0l ; Q.m[1][2] = 0.0l ; } /* row 3 is the cross product of rows 1 and 2*/ Q.m[2][0] = Q.m[0][1]*Q.m[1][2] - Q.m[0][2]*Q.m[1][1] ; /* cross */ Q.m[2][1] = Q.m[0][2]*Q.m[1][0] - Q.m[0][0]*Q.m[1][2] ; /* product */ Q.m[2][2] = Q.m[0][0]*Q.m[1][1] - Q.m[0][1]*Q.m[1][0] ; Q = nifti_mat33_transpose(Q); if (nifti_mat33_determ(Q) < 0.0) { Q.m[0][2] = -Q.m[0][2]; Q.m[1][2] = -Q.m[1][2]; Q.m[2][2] = -Q.m[2][2]; } //next scale matrix LOAD_MAT33(diagVox, xyzMM[1],0.0l,0.0l, 0.0l,xyzMM[2],0.0l, 0.0l,0.0l, xyzMM[3]); Q = nifti_mat33_mul(Q,diagVox); mat44 Q44; //4x4 matrix includes translations LOAD_MAT44(Q44, Q.m[0][0],Q.m[0][1],Q.m[0][2],patientPosition[1], Q.m[1][0],Q.m[1][1],Q.m[1][2],patientPosition[2], Q.m[2][0],Q.m[2][1],Q.m[2][2],patientPosition[3]); return Q44; } #ifndef USING_R float nifti_mat33_determ( mat33 R ) /* determinant of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 ; /* INPUT MATRIX: */ r11 = R.m[0][0]; r12 = R.m[0][1]; r13 = R.m[0][2]; /* [ r11 r12 r13 ] */ r21 = R.m[1][0]; r22 = R.m[1][1]; r23 = R.m[1][2]; /* [ r21 r22 r23 ] */ r31 = R.m[2][0]; r32 = R.m[2][1]; r33 = R.m[2][2]; /* [ r31 r32 r33 ] */ return (float)(r11*r22*r33-r11*r32*r23-r21*r12*r33 +r21*r32*r13+r31*r12*r23-r31*r22*r13) ; } mat33 nifti_mat33_mul( mat33 A , mat33 B ) /* multiply 2 3x3 matrices */ //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c { mat33 C ; int i,j ; for( i=0 ; i < 3 ; i++ ) for( j=0 ; j < 3 ; j++ ) C.m[i][j] = A.m[i][0] * B.m[0][j] + A.m[i][1] * B.m[1][j] + A.m[i][2] * B.m[2][j] ; return C ; } #endif mat44 nifti_mat44_mul( mat44 A , mat44 B ) /* multiply 2 3x3 matrices */ { mat44 C ; int i,j ; for( i=0 ; i < 4 ; i++ ) for( j=0 ; j < 4; j++ ) C.m[i][j] = A.m[i][0] * B.m[0][j] + A.m[i][1] * B.m[1][j] + A.m[i][2] * B.m[2][j] + A.m[i][3] * B.m[3][j]; return C ; } mat33 nifti_mat33_transpose( mat33 A ) /* transpose 3x3 matrix */ //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c { mat33 B; int i,j ; for( i=0 ; i < 3 ; i++ ) for( j=0 ; j < 3 ; j++ ) B.m[i][j] = A.m[j][i]; return B; } #ifndef USING_R mat33 nifti_mat33_inverse( mat33 R ) /* inverse of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 , deti ; mat33 Q ; // INPUT MATRIX: r11 = R.m[0][0]; r12 = R.m[0][1]; r13 = R.m[0][2]; // [ r11 r12 r13 ] r21 = R.m[1][0]; r22 = R.m[1][1]; r23 = R.m[1][2]; // [ r21 r22 r23 ] r31 = R.m[2][0]; r32 = R.m[2][1]; r33 = R.m[2][2]; // [ r31 r32 r33 ] deti = r11*r22*r33-r11*r32*r23-r21*r12*r33 +r21*r32*r13+r31*r12*r23-r31*r22*r13 ; if( deti != 0.0l ) deti = 1.0l / deti ; Q.m[0][0] = deti*( r22*r33-r32*r23) ; Q.m[0][1] = deti*(-r12*r33+r32*r13) ; Q.m[0][2] = deti*( r12*r23-r22*r13) ; Q.m[1][0] = deti*(-r21*r33+r31*r23) ; Q.m[1][1] = deti*( r11*r33-r31*r13) ; Q.m[1][2] = deti*(-r11*r23+r21*r13) ; Q.m[2][0] = deti*( r21*r32-r31*r22) ; Q.m[2][1] = deti*(-r11*r32+r31*r12) ; Q.m[2][2] = deti*( r11*r22-r21*r12) ; return Q ; } float nifti_mat33_rownorm( mat33 A ) // max row norm of 3x3 matrix { float r1,r2,r3 ; r1 = fabs(A.m[0][0])+fabs(A.m[0][1])+fabs(A.m[0][2]) ; r2 = fabs(A.m[1][0])+fabs(A.m[1][1])+fabs(A.m[1][2]) ; r3 = fabs(A.m[2][0])+fabs(A.m[2][1])+fabs(A.m[2][2]) ; if( r1 < r2 ) r1 = r2 ; if( r1 < r3 ) r1 = r3 ; return r1 ; } float nifti_mat33_colnorm( mat33 A ) // max column norm of 3x3 matrix { float r1,r2,r3 ; r1 = fabs(A.m[0][0])+fabs(A.m[1][0])+fabs(A.m[2][0]) ; r2 = fabs(A.m[0][1])+fabs(A.m[1][1])+fabs(A.m[2][1]) ; r3 = fabs(A.m[0][2])+fabs(A.m[1][2])+fabs(A.m[2][2]) ; if( r1 < r2 ) r1 = r2 ; if( r1 < r3 ) r1 = r3 ; return r1 ; } mat33 nifti_mat33_polar( mat33 A ) { mat33 X , Y , Z ; float alp,bet,gam,gmi , dif=1.0 ; int k=0 ; X = A ; // force matrix to be nonsingular gam = nifti_mat33_determ(X) ; while( gam == 0.0 ){ // perturb matrix gam = 0.00001 * ( 0.001 + nifti_mat33_rownorm(X) ) ; X.m[0][0] += gam ; X.m[1][1] += gam ; X.m[2][2] += gam ; gam = nifti_mat33_determ(X) ; } while(1){ Y = nifti_mat33_inverse(X) ; if( dif > 0.3 ){ // far from convergence alp = sqrt( nifti_mat33_rownorm(X) * nifti_mat33_colnorm(X) ) ; bet = sqrt( nifti_mat33_rownorm(Y) * nifti_mat33_colnorm(Y) ) ; gam = sqrt( bet / alp ) ; gmi = 1.0 / gam ; } else gam = gmi = 1.0 ; // close to convergence Z.m[0][0] = 0.5 * ( gam*X.m[0][0] + gmi*Y.m[0][0] ) ; Z.m[0][1] = 0.5 * ( gam*X.m[0][1] + gmi*Y.m[1][0] ) ; Z.m[0][2] = 0.5 * ( gam*X.m[0][2] + gmi*Y.m[2][0] ) ; Z.m[1][0] = 0.5 * ( gam*X.m[1][0] + gmi*Y.m[0][1] ) ; Z.m[1][1] = 0.5 * ( gam*X.m[1][1] + gmi*Y.m[1][1] ) ; Z.m[1][2] = 0.5 * ( gam*X.m[1][2] + gmi*Y.m[2][1] ) ; Z.m[2][0] = 0.5 * ( gam*X.m[2][0] + gmi*Y.m[0][2] ) ; Z.m[2][1] = 0.5 * ( gam*X.m[2][1] + gmi*Y.m[1][2] ) ; Z.m[2][2] = 0.5 * ( gam*X.m[2][2] + gmi*Y.m[2][2] ) ; dif = fabs(Z.m[0][0]-X.m[0][0])+fabs(Z.m[0][1]-X.m[0][1]) +fabs(Z.m[0][2]-X.m[0][2])+fabs(Z.m[1][0]-X.m[1][0]) +fabs(Z.m[1][1]-X.m[1][1])+fabs(Z.m[1][2]-X.m[1][2]) +fabs(Z.m[2][0]-X.m[2][0])+fabs(Z.m[2][1]-X.m[2][1]) +fabs(Z.m[2][2]-X.m[2][2]) ; k = k+1 ; if( k > 100 || dif < 3.e-6 ) break ; // convergence or exhaustion X = Z ; } return Z ; } void nifti_mat44_to_quatern( mat44 R , float *qb, float *qc, float *qd, float *qx, float *qy, float *qz, float *dx, float *dy, float *dz, float *qfac ) { double r11,r12,r13 , r21,r22,r23 , r31,r32,r33 ; double xd,yd,zd , a,b,c,d ; mat33 P,Q ; // offset outputs are read write out of input matrix ASSIF(qx,R.m[0][3]) ; ASSIF(qy,R.m[1][3]) ; ASSIF(qz,R.m[2][3]) ; // load 3x3 matrix into local variables */ r11 = R.m[0][0] ; r12 = R.m[0][1] ; r13 = R.m[0][2] ; r21 = R.m[1][0] ; r22 = R.m[1][1] ; r23 = R.m[1][2] ; r31 = R.m[2][0] ; r32 = R.m[2][1] ; r33 = R.m[2][2] ; // compute lengths of each column; these determine grid spacings xd = sqrt( r11*r11 + r21*r21 + r31*r31 ) ; yd = sqrt( r12*r12 + r22*r22 + r32*r32 ) ; zd = sqrt( r13*r13 + r23*r23 + r33*r33 ) ; // if a column length is zero, patch the trouble if( xd == 0.0l ){ r11 = 1.0l ; r21 = r31 = 0.0l ; xd = 1.0l ; } if( yd == 0.0l ){ r22 = 1.0l ; r12 = r32 = 0.0l ; yd = 1.0l ; } if( zd == 0.0l ){ r33 = 1.0l ; r13 = r23 = 0.0l ; zd = 1.0l ; } // assign the output lengths */ ASSIF(dx,xd) ; ASSIF(dy,yd) ; ASSIF(dz,zd) ; // normalize the columns */ r11 /= xd ; r21 /= xd ; r31 /= xd ; r12 /= yd ; r22 /= yd ; r32 /= yd ; r13 /= zd ; r23 /= zd ; r33 /= zd ; /* At this point, the matrix has normal columns, but we have to allow for the fact that the hideous user may not have given us a matrix with orthogonal columns. So, now find the orthogonal matrix closest to the current matrix. One reason for using the polar decomposition to get this orthogonal matrix, rather than just directly orthogonalizing the columns, is so that inputting the inverse matrix to R will result in the inverse orthogonal matrix at this point. If we just orthogonalized the columns, this wouldn't necessarily hold. */ Q.m[0][0] = r11 ; Q.m[0][1] = r12 ; Q.m[0][2] = r13 ; // load Q Q.m[1][0] = r21 ; Q.m[1][1] = r22 ; Q.m[1][2] = r23 ; Q.m[2][0] = r31 ; Q.m[2][1] = r32 ; Q.m[2][2] = r33 ; P = nifti_mat33_polar(Q) ; // P is orthog matrix closest to Q r11 = P.m[0][0] ; r12 = P.m[0][1] ; r13 = P.m[0][2] ; // unload r21 = P.m[1][0] ; r22 = P.m[1][1] ; r23 = P.m[1][2] ; r31 = P.m[2][0] ; r32 = P.m[2][1] ; r33 = P.m[2][2] ; // [ r11 r12 r13 ] // at this point, the matrix [ r21 r22 r23 ] is orthogonal // [ r31 r32 r33 ] // compute the determinant to determine if it is proper zd = r11*r22*r33-r11*r32*r23-r21*r12*r33 +r21*r32*r13+r31*r12*r23-r31*r22*r13 ; // should be -1 or 1 if( zd > 0 ){ // proper ASSIF(qfac,1.0) ; } else { // improper ==> flip 3rd column ASSIF(qfac,-1.0) ; r13 = -r13 ; r23 = -r23 ; r33 = -r33 ; } // now, compute quaternion parameters a = r11 + r22 + r33 + 1.0l ; if( a > 0.5l ){ // simplest case a = 0.5l * sqrt(a) ; b = 0.25l * (r32-r23) / a ; c = 0.25l * (r13-r31) / a ; d = 0.25l * (r21-r12) / a ; } else { // trickier case xd = 1.0 + r11 - (r22+r33) ; // 4*b*b yd = 1.0 + r22 - (r11+r33) ; // 4*c*c zd = 1.0 + r33 - (r11+r22) ; // 4*d*d if( xd > 1.0 ){ b = 0.5l * sqrt(xd) ; c = 0.25l* (r12+r21) / b ; d = 0.25l* (r13+r31) / b ; a = 0.25l* (r32-r23) / b ; } else if( yd > 1.0 ){ c = 0.5l * sqrt(yd) ; b = 0.25l* (r12+r21) / c ; d = 0.25l* (r23+r32) / c ; a = 0.25l* (r13-r31) / c ; } else { d = 0.5l * sqrt(zd) ; b = 0.25l* (r13+r31) / d ; c = 0.25l* (r23+r32) / d ; a = 0.25l* (r21-r12) / d ; } // if( a < 0.0l ){ b=-b ; c=-c ; d=-d; a=-a; } if( a < 0.0l ){ b=-b ; c=-c ; d=-d; } //a discarded... } ASSIF(qb,b) ; ASSIF(qc,c) ; ASSIF(qd,d) ; return ; } mat44 nifti_quatern_to_mat44( float qb, float qc, float qd, float qx, float qy, float qz, float dx, float dy, float dz, float qfac ) { mat44 R ; double a,b=qb,c=qc,d=qd , xd,yd,zd ; /* last row is always [ 0 0 0 1 ] */ R.m[3][0]=R.m[3][1]=R.m[3][2] = 0.0f ; R.m[3][3]= 1.0f ; /* compute a parameter from b,c,d */ a = 1.0l - (b*b + c*c + d*d) ; if( a < 1.e-7l ){ /* special case */ a = 1.0l / sqrt(b*b+c*c+d*d) ; b *= a ; c *= a ; d *= a ; /* normalize (b,c,d) vector */ a = 0.0l ; /* a = 0 ==> 180 degree rotation */ } else{ a = sqrt(a) ; /* angle = 2*arccos(a) */ } /* load rotation matrix, including scaling factors for voxel sizes */ xd = (dx > 0.0) ? dx : 1.0l ; /* make sure are positive */ yd = (dy > 0.0) ? dy : 1.0l ; zd = (dz > 0.0) ? dz : 1.0l ; if( qfac < 0.0 ) zd = -zd ; /* left handedness? */ R.m[0][0] = (float)( (a*a+b*b-c*c-d*d) * xd) ; R.m[0][1] = 2.0l * (b*c-a*d ) * yd ; R.m[0][2] = 2.0l * (b*d+a*c ) * zd ; R.m[1][0] = 2.0l * (b*c+a*d ) * xd ; R.m[1][1] = (float)( (a*a+c*c-b*b-d*d) * yd) ; R.m[1][2] = 2.0l * (c*d-a*b ) * zd ; R.m[2][0] = 2.0l * (b*d-a*c ) * xd ; R.m[2][1] = 2.0l * (c*d+a*b ) * yd ; R.m[2][2] = (float)( (a*a+d*d-c*c-b*b) * zd) ; /* load offsets */ R.m[0][3] = qx ; R.m[1][3] = qy ; R.m[2][3] = qz ; return R ; } mat44 nifti_mat44_inverse( mat44 R ) { double r11,r12,r13,r21,r22,r23,r31,r32,r33,v1,v2,v3 , deti ; mat44 Q ; /* INPUT MATRIX IS: */ r11 = R.m[0][0]; r12 = R.m[0][1]; r13 = R.m[0][2]; // [ r11 r12 r13 v1 ] r21 = R.m[1][0]; r22 = R.m[1][1]; r23 = R.m[1][2]; // [ r21 r22 r23 v2 ] r31 = R.m[2][0]; r32 = R.m[2][1]; r33 = R.m[2][2]; // [ r31 r32 r33 v3 ] v1 = R.m[0][3]; v2 = R.m[1][3]; v3 = R.m[2][3]; // [ 0 0 0 1 ] deti = r11*r22*r33-r11*r32*r23-r21*r12*r33 +r21*r32*r13+r31*r12*r23-r31*r22*r13 ; if( deti != 0.0l ) deti = 1.0l / deti ; Q.m[0][0] = deti*( r22*r33-r32*r23) ; Q.m[0][1] = deti*(-r12*r33+r32*r13) ; Q.m[0][2] = deti*( r12*r23-r22*r13) ; Q.m[0][3] = deti*(-r12*r23*v3+r12*v2*r33+r22*r13*v3 -r22*v1*r33-r32*r13*v2+r32*v1*r23) ; Q.m[1][0] = deti*(-r21*r33+r31*r23) ; Q.m[1][1] = deti*( r11*r33-r31*r13) ; Q.m[1][2] = deti*(-r11*r23+r21*r13) ; Q.m[1][3] = deti*( r11*r23*v3-r11*v2*r33-r21*r13*v3 +r21*v1*r33+r31*r13*v2-r31*v1*r23) ; Q.m[2][0] = deti*( r21*r32-r31*r22) ; Q.m[2][1] = deti*(-r11*r32+r31*r12) ; Q.m[2][2] = deti*( r11*r22-r21*r12) ; Q.m[2][3] = deti*(-r11*r22*v3+r11*r32*v2+r21*r12*v3 -r21*r32*v1-r31*r12*v2+r31*r22*v1) ; Q.m[3][0] = Q.m[3][1] = Q.m[3][2] = 0.0l ; Q.m[3][3] = (deti == 0.0l) ? 0.0l : 1.0l ; // failure flag if deti == 0 return Q ; } #endif dcm2niix-1.0.20181125/console/nifti1_io_core.h000066400000000000000000000057151337661136700205220ustar00rootroot00000000000000//this minimal set of nifti routines is based on nifti1_io without the dependencies (zlib) and a few extra functions // http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.h // http://niftilib.sourceforge.net #ifndef _NIFTI_IO_CORE_HEADER_ #define _NIFTI_IO_CORE_HEADER_ #ifdef USING_R #define STRICT_R_HEADERS #include "RNifti.h" #endif #ifdef __cplusplus extern "C" { #endif #include //requires VS 2015 or later #include #ifndef USING_R typedef struct { /** 4x4 matrix struct **/ float m[3][3] ; } mat33 ; typedef struct { /** 4x4 matrix struct **/ float m[4][4] ; } mat44 ; #endif typedef struct { /** x4 vector struct **/ float v[4] ; } vec4 ; typedef struct { /** x3 vector struct **/ float v[3] ; } vec3 ; typedef struct { /** x4 vector struct INTEGER**/ int v[3] ; } ivec3 ; #define LOAD_MAT33(AA,a11,a12,a13 ,a21,a22,a23 ,a31,a32,a33) \ ( AA.m[0][0]=a11 , AA.m[0][1]=a12 , AA.m[0][2]=a13 , \ AA.m[1][0]=a21 , AA.m[1][1]=a22 , AA.m[1][2]=a23 , \ AA.m[2][0]=a31 , AA.m[2][1]=a32 , AA.m[2][2]=a33 ) #define LOAD_MAT44(AA,a11,a12,a13,a14,a21,a22,a23,a24,a31,a32,a33,a34) \ ( AA.m[0][0]=a11 , AA.m[0][1]=a12 , AA.m[0][2]=a13 , AA.m[0][3]=a14 , \ AA.m[1][0]=a21 , AA.m[1][1]=a22 , AA.m[1][2]=a23 , AA.m[1][3]=a24 , \ AA.m[2][0]=a31 , AA.m[2][1]=a32 , AA.m[2][2]=a33 , AA.m[2][3]=a34 , \ AA.m[3][0]=AA.m[3][1]=AA.m[3][2]=0.0f , AA.m[3][3]=1.0f ) #undef ASSIF // assign v to *p, if possible #define ASSIF(p,v) if( (p)!=NULL ) *(p) = (v) float dotProduct(vec3 u, vec3 v); float nifti_mat33_determ( mat33 R ) ; int isSameFloat (float a, float b) ; int isSameDouble (double a, double b) ; mat33 nifti_mat33_inverse( mat33 R ); mat33 nifti_mat33_mul( mat33 A , mat33 B ); mat33 nifti_mat33_transpose( mat33 A ) ; mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]); mat44 nifti_mat44_inverse( mat44 R ); mat44 nifti_mat44_mul( mat44 A , mat44 B ); vec3 crossProduct(vec3 u, vec3 v); vec3 nifti_vect33_norm (vec3 v); vec3 nifti_vect33mat33_mul(vec3 v, mat33 m ); ivec3 setiVec3(int x, int y, int z); vec3 setVec3(float x, float y, float z); vec4 setVec4(float x, float y, float z); vec4 nifti_vect44mat44_mul(vec4 v, mat44 m ); void nifti_swap_2bytes( size_t n , void *ar ); // 2 bytes at a time void nifti_swap_4bytes( size_t n , void *ar ); // 4 bytes at a time void nifti_swap_8bytes( size_t n , void *ar ); // 8 bytes at a time void nifti_mat44_to_quatern( mat44 R , float *qb, float *qc, float *qd, float *qx, float *qy, float *qz, float *dx, float *dy, float *dz, float *qfac ); mat44 nifti_quatern_to_mat44( float qb, float qc, float qd, float qx, float qy, float qz, float dx, float dy, float dz, float qfac ); #ifdef __cplusplus } #endif #endifdcm2niix-1.0.20181125/console/nii_dicom.cpp000066400000000000000000010523721337661136700201200ustar00rootroot00000000000000//#define MY_DEBUG #if defined(_WIN64) || defined(_WIN32) #include //write to registry #endif #ifdef _MSC_VER #include #define getcwd _getcwd #define chdir _chrdir #include "io.h" #include //#define snprintf _snprintf //#define vsnprintf _vsnprintf #define strcasecmp _stricmp #define strncasecmp _strnicmp #else #include #endif //#include //clock() #ifndef USING_R #include "nifti1.h" #endif #include "print.h" #include "nii_dicom.h" #include #include // discriminate files from folders #include #include #include //toupper #include #include #include #include "jpg_0XC3.h" #include #include #include "nifti1_io_core.h" #ifdef USING_R #undef isnan #define isnan ISNAN #endif #ifndef myDisableClassicJPEG #ifdef myTurboJPEG #include #else #include "ujpeg.h" #endif #endif #ifdef myEnableJasper #include #endif #ifndef myDisableOpenJPEG #include "openjpeg.h" #ifdef myEnableJasper ERROR: YOU CAN NOT COMPILE WITH myEnableJasper AND NOT myDisableOpenJPEG OPTIONS SET SIMULTANEOUSLY #endif unsigned char * imagetoimg(opj_image_t * image) { int numcmpts = image->numcomps; int sgnd = image->comps[0].sgnd ; int width = image->comps[0].w; int height = image->comps[0].h; int bpp = (image->comps[0].prec + 7) >> 3; //e.g. 12 bits requires 2 bytes int imgbytes = bpp * width * height * numcmpts; bool isOK = true; if (numcmpts > 1) { for (int comp = 1; comp < numcmpts; comp++) { //check RGB data if (image->comps[0].w != image->comps[comp].w) isOK = false; if (image->comps[0].h != image->comps[comp].h) isOK = false; if (image->comps[0].dx != image->comps[comp].dx) isOK = false; if (image->comps[0].dy != image->comps[comp].dy) isOK = false; if (image->comps[0].prec != image->comps[comp].prec) isOK = false; if (image->comps[0].sgnd != image->comps[comp].sgnd) isOK = false; } if (numcmpts != 3) isOK = false; //we only handle Gray and RedGreenBlue, not GrayAlpha or RedGreenBlueAlpha if (image->comps[0].prec != 8) isOK = false; //only 8-bit for RGB data } if ((image->comps[0].prec < 1) || (image->comps[0].prec > 16)) isOK = false; //currently we only handle 1 and 2 byte data if (!isOK) { printMessage("jpeg decode failure w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); return NULL; } #ifdef MY_DEBUG printMessage("w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); #endif //extract the data if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { printError("Catastrophic decompression error\n"); return NULL; } unsigned char *img = (unsigned char *)malloc(imgbytes); uint16_t * img16ui = (uint16_t*) img; //unsigned 16-bit int16_t * img16i = (int16_t*) img; //signed 16-bit if (sgnd) bpp = -bpp; if (bpp == -1) { free(img); printError("Signed 8-bit DICOM?\n"); return NULL; } //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB int pix = 0; //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() typedef struct bufinfo { unsigned char *buf; unsigned char *cur; size_t len; } BufInfo; static void my_stream_free (void * p_user_data) { //do nothing //BufInfo d = (BufInfo) p_user_data; //free(d.buf); } // my_stream_free() static OPJ_UINT32 opj_read_from_buffer(void * p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo* p_file) { OPJ_UINT32 l_nb_read; if(p_file->cur + p_nb_bytes < p_file->buf + p_file->len ) { l_nb_read = p_nb_bytes; } else { l_nb_read = (OPJ_UINT32)(p_file->buf + p_file->len - p_file->cur); } memcpy(p_buffer, p_file->cur, l_nb_read); p_file->cur += l_nb_read; return l_nb_read ? l_nb_read : ((OPJ_UINT32)-1); } //opj_read_from_buffer() static OPJ_UINT32 opj_write_from_buffer(void * p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo* p_file) { memcpy(p_file->cur,p_buffer, p_nb_bytes); p_file->cur += p_nb_bytes; p_file->len += p_nb_bytes; return p_nb_bytes; } // opj_write_from_buffer() static OPJ_SIZE_T opj_skip_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo * p_file) { if(p_file->cur + p_nb_bytes < p_file->buf + p_file->len ) { p_file->cur += p_nb_bytes; return p_nb_bytes; } p_file->cur = p_file->buf + p_file->len; return (OPJ_SIZE_T)-1; } //opj_skip_from_buffer() //fix for https://github.com/neurolabusc/dcm_qa/issues/5 static OPJ_BOOL opj_seek_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo * p_file) { //printf("opj_seek_from_buffer %d + %d -> %d + %d\n", p_file->cur , p_nb_bytes, p_file->buf, p_file->len); if (p_nb_bytes < p_file->len ) { p_file->cur = p_file->buf + p_nb_bytes; return OPJ_TRUE; } p_file->cur = p_file->buf + p_file->len; return OPJ_FALSE; } //opj_seek_from_buffer() /*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_create_buffer_stream() unsigned char * nii_loadImgCoreOpenJPEG(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { //OpenJPEG library is not well documented and has changed between versions //Since the JPEG is embedded in a DICOM we need to skip bytes at the start of the file // In theory we might also want to strip data that exists AFTER the image, see gdcmJPEG2000Codec.c unsigned char * ret = NULL; opj_dparameters_t params; opj_codec_t *codec; opj_image_t *jpx; opj_stream_t *stream; FILE *reader = fopen(imgname, "rb"); fseek(reader, 0, SEEK_END); long size = ftell(reader)- dcm.imageStart; if (size <= 8) return NULL; fseek(reader, dcm.imageStart, SEEK_SET); unsigned char *data = (unsigned char*) malloc(size); size_t sz = fread(data, 1, size, reader); fclose(reader); if (sz < size) return NULL; OPJ_CODEC_FORMAT format = OPJ_CODEC_JP2; //DICOM JPEG2k is SUPPOSED to start with codestream, but some vendors include a header if (data[0] == 0xFF && data[1] == 0x4F && data[2] == 0xFF && data[3] == 0x51) format = OPJ_CODEC_J2K; opj_set_default_decoder_parameters(¶ms); BufInfo dx; dx.buf = data; dx.cur = data; dx.len = size; stream = opj_stream_create_buffer_stream(&dx, (OPJ_UINT32)size, true); if (stream == NULL) return NULL; codec = opj_create_decompress(format); // setup the decoder decoding parameters using user parameters if ( !opj_setup_decoder(codec, ¶ms) ) goto cleanup2; // Read the main header of the codestream and if necessary the JP2 boxes if(! opj_read_header( stream, codec, &jpx)){ printError( "OpenJPEG failed to read the header %s (offset %d)\n", imgname, dcm.imageStart); //comment these next lines to abort: include these to create zero-padded slice #ifdef MY_ZEROFILLBROKENJPGS //fix broken slices https://github.com/scitran-apps/dcm2niix/issues/4 printError( "Zero-filled slice created\n"); int imgbytes = (hdr.bitpix/8)*hdr.dim[1]*hdr.dim[2]; ret = (unsigned char*) calloc(imgbytes,1); #endif goto cleanup2; } // Get the decoded image if ( !( opj_decode(codec, stream, jpx) && opj_end_decompress(codec,stream) ) ) { printError( "OpenJPEG j2k_to_image failed to decode %s\n",imgname); goto cleanup1; } ret = imagetoimg(jpx); cleanup1: opj_image_destroy(jpx); cleanup2: free(dx.buf); opj_stream_destroy(stream); opj_destroy_codec(codec); return ret; } #endif //if #ifndef M_PI #define M_PI 3.14159265358979323846 #endif float deFuzz(float v) { if (fabs(v) < 0.00001) return 0; else return v; } #ifdef MY_DEBUG void reportMat33(char *str, mat33 A) { printMessage("%s = [%g %g %g ; %g %g %g; %g %g %g ]\n",str, deFuzz(A.m[0][0]),deFuzz(A.m[0][1]),deFuzz(A.m[0][2]), deFuzz(A.m[1][0]),deFuzz(A.m[1][1]),deFuzz(A.m[1][2]), deFuzz(A.m[2][0]),deFuzz(A.m[2][1]),deFuzz(A.m[2][2])); } void reportMat44(char *str, mat44 A) { //example: reportMat44((char*)"out",*R); printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n",str, deFuzz(A.m[0][0]),deFuzz(A.m[0][1]),deFuzz(A.m[0][2]),deFuzz(A.m[0][3]), deFuzz(A.m[1][0]),deFuzz(A.m[1][1]),deFuzz(A.m[1][2]),deFuzz(A.m[1][3]), deFuzz(A.m[2][0]),deFuzz(A.m[2][1]),deFuzz(A.m[2][2]),deFuzz(A.m[2][3])); } #endif int verify_slice_dir (struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, mat44 *R, int isVerbose){ //returns slice direction: 1=sag,2=coronal,3=axial, -= flipped if (h->dim[3] < 2) return 0; //don't care direction for single slice int iSL = 1; //find Z-slice direction: row with highest magnitude of 3rd column if ( (fabs(R->m[1][2]) >= fabs(R->m[0][2])) && (fabs(R->m[1][2]) >= fabs(R->m[2][2]))) iSL = 2; // if ( (fabs(R->m[2][2]) >= fabs(R->m[0][2])) && (fabs(R->m[2][2]) >= fabs(R->m[1][2]))) iSL = 3; //axial acquisition float pos = NAN; if ( !isnan(d2.patientPosition[iSL]) ) { //patient position fields exist pos = d2.patientPosition[iSL]; if (isSameFloat(pos, d.patientPosition[iSL])) pos = NAN; #ifdef MY_DEBUG if (!isnan(pos)) printMessage("position determined using lastFile %f\n",pos); #endif } if (isnan(pos) &&( !isnan(d.patientPositionLast[iSL]) ) ) { //patient position fields exist pos = d.patientPositionLast[iSL]; if (isSameFloat(pos, d.patientPosition[iSL])) pos = NAN; #ifdef MY_DEBUG if (!isnan(pos)) printMessage("position determined using last (4d) %f\n",pos); #endif } if (isnan(pos) && ( !isnan(d.stackOffcentre[iSL])) ) pos = d.stackOffcentre[iSL]; if (isnan(pos) && ( !isnan(d.lastScanLoc)) ) pos = d.lastScanLoc; //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]); //printMessage("rd %g %g %g\n",readV.v[0],readV.v[1],readV.v[2]); //printMessage("ph %g %g %g\n",phaseV.v[0],phaseV.v[1],phaseV.v[2]); vec3 sliceV = crossProduct(readV, phaseV); //order important: this is our hail mary flip = ((sliceV.v[0]+sliceV.v[1]+sliceV.v[2]) < 0); //printMessage("verify slice dir %g %g %g\n",sliceV.v[0],sliceV.v[1],sliceV.v[2]); if (isVerbose) { //1st pass only if (!d.isDerived) //do not warn user if image is derived printWarning("Unable to determine slice direction: please check whether slices are flipped\n"); else printWarning("Unable to determine slice direction: please check whether slices are flipped (derived image)\n"); } } if (flip) { for (int i = 0; i < 4; i++) R->m[i][2] = -R->m[i][2]; } if (flip) iSL = -iSL; #ifdef MY_DEBUG printMessage("verify slice dir %d %d %d\n",h->dim[1],h->dim[2],h->dim[3]); //reportMat44((char*)"Rout",*R); printMessage("flip = %d\n",flip); printMessage("sliceDir = %d\n",iSL); printMessage(" pos1 = %f\n",pos1); #endif return iSL; } //verify_slice_dir() mat44 noNaN(mat44 Q44, bool isVerbose, bool * isBogus) //simplify any headers that have NaN values { mat44 ret = Q44; bool isNaN44 = false; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (isnan(ret.m[i][j])) isNaN44 = true; if (isNaN44) { *isBogus = true; if (isVerbose) printWarning("Bogus spatial matrix (perhaps non-spatial image): inspect spatial orientation\n"); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (i == j) ret.m[i][j] = 1; else ret.m[i][j] = 0; ret.m[1][1] = -1; } //if isNaN detected return ret; } #define kSessionOK 0 #define kSessionBadMatrix 1 void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose) { bool isBogus = false; mat44 Q44 = noNaN(Q44i, isVerbose, & isBogus); if ((h->session_error == kSessionBadMatrix) || (isBogus)) { h->session_error = kSessionBadMatrix; h->sform_code = NIFTI_XFORM_UNKNOWN; } else h->sform_code = NIFTI_XFORM_SCANNER_ANAT; h->srow_x[0] = Q44.m[0][0]; h->srow_x[1] = Q44.m[0][1]; h->srow_x[2] = Q44.m[0][2]; h->srow_x[3] = Q44.m[0][3]; h->srow_y[0] = Q44.m[1][0]; h->srow_y[1] = Q44.m[1][1]; h->srow_y[2] = Q44.m[1][2]; h->srow_y[3] = Q44.m[1][3]; h->srow_z[0] = Q44.m[2][0]; h->srow_z[1] = Q44.m[2][1]; h->srow_z[2] = Q44.m[2][2]; h->srow_z[3] = Q44.m[2][3]; float dumdx, dumdy, dumdz; nifti_mat44_to_quatern( Q44 , &h->quatern_b, &h->quatern_c, &h->quatern_d,&h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz,&h->pixdim[0]) ; h->qform_code = h->sform_code; } //setQSForm() #ifdef my_unused ivec3 maxCol(mat33 R) { //return index of maximum column in 3x3 matrix, e.g. [1 0 0; 0 1 0; 0 0 1] -> 1,2,3 ivec3 ixyz; //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]); //ixyz.v[0] : row with largest value in column 1 ixyz.v[0] = 1; if ((foo.m[1][0] > foo.m[0][0]) && (foo.m[1][0] >= foo.m[2][0])) ixyz.v[0] = 2; //2nd column largest column else if ((foo.m[2][0] > foo.m[0][0]) && (foo.m[2][0] > foo.m[1][0])) ixyz.v[0] = 3; //3rd column largest column //ixyz.v[1] : row with largest value in column 2, but not the same row as ixyz.v[1] if (ixyz.v[0] == 1) { ixyz.v[1] = 2; if (foo.m[2][1] > foo.m[1][1]) ixyz.v[1] = 3; } else if (ixyz.v[0] == 2) { ixyz.v[1] = 1; if (foo.m[2][1] > foo.m[0][1]) ixyz.v[1] = 3; } else { //ixyz.v[0] == 3 ixyz.v[1] = 1; if (foo.m[1][1] > foo.m[0][1]) ixyz.v[1] = 2; } //ixyz.v[2] : 3rd row, constrained by previous rows ixyz.v[2] = 6 - ixyz.v[1] - ixyz.v[0];//sum of 1+2+3 return ixyz; } int sign(float x) { //returns -1,0,1 depending on if X is less than, equal to or greater than zero if (x < 0) return -1; else if (x > 0) return 1; return 0; } // Subfunction: get dicom xform matrix and related info // This is a direct port of Xiangrui Li's dicm2nii function mat44 xform_mat(struct TDICOMdata d) { vec3 readV = setVec3(d.orient[1],d.orient[2],d.orient[3]); vec3 phaseV = setVec3(d.orient[4],d.orient[5],d.orient[6]); vec3 sliceV = crossProduct(readV ,phaseV); mat33 R; LOAD_MAT33(R, readV.v[0], readV.v[1], readV.v[2], phaseV.v[0], phaseV.v[1], phaseV.v[2], sliceV.v[0], sliceV.v[1], sliceV.v[2]); R = nifti_mat33_transpose(R); //reportMat33((char*)"R",R); ivec3 ixyz = maxCol(R); //printMessage("%d %d %d\n", ixyz.v[0], ixyz.v[1], ixyz.v[2]); int iSL = ixyz.v[2]; // 1/2/3 for Sag/Cor/Tra slice float cosSL = R.m[iSL-1][2]; //printMessage("cosSL\t%g\n", cosSL); //vec3 pixdim = setVec3(d.xyzMM[1], d.xyzMM[2], d.xyzMM[3]); //printMessage("%g %g %g\n", pixdim.v[0], pixdim.v[1], pixdim.v[2]); mat33 pixdim; LOAD_MAT33(pixdim, d.xyzMM[1], 0.0, 0.0, 0.0, d.xyzMM[2], 0.0, 0.0, 0.0, d.xyzMM[3]); R = nifti_mat33_mul(R, pixdim); //reportMat33((char*)"R",R); mat44 R44; LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.patientPosition[1], R.m[1][0], R.m[1][1], R.m[1][2], d.patientPosition[2], R.m[2][0], R.m[2][1], R.m[2][2], d.patientPosition[3]); //reportMat44((char*)"R",R44); //rest are former: R = verify_slice_dir(R, s, dim, iSL) if ((d.xyzDim[3]<2) && (d.CSA.mosaicSlices < 2)) return R44; //don't care direction for single slice vec3 dim = setVec3(d.xyzDim[1], d.xyzDim[2], d.xyzDim[3]); if (d.CSA.mosaicSlices > 1) { //Siemens mosaic: use dim(1) since no transpose to img float nRowCol = ceil(sqrt((double) d.CSA.mosaicSlices)); dim.v[0] = dim.v[0] / nRowCol; dim.v[1] = dim.v[1] / nRowCol; dim.v[2] = d.CSA.mosaicSlices; vec4 dim4 = setVec4((nRowCol-1)*dim.v[0]/2.0f, (nRowCol-1)*dim.v[1]/2.0f, 0); vec4 offset = nifti_vect44mat44_mul(dim4, R44 ); //printMessage("%g %g %g\n", dim.v[0], dim.v[1], dim.v[2]); //printMessage("%g %g %g\n", dim4.v[0], dim4.v[1], dim4.v[2]); //printMessage("%g %g %g %g\n", offset.v[0], offset.v[1], offset.v[2], offset.v[3]); //printMessage("nRowCol\t%g\n", nRowCol); R44.m[0][3] = offset.v[0]; R44.m[1][3] = offset.v[1]; R44.m[2][3] = offset.v[2]; //R44.m[3][3] = offset.v[3]; if (sign(d.CSA.sliceNormV[iSL]) != sign(cosSL)) { R44.m[0][2] = -R44.m[0][2]; R44.m[1][2] = -R44.m[1][2]; R44.m[2][2] = -R44.m[2][2]; R44.m[3][2] = -R44.m[3][2]; } //reportMat44((char*)"iR44",R44); return R44; } else if (true) { //SliceNormalVector TO DO printMessage("Not completed"); exit(2); return R44; } printMessage("Unable to determine spatial transform\n"); exit(1); } mat44 set_nii_header(struct TDICOMdata d) { mat44 R = xform_mat(d); //R(1:2,:) = -R(1:2,:); % dicom LPS to nifti RAS, xform matrix before reorient for (int i=0; i<2; i++) for(int j=0; j<4; j++) R.m[i][j] = -R.m[i][j]; #ifdef MY_DEBUG reportMat44((char*)"R44",R); #endif } #endif // This code predates Xiangrui Li's set_nii_header function mat44 set_nii_header_x(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int* sliceDir, int isVerbose) { *sliceDir = 0; mat44 Q44 = nifti_dicom2mat(d.orient, d.patientPosition, d.xyzMM); if (d.isSegamiOasis == true) { //Segami reconstructions appear to disregard DICOM spatial parameters: assume center of volume is isocenter and no table tilt // Consider sample image with d.orient (0020,0037) = -1 0 0; 0 1 0: this suggests image RAI (L->R, P->A, S->I) but the vendors viewing software suggests LPS //Perhaps we should ignore 0020,0037 and 0020,0032 as they are hidden in sequence 0054,0022, but in this case no positioning is provided // http://www.cs.ucl.ac.uk/fileadmin/cmic/Documents/DavidAtkinson/DICOM.pdf // https://www.slicer.org/wiki/Coordinate_systems LOAD_MAT44(Q44, -h->pixdim[1],0,0,0, 0,-h->pixdim[2],0,0, 0,0,h->pixdim[3],0); //X and Y dimensions flipped in NIfTI (RAS) vs DICOM (LPS) vec4 originVx = setVec4( (h->dim[1]+1.0f)/2.0f, (h->dim[2]+1.0f)/2.0f, (h->dim[3]+1.0f)/2.0f); vec4 originMm = nifti_vect44mat44_mul(originVx, Q44); for (int i = 0; i < 3; i++) Q44.m[i][3] = -originMm.v[i]; //set origin to center voxel if (isVerbose) { //printMessage("origin (vx) %g %g %g\n",originVx.v[0],originVx.v[1],originVx.v[2]); //printMessage("origin (mm) %g %g %g\n",originMm.v[0],originMm.v[1],originMm.v[2]); printWarning("Segami coordinates defy DICOM convention, please check orientation\n"); } return Q44; } //next line only for Siemens mosaic: ignore for UIH grid // https://github.com/xiangruili/dicm2nii/commit/47ad9e6d9bc8a999344cbd487d602d420fb1509f if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.mosaicSlices > 1)) { double nRowCol = ceil(sqrt((double) d.CSA.mosaicSlices)); double lFactorX = (d.xyzDim[1] -(d.xyzDim[1]/nRowCol) )/2.0; double lFactorY = (d.xyzDim[2] -(d.xyzDim[2]/nRowCol) )/2.0; Q44.m[0][3] =(float)((Q44.m[0][0]*lFactorX)+(Q44.m[0][1]*lFactorY)+Q44.m[0][3]); Q44.m[1][3] = (float)((Q44.m[1][0] * lFactorX) + (Q44.m[1][1] * lFactorY) + Q44.m[1][3]); Q44.m[2][3] = (float)((Q44.m[2][0] * lFactorX) + (Q44.m[2][1] * lFactorY) + Q44.m[2][3]); for (int c=0; c<2; c++) for (int r=0; r<4; r++) Q44.m[c][r] = -Q44.m[c][r]; mat33 Q; LOAD_MAT33(Q, d.orient[1], d.orient[4],d.CSA.sliceNormV[1], d.orient[2],d.orient[5],d.CSA.sliceNormV[2], d.orient[3],d.orient[6],d.CSA.sliceNormV[3]); if (nifti_mat33_determ(Q) < 0) { //Siemens sagittal are R>>L, whereas NIfTI is L>>R, we retain Siemens order on disk so ascending is still ascending, but we need to have the spatial transform reflect this. mat44 det; *sliceDir = kSliceOrientMosaicNegativeDeterminant; //we need to handle DTI vectors accordingly LOAD_MAT44(det, 1.0l,0.0l,0.0l,0.0l, 0.0l,1.0l,0.0l,0.0l, 0.0l,0.0l,-1.0l,0.0l); //patient_to_tal.m[2][3] = 1-d.CSA.MosaicSlices; Q44 = nifti_mat44_mul(Q44,det); } } else { //not a mosaic *sliceDir = verify_slice_dir(d, d2, h, &Q44, isVerbose); for (int c=0; c<4; c++)// LPS to nifti RAS, xform matrix before reorient for (int r=0; r<2; r++) //swap rows 1 & 2 Q44.m[r][c] = - Q44.m[r][c]; } #ifdef MY_DEBUG reportMat44((char*)"Q44",Q44); #endif return Q44; } int headerDcm2NiiSForm(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //fill header s and q form //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c //returns sliceDir: 0=unknown,1=sag,2=coro,3=axial,-=reversed slices int sliceDir = 0; if (h->dim[3] < 2) { mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); setQSForm(h,Q44, isVerbose); return sliceDir; //don't care direction for single slice } h->sform_code = NIFTI_XFORM_UNKNOWN; h->qform_code = NIFTI_XFORM_UNKNOWN; bool isOK = false; for (int i = 1; i <= 6; i++) if (d.orient[i] != 0.0) isOK = true; if (!isOK) { //we will have to guess, assume axial acquisition saved in standard Siemens style? d.orient[1] = 1.0f; d.orient[2] = 0.0f; d.orient[3] = 0.0f; d.orient[1] = 0.0f; d.orient[2] = 1.0f; d.orient[3] = 0.0f; if ((d.isDerived) || ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3) && (d.manufacturer == kMANUFACTURER_SIEMENS))) { printMessage("Unable to determine spatial orientation: 0020,0037 missing (probably not a problem: derived image)\n"); } else { printMessage("Unable to determine spatial orientation: 0020,0037 missing (Type 1 attribute: not a valid DICOM) Series %ld\n", d.seriesNum); } } mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); setQSForm(h,Q44, isVerbose); return sliceDir; } //headerDcm2NiiSForm() int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //final pass after de-mosaic char txt[1024] = {""}; if (h->slice_code == NIFTI_SLICE_UNKNOWN) h->slice_code = d.CSA.sliceOrder; if (h->slice_code == NIFTI_SLICE_UNKNOWN) h->slice_code = d2.CSA.sliceOrder; //sometimes the first slice order is screwed up https://github.com/eauerbach/CMRR-MB/issues/29 if (d.modality == kMODALITY_MR) sprintf(txt, "TE=%.2g;Time=%.3f", d.TE,d.acquisitionTime); else sprintf(txt, "Time=%.3f", d.acquisitionTime); if (d.CSA.phaseEncodingDirectionPositive >= 0) { char dtxt[1024] = {""}; sprintf(dtxt, ";phase=%d", d.CSA.phaseEncodingDirectionPositive); strcat(txt,dtxt); } //from dicm2nii 20151117 InPlanePhaseEncodingDirection if (d.phaseEncodingRC =='R') h->dim_info = (3 << 4) + (1 << 2) + 2; if (d.phaseEncodingRC =='C') h->dim_info = (3 << 4) + (2 << 2) + 1; if (d.CSA.multiBandFactor > 1) { char dtxt[1024] = {""}; sprintf(dtxt, ";mb=%d", d.CSA.multiBandFactor); strcat(txt,dtxt); } // GCC 8 warns about truncation using snprintf; using strncpy instead seems to keep it happy // snprintf(h->descrip,80, "%s",txt); strncpy(h->descrip, txt, 79); h->descrip[79] = '\0'; if (strlen(d.imageComments) > 0) snprintf(h->aux_file,24,"%.23s",d.imageComments); return headerDcm2NiiSForm(d,d2, h, isVerbose); } //headerDcm2Nii2() int dcmStrLen (int len, int kMaxLen) { if (len < kMaxLen) return len+1; else return kMaxLen; } //dcmStrLen() struct TDICOMdata clear_dicom_data() { struct TDICOMdata d; //d.dti4D = NULL; d.locationsInAcquisition = 0; d.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.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.studyID, ""); strcpy(d.studyInstanceUID, ""); strcpy(d.bodyPartExamined,""); strcpy(d.coilName, ""); strcpy(d.coilElements, ""); 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.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.phaseFieldofView = 0.0; d.dwellTime = 0; d.protocolBlockStartGE = 0; d.protocolBlockLengthGE = 0; d.phaseEncodingSteps = 0; d.coilCrc = 0; d.accelFactPE = 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.radionuclideTotalDose = 0.0; d.radionuclideHalfLife = 0.0; d.doseCalibrationFactor = 0.0; d.ecat_isotope_halflife = 0.0; d.ecat_dosage = 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.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.bitsAllocated = 16;//bits d.bitsStored = 0; d.samplesPerPixel = 1; 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.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] = 1.0; d.CSA.sliceNormV[2] = 0.0; d.CSA.sliceNormV[3] = 0.0; d.CSA.sliceOrder = NIFTI_SLICE_UNKNOWN; d.CSA.slice_start = 0; d.CSA.slice_end = 0; d.CSA.protocolSliceNumber1 = 0; d.CSA.phaseEncodingDirectionPositive = -1; //unknown d.CSA.isPhaseMap = false; d.CSA.multiBandFactor = 1; d.CSA.SeriesHeader_offset = 0; d.CSA.SeriesHeader_length = 0; return d; } //clear_dicom_data() int isdigitdot(int c) { //returns true if digit or '.' if (c == '.') return 1; return isdigit(c); } void dcmStrDigitsDotOnlyKey(char key, char* lStr) { //e.g. string "F:2.50" returns 2.50 if key==":" size_t len = strlen(lStr); if (len < 1) return; bool isKey = false; for (int i = 0; i < (int) len; i++) { if (!isdigitdot(lStr[i]) ) { isKey = (lStr[i] == key); lStr[i] = ' '; } else if (!isKey) lStr[i] = ' '; } } //dcmStrDigitsOnlyKey() void dcmStrDigitsOnlyKey(char key, char* lStr) { //e.g. string "p2s3" returns 2 if key=="p" and 3 if key=="s" size_t len = strlen(lStr); if (len < 1) return; bool isKey = false; for (int i = 0; i < (int) len; i++) { if (!isdigit(lStr[i]) ) { isKey = (lStr[i] == key); lStr[i] = ' '; } else if (!isKey) lStr[i] = ' '; } } //dcmStrDigitsOnlyKey() void dcmStrDigitsOnly(char* lStr) { //e.g. change "H11" to " 11" size_t len = strlen(lStr); if (len < 1) return; for (int i = 0; i < (int) len; i++) if (!isdigit(lStr[i]) ) lStr[i] = ' '; } // Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ uint32_t mz_crc32(unsigned char *ptr, uint32_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; //#ifdef _MSC_VER char * cString = (char *)malloc(sizeof(char) * (lLength + 1)); //#else // char cString[lLength + 1]; //#endif cString[lLength] =0; memcpy(cString, (char*)&lBuffer[0], lLength); //memcpy(cString, test, lLength); //printMessage("X%dX\n", (unsigned char)d.patientName[1]); 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]=='*') || (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--; //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; //#ifdef _MSC_VER free(cString); //#endif } //dcmStr() inline bool littleEndianPlatform () { uint32_t value = 1; return (*((char *) &value) == 1); } float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) {//read binary 32-bit float //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian bool swap = (littleEndian != littleEndianPlatform()); float retVal = 0; if (lByteLength < 4) return retVal; memcpy(&retVal, (char*)&lBuffer[0], 4); if (!swap) return retVal; float swapVal; char *inFloat = ( char* ) & retVal; char *outFloat = ( char* ) & swapVal; outFloat[0] = inFloat[3]; outFloat[1] = inFloat[2]; outFloat[2] = inFloat[1]; outFloat[3] = inFloat[0]; //printMessage("swapped val = %f\n",swapVal); return swapVal; } //dcmFloat() double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) {//read binary 64-bit float //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian bool swap = (littleEndian != littleEndianPlatform()); double retVal = 0.0f; if (lByteLength < 8) return retVal; memcpy(&retVal, (char*)&lBuffer[0], 8); if (!swap) return retVal; char *floatToConvert = ( char* ) & lBuffer; char *returnFloat = ( char* ) & retVal; //swap the bytes into a temporary buffer returnFloat[0] = floatToConvert[7]; returnFloat[1] = floatToConvert[6]; returnFloat[2] = floatToConvert[5]; returnFloat[3] = floatToConvert[4]; returnFloat[4] = floatToConvert[3]; returnFloat[5] = floatToConvert[2]; returnFloat[6] = floatToConvert[1]; returnFloat[7] = floatToConvert[0]; //printMessage("swapped val = %f\n",retVal); return retVal; } //dcmFloatDouble() 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() /* //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 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 ret = kMANUFACTURER_UNKNOWN; cString[lByteLength] =0; memcpy(cString, (char*)&lBuffer[0], lByteLength); //printMessage("MANU %s\n",cString); 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])== 'P') && (toupper(cString[1])== 'H')) ret = kMANUFACTURER_PHILIPS; if ((toupper(cString[0])== 'T') && (toupper(cString[1])== 'O')) ret = kMANUFACTURER_TOSHIBA; if ((toupper(cString[0])== 'U') && (toupper(cString[1])== 'I')) ret = kMANUFACTURER_UIH; if ((toupper(cString[0])== 'B') && (toupper(cString[1])== 'R')) ret = kMANUFACTURER_BRUKER; //#ifdef _MSC_VER free(cString); //#endif return ret; } //dcmStrManufacturer float csaMultiFloat (unsigned char buff[], int nItems, float Floats[], int *ItemsOK) { //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] //if lnItems == 1, returns first item, if lnItems > 1 returns index of final successful conversion TCSAitem itemCSA; *ItemsOK = 0; if (nItems < 1) return 0.0f; Floats[1] = 0; int lPos = 0; for (int lI = 1; lI <= nItems; lI++) { memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); lPos +=sizeof(itemCSA); // Storage order is always little-endian, so byte-swap required values if necessary if (!littleEndianPlatform()) nifti_swap_4bytes(1, &itemCSA.xx2_Len); if (itemCSA.xx2_Len > 0) { char * cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len)); memcpy(cString, &buff[lPos], itemCSA.xx2_Len); //TPX memcpy(&cString, &buff[lPos], sizeof(cString)); lPos += ((itemCSA.xx2_Len +3)/4)*4; //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString); Floats[lI] = (float) atof(cString); *ItemsOK = lI; //some sequences have store empty items free(cString); } } //for each item return Floats[1]; } //csaMultiFloat() bool csaIsPhaseMap (unsigned char buff[], int nItems) { //returns true if the tag "ImageHistory" has an item named "CC:ComplexAdd" TCSAitem itemCSA; if (nItems < 1) return false; int lPos = 0; for (int lI = 1; lI <= nItems; lI++) { memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); lPos +=sizeof(itemCSA); // Storage order is always little-endian, so byte-swap required values if necessary if (!littleEndianPlatform()) nifti_swap_4bytes(1, &itemCSA.xx2_Len); if (itemCSA.xx2_Len > 0) { //#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; } //csaIsPhaseMap() //int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, int isVerbose, struct TDTI4D *dti4D) { int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, int isVerbose) { //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; bool is3D = false; 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) 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, "Actual3DImaPartNumber") == 0) && (tagCSA.nitems > 0) ) is3D = true; else if ((strcmp(tagCSA.name, "MosaicRefAcqTimes") == 0) && (tagCSA.nitems > 3) ){ float * sliceTimes = (float *)malloc(sizeof(float) * (tagCSA.nitems + 1)); csaMultiFloat (&buff[lPos], tagCSA.nitems,sliceTimes, &itemsOK); float maxTimeValue, minTimeValue, timeValue1; for (int z = 0; z < kMaxEPI3D; z++) CSA->sliceTiming[z] = -1.0; if (itemsOK <= kMaxEPI3D) { for (int z = 1; z <= itemsOK; z++) CSA->sliceTiming[z-1] = sliceTimes[z]; } else printError("Please increase kMaxEPI3D and recompile\n"); CSA->multiBandFactor = 1; timeValue1 = sliceTimes[1]; int nTimeZero = 0; if (sliceTimes[1] == 0) nTimeZero++; int minTimeIndex = 1; int maxTimeIndex = minTimeIndex; minTimeValue = sliceTimes[1]; maxTimeValue = minTimeValue; if (isVerbose) printMessage(" sliceTimes %g\t", sliceTimes[1]); for (int z = 2; z <= itemsOK; z++) { //find index and value of fastest time if (isVerbose) printMessage("%g\t", sliceTimes[z]); if (sliceTimes[z] == 0) nTimeZero++; if (sliceTimes[z] < minTimeValue) { minTimeValue = sliceTimes[z]; minTimeIndex = (float) z; } if (sliceTimes[z] > maxTimeValue) { maxTimeValue = sliceTimes[z]; maxTimeIndex = (float) z; } if (sliceTimes[z] == timeValue1) CSA->multiBandFactor++; } if (isVerbose) printMessage("\n"); CSA->slice_start = minTimeIndex -1; CSA->slice_end = maxTimeIndex -1; if (minTimeIndex == 2) CSA->sliceOrder = NIFTI_SLICE_ALT_INC2;// e.g. 3,1,4,2 else if (minTimeIndex == (itemsOK-1)) CSA->sliceOrder = NIFTI_SLICE_ALT_DEC2;// e.g. 2,4,1,3 or 5,2,4,1,3 else if ((minTimeIndex == 1) && (sliceTimes[2] < sliceTimes[3])) CSA->sliceOrder = NIFTI_SLICE_SEQ_INC; // e.g. 1,2,3,4 else if ((minTimeIndex == 1) && (sliceTimes[2] > sliceTimes[3])) CSA->sliceOrder = NIFTI_SLICE_ALT_INC; //e.g. 1,3,2,4 else if ((minTimeIndex == itemsOK) && (sliceTimes[itemsOK-2] > sliceTimes[itemsOK-1])) CSA->sliceOrder = NIFTI_SLICE_SEQ_DEC; //e.g. 4,3,2,1 or 5,4,3,2,1 else if ((minTimeIndex == itemsOK) && (sliceTimes[itemsOK-2] < sliceTimes[itemsOK-1])) CSA->sliceOrder = NIFTI_SLICE_ALT_DEC; //e.g. 4,2,3,1 or 3,5,2,4,1 else { /*NSMutableArray *sliceTimesNS = [NSMutableArray arrayWithCapacity:tagCSA.nitems]; for (int z = 1; z <= itemsOK; z++) [sliceTimesNS addObject:[NSNumber numberWithFloat:sliceTimes[z]]]; NSLog(@" Warning: unable to determine slice order for %lu slice mosaic: %@",(unsigned long)[sliceTimesNS count],sliceTimesNS ); */ printWarning("Unable to determine slice order from CSA tag MosaicRefAcqTimes\n"); } if ((CSA->sliceOrder != NIFTI_SLICE_UNKNOWN) && (nTimeZero > 1)) { if (isVerbose) printMessage(" Multiband x%d sequence: setting slice order as UNKNOWN (instead of %d)\n", nTimeZero, CSA->sliceOrder); CSA->sliceOrder = NIFTI_SLICE_UNKNOWN; } //#ifdef _MSC_VER free(sliceTimes); //#endif } 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() //int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, int isVerbose) { int readProtocolDataBlockGE(unsigned char *buff, int lLength, struct TCSAdata *CSA, int isVerbose) { return EXIT_SUCCESS; } void dcmMultiShorts (int lByteLength, unsigned char lBuffer[], int lnShorts, uint16_t *lShorts, bool littleEndian) { //read array of unsigned shorts US http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html if ((lnShorts < 1) || (lByteLength != (lnShorts * 2))) return; memcpy(&lShorts[0], (uint16_t *)&lBuffer[0], lByteLength); bool swap = (littleEndian != littleEndianPlatform()); if (swap) nifti_swap_2bytes(lnShorts, &lShorts[0]); } //dcmMultiShorts() void dcmMultiLongs (int lByteLength, unsigned char lBuffer[], int lnLongs, uint32_t *lLongs, bool littleEndian) { //read array of unsigned longs UL http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html if((lnLongs < 1) || (lByteLength != (lnLongs * 4))) return; memcpy(&lLongs[0], (uint32_t *)&lBuffer[0], lByteLength); bool swap = (littleEndian != littleEndianPlatform()); if (swap) nifti_swap_4bytes(lnLongs, &lLongs[0]); } //dcmMultiLongs() void dcmMultiFloat (int lByteLength, char lBuffer[], int lnFloats, float *lFloats) { //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] if ((lnFloats < 1) || (lByteLength < 1)) return; //#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 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 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 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; //TR reported in msec, time is in sec h->dim[1] = d.xyzDim[1]; h->dim[2] = d.xyzDim[2]; h->dim[3] = d.xyzDim[3]; h->dim[4] = d.xyzDim[4]; h->dim[5] = 1; h->dim[6] = 1; h->dim[7] = 1; if (h->dim[4] < 2) h->dim[0] = 3; else h->dim[0] = 4; for (int i = 0; i <= 3; i++) { h->srow_x[i] = 0.0f; h->srow_y[i] = 0.0f; h->srow_z[i] = 0.0f; } h->slice_start = 0; h->slice_end = 0; h->srow_x[0] = -1; h->srow_y[2] = 1; h->srow_z[1] = -1; h->srow_x[3] = ((float) h->dim[1] / 2); h->srow_y[3] = -((float)h->dim[3] / 2); h->srow_z[3] = ((float)h->dim[2] / 2); h->qform_code = NIFTI_XFORM_UNKNOWN; h->sform_code = NIFTI_XFORM_UNKNOWN; h->toffset = 0; h->intent_code = NIFTI_INTENT_NONE; h->dim_info = 0; //Freq, Phase and Slice all unknown h->xyzt_units = NIFTI_UNITS_MM + NIFTI_UNITS_SEC; h->slice_duration = 0; //avoid +inf/-inf, NaN h->intent_p1 = 0; //avoid +inf/-inf, NaN h->intent_p2 = 0; //avoid +inf/-inf, NaN h->intent_p3 = 0; //avoid +inf/-inf, NaN h->pixdim[0] = 1; //QFactor should be 1 or -1 h->sizeof_hdr = 348; //used to signify header does not need to be byte-swapped h->slice_code = d.CSA.sliceOrder; if (isComputeSForm) headerDcm2Nii2(d, d, h, false); return EXIT_SUCCESS; } // headerDcm2Nii() bool isFloatDiff (float a, float b) { return (fabs (a - b) > FLT_EPSILON); } //isFloatDiff() mat33 nifti_mat33_reorder_cols( mat33 m, ivec3 v ) { // matlab equivalent ret = m(:, v); where v is 1,2,3 [INDEXED FROM ONE!!!!] mat33 ret; for (int r=0; r<3; r++) { for(int c=0; c<3; c++) ret.m[r][c] = m.m[r][v.v[c]-1]; } return ret; } //nifti_mat33_reorder_cols() void changeExt (char *file_name, const char* ext) { char *p_extension; p_extension = strrchr(file_name, '.'); //if ((p_extension > file_name) && (strlen(ext) < 1)) // p_extension--; if (p_extension) strcpy(++p_extension, ext); } //changeExt() void cleanStr(char* lOut) { //e.g. strings such as image comments with special characters (e.g. "G/6/2009") can disrupt file saves size_t lLength = strlen(lOut); if (lLength < 1) return; char * cString = (char *)malloc(sizeof(char) * (lLength + 1)); cString[lLength] =0; memcpy(cString, (char*)&lOut[0], lLength); for (int i = 0; i < lLength; i++) //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 if (cString[i]< 1) { unsigned char c = (unsigned char)cString[i]; if ((c >= 192) && (c <= 198)) cString[i] = 'A'; if (c == 199) cString[i] = 'C'; if ((c >= 200) && (c <= 203)) cString[i] = 'E'; if ((c >= 204) && (c <= 207)) cString[i] = 'I'; if (c == 208) cString[i] = 'D'; if (c == 209) cString[i] = 'N'; if ((c >= 210) && (c <= 214)) cString[i] = 'O'; if (c == 215) cString[i] = 'x'; if (c == 216) cString[i] = 'O'; if ((c >= 217) && (c <= 220)) cString[i] = 'O'; if (c == 221) cString[i] = 'Y'; if ((c >= 224) && (c <= 230)) cString[i] = 'a'; if (c == 231) cString[i] = 'c'; if ((c >= 232) && (c <= 235)) cString[i] = 'e'; if ((c >= 236) && (c <= 239)) cString[i] = 'i'; if (c == 240) cString[i] = 'o'; if (c == 241) cString[i] = 'n'; if ((c >= 242) && (c <= 246)) cString[i] = 'o'; if (c == 248) cString[i] = 'o'; if ((c >= 249) && (c <= 252)) cString[i] = 'u'; if (c == 253) cString[i] = 'y'; if (c == 255) cString[i] = 'y'; } for (int i = 0; i < lLength; i++) if ((cString[i]<1) || (cString[i]==' ') || (cString[i]==',') || (cString[i]=='^') || (cString[i]=='/') || (cString[i]=='\\') || (cString[i]=='%') || (cString[i]=='*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_'; int len = 1; for (int i = 1; i < lLength; i++) { //remove repeated "_" if ((cString[i-1]!='_') || (cString[i]!='_')) { cString[len] =cString[i]; len++; } } //for each item if (cString[len-1] == '_') len--; cString[len] = 0; //null-terminate, strlcpy does this anyway int maxLen = kDICOMStr; len = dcmStrLen(len, maxLen); if (len == maxLen) { //we need space for null-termination if (cString[len-2] == '_') len = len -2; } memcpy(lOut,cString,len-1); lOut[len-1] = 0; free(cString); } //cleanStr() int isSameFloatGE (float a, float b) { //Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! //return (a == b); //niave approach does not have any tolerance for rounding errors return (fabs (a - b) <= 0.0001); } struct TDICOMdata nii_readParRec (char * parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase) { struct TDICOMdata d = clear_dicom_data(); strcpy(d.protocolName, ""); //erase dummy with empty strcpy(d.seriesDescription, ""); //erase dummy with empty strcpy(d.sequenceName, ""); //erase dummy with empty strcpy(d.scanningSequence, ""); FILE *fp = fopen(parname, "r"); if (fp == NULL) return d; #define LINESZ 2048 #define kSlice 0 #define kEcho 1 #define kDyn 2 #define kCardiac 3 #define kImageType 4 #define kSequence 5 #define kIndex 6 //V3 only identical for columns 1..6 #define kBitsPerVoxel 7 //V3: not per slice: "Image pixel size [8 or 16 bits]" #define kXdim 9 //V3: not per slice: "Recon resolution (x, y)" #define kYdim 10 //V3: not per slice: "Recon resolution (x, y)" int kRI = 11; //V3: 7 int kRS = 12; //V3: 8 int kSS = 13; //V3: 9 int kAngulationAPs = 16; //V3: 12 int kAngulationFHs = 17; //V3: 13 int kAngulationRLs = 18; //V3: 14 int kPositionAP = 19; //V3: 15 int kPositionFH = 20; //V3: 16 int kPositionRL = 21; //V3: 17 #define kThickmm 22 //V3: not per slice: "Slice thickness [mm]" #define kGapmm 23 //V3: not per slice: "Slice gap [mm]" int kSliceOrients = 25; //V3: 19 int kXmm = 28; //V3: 22 int kYmm = 29; //V3: 23 int kTEcho = 30; //V3: 24 int kDynTime = 31; //V3: 25 int kTriggerTime = 32; //V3: 26 int kbval = 33; //V3: 27 //the following do not exist in V3 #define kInversionDelayMs 40 #define kbvalNumber 41 #define kGradientNumber 42 //the following do not exist in V40 or earlier #define kv1 47 #define kv2 45 #define kv3 46 //the following do not exist in V41 or earlier #define kASL 48 #define kMaxImageType 4 //4 observed image types: real, imag, mag, phase (in theory also subsequent calculation such as B1) printWarning("dcm2niix PAR is not actively supported (hint: use dicm2nii)\n"); if (isReadPhase) printWarning(" Reading phase images from PAR/REC\n"); char buff[LINESZ]; //next values: PAR V3 only int v3BitsPerVoxel = 16; //V3: not per slice: "Image pixel size [8 or 16 bits]" int v3Xdim = 128; //not per slice: "Recon resolution (x, y)" int v3Ydim = 128; //V3: not per slice: "Recon resolution (x, y)" float v3Thickmm = 2.0; //V3: not per slice: "Slice thickness [mm]" float v3Gapmm = 0.0; //V3: not per slice: "Slice gap [mm]" //from top of header int maxNumberOfDiffusionValues = 1; int maxNumberOfGradientOrients = 1; int maxNumberOfCardiacPhases = 1; int maxNumberOfEchoes = 1; int maxNumberOfDynamics = 1; int maxNumberOfMixes = 1; int maxNumberOfLabels = 1;//Number of label types <0=no ASL> float maxBValue = 0.0f; float maxDynTime = 0.0f; float minDynTime = 999999.0f; float TE = 0.0; int minDyn = 32767; int maxDyn = 0; int minSlice = 32767; int maxSlice = 0; bool ADCwarning = false; bool isTypeWarning = false; int numSlice2D = 0; int prevDyn = -1; bool dynNotAscending = false; int parVers = 0; 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); 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 kMaxDTI4D to be more than %d\n", num2DExpected); printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients,maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); free (cols); return d; } } if (strcmp(Comment[1], "TRYOUT") == 0) { //sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3],Comment[4], Comment[5],Comment[6]); parVers = (int)round(atof(Comment[6])*10); //4.2 = 42 etc if (parVers <= 29) { printMessage("Unsupported old PAR version %0.2f (use dicm2nii)\n", parVers/10.0); return d; //nCols = 26; //e.g. PAR 3.0 has 26 relevant columns } if (parVers < 40) { nCols = 29; // PAR 3.0? kRI = 7; kRS = 8; kSS = 9; kAngulationAPs = 12; kAngulationFHs = 13; kAngulationRLs = 14; kPositionAP = 15; kPositionFH = 16; kPositionRL = 17; kSliceOrients = 19; kXmm = 22; kYmm = 23; kTEcho = 24; kDynTime = 25; kTriggerTime = 26; kbval = 27; } else if (parVers < 41) nCols = kv1; //e.g PAR 4.0 else if (parVers < 42) nCols = kASL; //e.g. PAR 4.1 - last column is final diffusion b-value else nCols = kMaxCols; //e.g. PAR 4.2 } //the following do not exist in V3 p = fgets (buff, LINESZ, fp);//get next line continue; } //process '#' comment if (buff[0] == '.') { //tag char Comment[9][50]; for (int i = 0; i < 9; i++) strcpy(Comment[i], ""); sscanf(buff, ". %s %s %s %s %s %s %s %s %s\n", Comment[0], Comment[1],Comment[2], Comment[3], Comment[4], Comment[5], Comment[6], Comment[7], Comment[8]); if ((strcmp(Comment[0], "Acquisition") == 0) && (strcmp(Comment[1], "nr") == 0)) { d.acquNum = atoi( Comment[3]); d.seriesNum = d.acquNum; } if ((strcmp(Comment[0], "Recon") == 0) && (strcmp(Comment[1], "resolution") == 0)) { v3Xdim = (int) atoi(Comment[5]); v3Ydim = (int) atoi(Comment[6]); //printMessage("recon %d,%d\n", v3Xdim,v3Ydim); } if ((strcmp(Comment[1], "pixel") == 0) && (strcmp(Comment[2], "size") == 0)) { v3BitsPerVoxel = (int) atoi(Comment[8]); //printMessage("bits %d\n", v3BitsPerVoxel); } if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "gap") == 0)) { v3Gapmm = (float) atof(Comment[4]); //printMessage("gap %g\n", v3Gapmm); } if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "thickness") == 0)) { v3Thickmm = (float) atof(Comment[4]); //printMessage("thick %g\n", v3Thickmm); } if ((strcmp(Comment[0], "Repetition") == 0) && (strcmp(Comment[1], "time") == 0)) d.TR = (float) atof(Comment[4]); if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "name") == 0)) { strcpy(d.patientName, Comment[3]); strcat(d.patientName, Comment[4]); strcat(d.patientName, Comment[5]); strcat(d.patientName, Comment[6]); strcat(d.patientName, Comment[7]); cleanStr(d.patientName); //printMessage("%s\n",d.patientName); } if ((strcmp(Comment[0], "Technique") == 0) && (strcmp(Comment[1], ":") == 0)) { strcpy(d.patientID, Comment[2]); strcat(d.patientID, Comment[3]); strcat(d.patientID, Comment[4]); strcat(d.patientID, Comment[5]); strcat(d.patientID, Comment[6]); strcat(d.patientID, Comment[7]); cleanStr(d.patientID); } if ((strcmp(Comment[0], "Protocol") == 0) && (strcmp(Comment[1], "name") == 0)) { strcpy(d.protocolName, Comment[3]); strcat(d.protocolName, Comment[4]); strcat(d.protocolName, Comment[5]); strcat(d.protocolName, Comment[6]); strcat(d.protocolName, Comment[7]); cleanStr(d.protocolName); } if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "name") == 0)) { strcpy(d.imageComments, Comment[3]); strcat(d.imageComments, Comment[4]); strcat(d.imageComments, Comment[5]); strcat(d.imageComments, Comment[6]); strcat(d.imageComments, Comment[7]); cleanStr(d.imageComments); } if ((strcmp(Comment[0], "Series") == 0) && (strcmp(Comment[1], "Type") == 0)) { strcpy(d.seriesDescription, Comment[3]); strcat(d.seriesDescription, Comment[4]); strcat(d.seriesDescription, Comment[5]); strcat(d.seriesDescription, Comment[6]); strcat(d.seriesDescription, Comment[7]); cleanStr(d.seriesDescription); } if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "date/time") == 0)) { if ((strlen(Comment[3]) >= 10) && (strlen(Comment[5]) >= 8)) { //DICOM date format is YYYYMMDD, but PAR stores YYYY.MM.DD 2016.03.25 d.studyDate[0] = Comment[3][0]; d.studyDate[1] = Comment[3][1]; d.studyDate[2] = Comment[3][2]; d.studyDate[3] = Comment[3][3]; d.studyDate[4] = Comment[3][5]; d.studyDate[5] = Comment[3][6]; d.studyDate[6] = Comment[3][8]; d.studyDate[7] = Comment[3][9]; d.studyDate[8] = '\0'; //DICOM time format is HHMMSS.FFFFFF, but PAR stores HH:MM:SS, e.g. 18:00:42 or 09:34:16 d.studyTime[0] = Comment[5][0]; d.studyTime[1] = Comment[5][1]; d.studyTime[2] = Comment[5][3]; d.studyTime[3] = Comment[5][4]; d.studyTime[4] = Comment[5][6]; d.studyTime[5] = Comment[5][7]; d.studyTime[6] = '\0'; d.dateTime = (atof(d.studyDate)* 1000000) + atof(d.studyTime); } } if ((strcmp(Comment[0], "Off") == 0) && (strcmp(Comment[1], "Centre") == 0)) { //Off Centre midslice(ap,fh,rl) [mm] d.stackOffcentre[2] = (float) atof(Comment[5]); d.stackOffcentre[3] = (float) atof(Comment[6]); d.stackOffcentre[1] = (float) atof(Comment[7]); } if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "position") == 0)) { //Off Centre midslice(ap,fh,rl) [mm] d.patientOrient[0] = toupper(Comment[3][0]); d.patientOrient[1] = toupper(Comment[4][0]); d.patientOrient[2] = toupper(Comment[5][0]); d.patientOrient[3] = 0; } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "slices/locations") == 0)) { d.xyzDim[3] = atoi(Comment[5]); } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "diffusion") == 0)) { maxNumberOfDiffusionValues = atoi(Comment[6]); //if (maxNumberOfDiffusionValues > 1) maxNumberOfDiffusionValues -= 1; //if two listed, one is B=0 } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "gradient") == 0)) { maxNumberOfGradientOrients = atoi(Comment[6]); //Warning ISOTROPIC scans may be stored that are not reported here! 32 directions plus isotropic = 33 volumes } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "cardiac") == 0)) { maxNumberOfCardiacPhases = atoi(Comment[6]); } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "echoes") == 0)) { maxNumberOfEchoes = atoi(Comment[5]); if (maxNumberOfEchoes > 1) d.isMultiEcho = true; } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "dynamics") == 0)) { maxNumberOfDynamics = atoi(Comment[5]); } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "mixes") == 0)) { maxNumberOfMixes = atoi(Comment[5]); if (maxNumberOfMixes > 1) printError("maxNumberOfMixes > 1. Please update this software to support these images\n"); } if ((strcmp(Comment[0], "Number") == 0) && (strcmp(Comment[2], "label") == 0)) { maxNumberOfLabels = atoi(Comment[7]); if (maxNumberOfLabels < 1) maxNumberOfLabels = 1; } p = fgets (buff, LINESZ, fp);//get next line continue; } //process '.' tag if (strlen(buff) < 24) { //empty line p = fgets (buff, LINESZ, fp);//get next line continue; } if (parVers < 20) { printError("PAR files should have 'CLINICAL TRYOUT' line with a version from 2.0-4.2: %s\n", parname); free (cols); return d; } for (int i = 0; i <= nCols; i++) cols[i] = strtof(p, &p); // p+1 skip comma, read a float //printMessage("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1],(int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); if ((int)cols[kSlice] == 0) { //line does not contain attributes p = fgets (buff, LINESZ, fp);//get next line continue; } //diskSlice ++; bool isADC = false; if ((maxNumberOfGradientOrients >= 2) && (cols[kbval] > 50) && isSameFloat(0.0, cols[kv1]) && isSameFloat(0.0, cols[kv2]) && isSameFloat(0.0, cols[kv2]) ) { isADC = true; ADCwarning = true; } //printMessage(">>%d %d\n", (int)cols[kSlice], diskSlice); if (numSlice2D < 1) { d.xyzMM[1] = cols[kXmm]; d.xyzMM[2] = cols[kYmm]; if (parVers < 40) { //v3 does things differently //cccc d.xyzDim[1] = v3Xdim; d.xyzDim[2] = v3Ydim; d.xyzMM[3] = v3Thickmm + v3Gapmm; d.bitsAllocated = v3BitsPerVoxel; d.bitsStored = v3BitsPerVoxel; } else { d.xyzDim[1] = (int) cols[kXdim]; d.xyzDim[2] = (int) cols[kYdim]; d.xyzMM[3] = cols[kThickmm] + cols[kGapmm]; d.bitsAllocated = (int) cols[kBitsPerVoxel]; d.bitsStored = (int) cols[kBitsPerVoxel]; } d.patientPosition[1] = cols[kPositionRL]; d.patientPosition[2] = cols[kPositionAP]; d.patientPosition[3] = cols[kPositionFH]; d.angulation[1] = cols[kAngulationRLs]; d.angulation[2] = cols[kAngulationAPs]; d.angulation[3] = cols[kAngulationFHs]; d.sliceOrient = (int) cols[kSliceOrients]; d.TE = cols[kTEcho]; d.echoNum = cols[kEcho]; d.TI = cols[kInversionDelayMs]; d.intenIntercept = cols[kRI]; d.intenScale = cols[kRS]; d.intenScalePhilips = cols[kSS]; } else { if (parVers >= 40) { if ((d.xyzDim[1] != cols[kXdim]) || (d.xyzDim[2] != cols[kYdim]) || (d.bitsAllocated != cols[kBitsPerVoxel]) ) { printError("Slice dimensions or bit depth varies %s\n", parname); printError("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1],(int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); return d; } } if ((d.intenScale != cols[kRS]) || (d.intenIntercept != cols[kRI])) isIntenScaleVaries = true; } if (cols[kImageType] == 0) d.isHasMagnitude = true; if (cols[kImageType] != 0) d.isHasPhase = true; if ((isSameFloat(cols[kImageType],18)) && (!isTypeWarning)) { printWarning("Field map in Hz will be saved as the 'real' image.\n"); isTypeWarning = true; } else if (((cols[kImageType] < 0.0) || (cols[kImageType] > 3.0)) && (!isTypeWarning)) { printError("Unknown type %g: not magnitude[0], real[1], imaginary[2] or phase[3].\n", cols[kImageType]); isTypeWarning = true; } if (cols[kDyn] > maxDyn) maxDyn = (int) cols[kDyn]; if (cols[kDyn] < minDyn) minDyn = (int) cols[kDyn]; if (cols[kDyn] < prevDyn) dynNotAscending = true; prevDyn = cols[kDyn]; if (cols[kDynTime] > maxDynTime) maxDynTime = cols[kDynTime]; if (cols[kDynTime] < minDynTime) minDynTime = cols[kDynTime]; if (cols[kEcho] > maxEcho) maxEcho = cols[kEcho]; if (cols[kCardiac] > maxCardiac) maxCardiac = cols[kCardiac]; if ((cols[kEcho] == 1) && (cols[kDyn] == 1) && (cols[kCardiac] == 1) && (cols[kGradientNumber] == 1)) { if (cols[kSlice] == 1) { d.patientPosition[1] = cols[kPositionRL]; d.patientPosition[2] = cols[kPositionAP]; d.patientPosition[3] = cols[kPositionFH]; } patientPositionNumPhilips++; } if (true) { //for every slice int slice = (int)cols[kSlice]; if (slice < minSlice) minSlice = slice; if (slice > maxSlice) { maxSlice = slice; d.patientPositionLast[1] = cols[kPositionRL]; d.patientPositionLast[2] = cols[kPositionAP]; d.patientPositionLast[3] = cols[kPositionFH]; } int volStep = maxNumberOfDynamics; int vol = ((int)cols[kDyn] - 1); #ifdef old int gradDynVol = (int)cols[kGradientNumber] - 1; if (gradDynVol < 0) gradDynVol = 0; //old PAREC without cols[kGradientNumber] vol = vol + (volStep * (gradDynVol)); if (vol < 0) vol = 0; volStep = volStep * maxNumberOfGradientOrients; int bval = (int)cols[kbvalNumber]; if (bval > 2) //b=0 is 0, b=1000 is 1, b=2000 is 2 - b=0 does not have multiple directions bval = bval - 1; else bval = 1; //if (slice == 1) printMessage("bVal %d bVec %d isADC %d nbVal %d nGrad %d\n",(int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); vol = vol + (volStep * (bval- 1)); volStep = volStep * (maxNumberOfDiffusionValues-1); if (isADC) vol = volStep + (bval-1); #else if (maxNumberOfDiffusionValues > 1) { int grad = (int)cols[kGradientNumber] - 1; if (grad < 0) grad = 0; //old v4 does not have this tag int bval = (int)cols[kbvalNumber] - 1; if (bval < 0) bval = 0; //old v4 does not have this tag if (isADC) vol = vol + (volStep * maxNumberOfDiffusionValues * maxNumberOfGradientOrients) +bval; else vol = vol + (volStep * grad) + (bval * maxNumberOfGradientOrients); volStep = volStep * (maxNumberOfDiffusionValues+1) * maxNumberOfGradientOrients; //if (slice == 1) printMessage("vol %d step %d bVal %d bVec %d isADC %d nbVal %d nGrad %d\n", vol, volStep, (int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); } #endif vol = vol + (volStep * ((int)cols[kEcho] - 1)); volStep = volStep * maxNumberOfEchoes; vol = vol + (volStep * ((int)cols[kCardiac] - 1)); volStep = volStep * maxNumberOfCardiacPhases; int ASL = (int)cols[kASL]; if (ASL < 1) ASL = 1; vol = vol + (volStep * (ASL - 1)); volStep = volStep * maxNumberOfLabels; //if (slice == 1) printMessage("%d\t%d\t%d\t%d\t%d\n", isADC,(int)cols[kbvalNumber], (int)cols[kGradientNumber], bval, vol); if (vol > maxVol) maxVol = vol; bool isReal = (cols[kImageType] == 1); bool isImaginary = (cols[kImageType] == 2); bool isPhase = (cols[kImageType] == 3); if ((cols[kImageType] < 0.0) || (cols[kImageType] > 3.0)) isReal = true; //<- this is not correct, kludge for bug in ROGERS_20180526_WIP_B0_NS_8_1.PAR if (isReal) vol += num3DExpected; if (isImaginary) vol += (2*num3DExpected); if (isPhase) vol += (3*num3DExpected); if (vol >= kMaxDTI4D) { printError("Use dicm2nii or increase kMaxDTI4D (currently %d)to be more than %d\n", kMaxDTI4D, kMaxImageType*num2DExpected); printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); free (cols); return d; } // dti4D->S[vol].V[0] = cols[kbval]; //dti4D->gradDynVol[vol] = gradDynVol; dti4D->TE[vol] = cols[kTEcho]; if (isSameFloatGE(cols[kTEcho], 0)) dti4D->TE[vol] = TE;//kludge for cols[kImageType]==18 where TE set as 0 else TE = cols[kTEcho]; dti4D->triggerDelayTime[vol] = cols[kTriggerTime]; if (dti4D->TE[vol] < 0) dti4D->TE[vol] = 0; //used to detect sparse volumes dti4D->intenIntercept[vol] = cols[kRI]; dti4D->intenScale[vol] = cols[kRS]; dti4D->intenScalePhilips[vol] = cols[kSS]; dti4D->isReal[vol] = isReal; dti4D->isImaginary[vol] = isImaginary; dti4D->isPhase[vol] = isPhase; if ((maxNumberOfGradientOrients > 1) && (parVers > 40)) { dti4D->S[vol].V[0] = cols[kbval]; dti4D->S[vol].V[1] = cols[kv1]; dti4D->S[vol].V[2] = cols[kv2]; dti4D->S[vol].V[3] = cols[kv3]; if ((vol+1) > d.CSA.numDti) d.CSA.numDti = vol+1; } //if (slice == 1) printWarning("%d\n", (int)cols[kEcho]); slice = slice + (vol * d.xyzDim[3]); //offset images by type: mag+0,real+1, imag+2,phase+3 //if (cols[kImageType] != 0) //yikes - phase maps! // slice = slice + numExpected; //printWarning("%d\t%d\n", slice -1, numSlice2D); //printMessage("%d\t%d\t%d\n", numSlice2D, slice, vol); if ((slice >= 0) && (slice < kMaxSlice2D) && (numSlice2D < kMaxSlice2D) && (numSlice2D >= 0)) { dti4D->sliceOrder[slice -1] = numSlice2D; //printMessage("%d\t%d\t%d\n", numSlice2D, slice, (int)cols[kSlice],(int)vol); } numSlice2D++; } //printMessage("%f %f %lu\n",cols[9],cols[kGradientNumber], strlen(buff)) p = fgets (buff, LINESZ, fp);//get next line } free (cols); fclose (fp); if ((parVers <= 0) || (numSlice2D < 1)) { printError("Invalid PAR format header (unable to detect version or slices) %s\n", parname); return d; } d.manufacturer = kMANUFACTURER_PHILIPS; d.isValid = true; d.isSigned = true; //remove unused volumes - this will happen if unless we have all 4 image types: real, imag, mag, phase maxVol = 0; for (int i = 0; i < kMaxDTI4D; i++) { if (dti4D->TE[i] > -1.0) { dti4D->TE[maxVol] = dti4D->TE[i]; dti4D->triggerDelayTime[maxVol] = dti4D->triggerDelayTime[i]; dti4D->intenIntercept[maxVol] = dti4D->intenIntercept[i]; dti4D->intenScale[maxVol] = dti4D->intenScale[i]; dti4D->intenScalePhilips[maxVol] = dti4D->intenScalePhilips[i]; dti4D->isReal[maxVol] = dti4D->isReal[i]; dti4D->isImaginary[maxVol] = dti4D->isImaginary[i]; dti4D->isPhase[maxVol] = dti4D->isPhase[i]; dti4D->S[maxVol].V[0] = dti4D->S[i].V[0]; dti4D->S[maxVol].V[1] = dti4D->S[i].V[1]; dti4D->S[maxVol].V[2] = dti4D->S[i].V[2]; dti4D->S[maxVol].V[3] = dti4D->S[i].V[3]; maxVol = maxVol + 1; } } if (d.CSA.numDti > 0) d.CSA.numDti = maxVol; //e.g. gradient 2 can skip B=0 but include isotropic //remove unused slices - this will happen if unless we have all 4 image types: real, imag, mag, phase 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); d.isValid = false; } 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; } 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; } if ((maxSlice-minSlice+1) != d.xyzDim[3]) { int numSlice = (maxSlice - minSlice)+1; printWarning("Expected %d slices, but found %d (%d..%d). %s\n", d.xyzDim[3], numSlice, minSlice, maxSlice, parname); if (numSlice <= 0) d.isValid = false; d.xyzDim[3] = numSlice; num2DExpected = d.xyzDim[3] * num3DExpected; } if ((maxBValue <= 0.0f) && (maxDyn > minDyn) && (maxDynTime > minDynTime)) { //use max vs min Dyn instead of && (d.CSA.numDti > 1) int numDyn = (maxDyn - minDyn)+1; if (numDyn != maxNumberOfDynamics) { printWarning("Expected %d dynamics, but found %d (%d..%d).\n", maxNumberOfDynamics, numDyn, minDyn, maxDyn); maxNumberOfDynamics = numDyn; num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; num2DExpected = d.xyzDim[3] * num3DExpected; } float TRms = 1000.0f * (maxDynTime - minDynTime) / (float)(numDyn-1); //-1 for fence post //float TRms = 1000.0f * (maxDynTime - minDynTime) / (float)(d.CSA.numDti-1); 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"); 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 try and err, not 100% sure float d2r = (float) (M_PI/180.0); vec3 ca = setVec3(cos(d.angulation[1]*d2r),cos(d.angulation[2]*d2r),cos(d.angulation[3]*d2r)); vec3 sa = setVec3(sin(d.angulation[1]*d2r),sin(d.angulation[2]*d2r),sin(d.angulation[3]*d2r)); mat33 rx,ry,rz; LOAD_MAT33(rx,1.0f, 0.0f, 0.0f, 0.0f, ca.v[0], -sa.v[0], 0.0f, sa.v[0], ca.v[0]); LOAD_MAT33(ry, ca.v[1], 0.0f, sa.v[1], 0.0f, 1.0f, 0.0f, -sa.v[1], 0.0f, ca.v[1]); LOAD_MAT33(rz, ca.v[2], -sa.v[2], 0.0f, sa.v[2], ca.v[2], 0.0f, 0.0f, 0.0f, 1.0f); mat33 R = nifti_mat33_mul( rx,ry ); R = nifti_mat33_mul( R,rz); ivec3 ixyz = setiVec3(1,2,3); if (d.sliceOrient == kSliceOrientSag) { ixyz = setiVec3(2,3,1); for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++) if (c != 1) R.m[r][c] = -R.m[r][c]; //invert first and final columns }else if (d.sliceOrient == kSliceOrientCor) { ixyz = setiVec3(1,3,2); for (int r = 0; r < 3; r++) R.m[r][2] = -R.m[r][2]; //invert rows of final column } R = nifti_mat33_reorder_cols(R,ixyz); //dicom rotation matrix d.orient[1] = R.m[0][0]; d.orient[2] = R.m[1][0]; d.orient[3] = R.m[2][0]; d.orient[4] = R.m[0][1]; d.orient[5] = R.m[1][1]; d.orient[6] = R.m[2][1]; mat33 diag; LOAD_MAT33(diag, d.xyzMM[1],0.0f,0.0f, 0.0f,d.xyzMM[2],0.0f, 0.0f,0.0f, d.xyzMM[3]); R= nifti_mat33_mul( R, diag ); mat44 R44; LOAD_MAT44(R44, R.m[0][0],R.m[0][1],R.m[0][2],d.stackOffcentre[1], R.m[1][0],R.m[1][1],R.m[1][2],d.stackOffcentre[2], R.m[2][0],R.m[2][1],R.m[2][2],d.stackOffcentre[3]); vec3 x; if (parVers > 40) //guess x = setVec3(((float)d.xyzDim[1]-1)/2,((float)d.xyzDim[2]-1)/2,((float)d.xyzDim[3]-1)/2); else x = setVec3((float)d.xyzDim[1]/2,(float)d.xyzDim[2]/2,((float)d.xyzDim[3]-1)/2); mat44 eye; LOAD_MAT44(eye, 1.0f,0.0f,0.0f,x.v[0], 0.0f,1.0f,0.0f,x.v[1], 0.0f,0.0f,1.0f,x.v[2]); eye= nifti_mat44_inverse( eye ); //we wish to compute R/eye, so compute invEye and calculate R*invEye R44= nifti_mat44_mul( R44 , eye ); vec4 y; y.v[0]=0.0f; y.v[1]=0.0f; y.v[2]=(float) d.xyzDim[3]-1.0f; y.v[3]=1.0f; y= nifti_vect44mat44_mul(y, R44 ); int iOri = 2; //for axial, slices are 3rd dimenson (indexed from 0) (k) if (d.sliceOrient == kSliceOrientSag) iOri = 0; //for sagittal, slices are 1st dimension (i) if (d.sliceOrient == kSliceOrientCor) iOri = 1; //for coronal, slices are 2nd dimension (j) if (d.xyzDim[3] > 1) { //detect and fix Philips Bug //Est: assuming "image offcentre (ap,fh,rl in mm )" is correct float stackOffcentreEst[4]; stackOffcentreEst[1] = (d.patientPosition[1]+d.patientPositionLast[1]) * 0.5; stackOffcentreEst[2] = (d.patientPosition[2]+d.patientPositionLast[2]) * 0.5; stackOffcentreEst[3] = (d.patientPosition[3]+d.patientPositionLast[3]) * 0.5; //compute error using 3D pythagorean theorm stackOffcentreEst[0] = sqrt(pow(stackOffcentreEst[1]-d.stackOffcentre[1],2)+pow(stackOffcentreEst[2]-d.stackOffcentre[2],2)+pow(stackOffcentreEst[3]-d.stackOffcentre[3],2) ); //Est: assuming "image offcentre (ap,fh,rl in mm )" is stored in order rl,ap,fh float stackOffcentreRev[4]; stackOffcentreRev[1] = (d.patientPosition[2]+d.patientPositionLast[2]) * 0.5; stackOffcentreRev[2] = (d.patientPosition[3]+d.patientPositionLast[3]) * 0.5; stackOffcentreRev[3] = (d.patientPosition[1]+d.patientPositionLast[1]) * 0.5; //compute error using 3D pythagorean theorm stackOffcentreRev[0] = sqrt(pow(stackOffcentreRev[1]-d.stackOffcentre[1],2)+pow(stackOffcentreRev[2]-d.stackOffcentre[2],2)+pow(stackOffcentreRev[3]-d.stackOffcentre[3],2) ); //detect, report and fix error if ((stackOffcentreEst[0] > 1.0) && (stackOffcentreRev[0] < stackOffcentreEst[0])) { //error detected: the ">1.0" handles the low precision of the "Off Centre" values printMessage("Order of 'image offcentre (ap,fh,rl in mm )' appears incorrect (assuming rl,ap,fh)\n"); printMessage(" err[ap,fh,rl]= %g (%g %g %g) \n",stackOffcentreEst[0],stackOffcentreEst[1],stackOffcentreEst[2],stackOffcentreEst[3]); printMessage(" err[rl,ap,fh]= %g (%g %g %g) \n",stackOffcentreRev[0],stackOffcentreRev[1],stackOffcentreRev[2],stackOffcentreRev[3]); printMessage(" orient\t%d\tOffCentre 1st->mid->nth\t%g\t%g\t%g\t->\t%g\t%g\t%g\t->\t%g\t%g\t%g\t=\t%g\t%s\n",iOri, d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.stackOffcentre[1], d.stackOffcentre[2], d.stackOffcentre[3], d.patientPositionLast[1],d.patientPositionLast[2],d.patientPositionLast[3],(d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]), parname); //correct patientPosition for (int i = 1; i < 4; i++) stackOffcentreRev[i] = d.patientPosition[i]; d.patientPosition[1] = stackOffcentreRev[2]; d.patientPosition[2] = stackOffcentreRev[3]; d.patientPosition[3] = stackOffcentreRev[1]; //correct patientPositionLast for (int i = 1; i < 4; i++) stackOffcentreRev[i] = d.patientPositionLast[i]; d.patientPositionLast[1] = stackOffcentreRev[2]; d.patientPositionLast[2] = stackOffcentreRev[3]; d.patientPositionLast[3] = stackOffcentreRev[1]; } //if bug: report and fix } //if 3D data bool flip = false; //assume head first supine if ((iOri == 0) && (((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) > 0))) flip = true; //6/2018 : TODO, not sure if this is >= or > if ((iOri == 1) && (((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) <= 0))) flip = true; //<= not <, leslie_dti_6_1.PAR if ((iOri == 2) && (((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) <= 0))) flip = true; //<= not <, see leslie_dti_3_1.PAR if (flip) { //if ((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) < 0) { //if (( (y.v[iOri]-R44.m[iOri][3])>0 ) == ( (y.v[iOri]-d.stackOffcentre[iOri+1])>0 ) ) { d.patientPosition[1] = R44.m[0][3]; d.patientPosition[2] = R44.m[1][3]; d.patientPosition[3] = R44.m[2][3]; d.patientPositionLast[1] = y.v[0]; d.patientPositionLast[2] = y.v[1]; d.patientPositionLast[3] = y.v[2]; //printWarning(" Flipping slice order: please verify %s\n", parname); }else { //printWarning(" NOT Flipping slice order: please verify %s\n", parname); d.patientPosition[1] = y.v[0]; d.patientPosition[2] = y.v[1]; d.patientPosition[3] = y.v[2]; d.patientPositionLast[1] = R44.m[0][3]; d.patientPositionLast[2] = R44.m[1][3]; d.patientPositionLast[3] = R44.m[2][3]; } //finish up changeExt (parname, "REC"); #ifndef _MSC_VER //Linux is case sensitive, #include if( access( parname, F_OK ) != 0 ) changeExt (parname, "rec"); #endif d.locationsInAcquisition = d.xyzDim[3]; d.imageStart = 0; if (d.CSA.numDti >= kMaxDTI4D) { printError("Unable to convert DTI [increase kMaxDTI4D] found %d directions\n", d.CSA.numDti); 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]; if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleOrTEVaries = true; if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleOrTEVaries = true; if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleOrTEVaries = true; if (dti4D->isPhase[i] != dti4D->isPhase[0]) d.isScaleOrTEVaries = true; if (dti4D->isReal[i] != dti4D->isReal[0]) d.isScaleOrTEVaries = true; if (dti4D->isImaginary[i] != dti4D->isImaginary[0]) d.isScaleOrTEVaries = true; if (dti4D->triggerDelayTime[i] != dti4D->triggerDelayTime[0]) d.isScaleOrTEVaries = true; } //if (d.isScaleOrTEVaries) // printWarning("Varying dimensions (echoes, phase maps, intensity scaling) will require volumes to be saved separately (hint: you may prefer dicm2nii output)\n"); } //if (d.CSA.numDti > 1) // for (int i = 0; i < d.CSA.numDti; i++) // printMessage("%d\tb=\t%g\tv=\t%g\t%g\t%g\n",i,dti4D->S[i].V[0],dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]); //check DTI makes sense if (d.CSA.numDti > 1) { bool v1varies = false; bool v2varies = false; bool v3varies = false; for (int i = 1; i < d.CSA.numDti; i++) { if (dti4D->S[0].V[1] != dti4D->S[i].V[1]) v1varies = true; if (dti4D->S[0].V[2] != dti4D->S[i].V[2]) v2varies = true; if (dti4D->S[0].V[3] != dti4D->S[i].V[3]) v3varies = true; } if ((!v1varies) || (!v2varies) || (!v3varies)) printError("Bizarre b-vectors %s\n", parname); } if ((maxEcho > 1) || (maxCardiac > 1)) printWarning("Multiple Echo (%d) or Cardiac (%d). Carefully inspect output\n", maxEcho, maxCardiac); if ((maxEcho > 1) || (maxCardiac > 1)) d.isScaleOrTEVaries = true; return d; } //nii_readParRec() size_t nii_SliceBytes(struct nifti_1_header hdr) { //size of 2D slice size_t imgsz = hdr.bitpix/8; for (int i = 1; i < 3; i++) if (hdr.dim[i] > 1) imgsz = imgsz * hdr.dim[i]; return imgsz; } //nii_SliceBytes() size_t nii_ImgBytes(struct nifti_1_header hdr) { size_t imgsz = hdr.bitpix/8; for (int i = 1; i < 8; i++) if (hdr.dim[i] > 1) imgsz = imgsz * hdr.dim[i]; return imgsz; } //nii_ImgBytes() //unsigned char * nii_demosaic(unsigned char* inImg, struct nifti_1_header *hdr, int nMosaicSlices, int ProtocolSliceNumber1) { unsigned char * nii_demosaic(unsigned char* inImg, struct nifti_1_header *hdr, int nMosaicSlices, bool isUIH) { //demosaic http://nipy.org/nibabel/dicom/dicom_mosaic.html if (nMosaicSlices < 2) return inImg; //Byte inImg[ [img length] ]; //[img getBytes:&inImg length:[img length]]; int nCol = (int) ceil(sqrt((double) nMosaicSlices)); int nRow = nCol; //n.b. Siemens store 20 images as 5x5 grid, UIH as 5rows, 4 Col https://github.com/rordenlab/dcm2niix/issues/225 if (isUIH) nRow = ceil((float)nMosaicSlices/(float)nCol); //printf("%d = %dx%d\n", nMosaicSlices, nCol, nRow); int colBytes = hdr->dim[1]/nCol * hdr->bitpix/8; int lineBytes = hdr->dim[1] * hdr->bitpix/8; int rowBytes = hdr->dim[1] * hdr->dim[2]/nRow * hdr->bitpix/8; int col = 0; int row = 0; int lOutPos = 0; hdr->dim[1] = hdr->dim[1]/nCol; hdr->dim[2] = hdr->dim[2]/nRow; hdr->dim[3] = nMosaicSlices; size_t imgsz = nii_ImgBytes(*hdr); unsigned char *outImg = (unsigned char *)malloc(imgsz); for (int m=1; m <= nMosaicSlices; m++) { int lPos = (row * rowBytes) + (col * colBytes); for (int y = 0; y < hdr->dim[2]; y++) { memcpy(&outImg[lOutPos], &inImg[lPos], colBytes); // dest, src, bytes lPos += lineBytes; lOutPos +=colBytes; } col ++; if (col >= nCol) { row ++; col = 0; } //start new column } //for m = each mosaic slice free(inImg); return outImg; } // nii_demosaic() unsigned char * nii_flipImgY(unsigned char* bImg, struct nifti_1_header *hdr){ //DICOM row order opposite from NIfTI int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; size_t lineBytes = hdr->dim[1] * hdr->bitpix/8; if ((hdr->datatype == DT_RGB24) && (hdr->bitpix == 24) && (hdr->intent_code == NIFTI_INTENT_NONE)) { //we use the intent code to indicate planar vs triplet... lineBytes = hdr->dim[1]; dim3to7 = dim3to7 * 3; } //rgb data saved planar (RRR..RGGGG..GBBB..B //#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 free(line); //#endif 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]; int sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix/8; size_t volBytes = sliceBytes * hdr->dim[3]; //#ifdef _MSC_VER unsigned char * slice = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes)); //#else // unsigned char slice[sliceBytes]; //#endif 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 //#ifdef _MSC_VER free(slice); //#endif 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 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; } } //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"); if (!file) { printError("Unable to open %s\n", imgname); return NULL; } fseek(file, 0, SEEK_END); long fileLen=ftell(file); if (fileLen < (imgszRead+hdr.vox_offset)) { printMessage("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); fclose(file); if (sz < imgszRead) { printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname); return NULL; } if (bitsAllocated == 12) conv12bit16bit(bImg, hdr); return bImg; } //nii_loadImgCore() unsigned char * nii_planar2rgb(unsigned char* bImg, struct nifti_1_header *hdr, int isPlanar) { //DICOM data saved in triples RGBRGBRGB, NIfTI RGB saved in planes RRR..RGGG..GBBBB..B if (bImg == NULL) return NULL; if (hdr->datatype != DT_RGB24) return bImg; if (isPlanar == 0) return bImg; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int sliceBytes8 = hdr->dim[1]*hdr->dim[2]; int sliceBytes24 = sliceBytes8 * 3; unsigned char * slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); int sliceOffsetRGB = 0; int sliceOffsetR = 0; int sliceOffsetG = sliceOffsetR + sliceBytes8; int sliceOffsetB = sliceOffsetR + 2*sliceBytes8; //printMessage("planar->rgb %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); int i = 0; for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice memcpy(slice24, &bImg[sliceOffsetRGB], sliceBytes24); for (int rgb = 0; rgb < sliceBytes8; rgb++) { bImg[i++] =slice24[sliceOffsetR+rgb]; bImg[i++] =slice24[sliceOffsetG+rgb]; bImg[i++] =slice24[sliceOffsetB+rgb]; } sliceOffsetRGB += sliceBytes24; } //for each slice free(slice24); return bImg; } //nii_planar2rgb() unsigned char * nii_rgb2planar(unsigned char* bImg, struct nifti_1_header *hdr, int isPlanar) { //DICOM data saved in triples RGBRGBRGB, Analyze RGB saved in planes RRR..RGGG..GBBBB..B if (bImg == NULL) return NULL; if (hdr->datatype != DT_RGB24) return bImg; if (isPlanar == 1) return bImg;//return nii_bgr2rgb(bImg,hdr); int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int sliceBytes8 = hdr->dim[1]*hdr->dim[2]; int sliceBytes24 = sliceBytes8 * 3; unsigned char * slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); //printMessage("rgb->planar %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); int sliceOffsetR = 0; for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice memcpy(slice24, &bImg[sliceOffsetR], sliceBytes24); //TPX memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24); int sliceOffsetG = sliceOffsetR + sliceBytes8; int sliceOffsetB = sliceOffsetR + 2*sliceBytes8; int i = 0; int j = 0; for (int rgb = 0; rgb < sliceBytes8; rgb++) { bImg[sliceOffsetR+j] =slice24[i++]; bImg[sliceOffsetG+j] =slice24[i++]; bImg[sliceOffsetB+j] =slice24[i++]; j++; } sliceOffsetR += sliceBytes24; } //for each slice free(slice24); return bImg; } //nii_rgb2Planar() unsigned char * nii_iVaries(unsigned char *img, struct nifti_1_header *hdr){ //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 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; 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 int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; if (dim3to7 < 2) return bImg; if (dim3to7 > kMaxSlice2D) return bImg; uint64_t imgSz = nii_ImgBytes(*hdr); uint64_t sliceBytes = hdr->dim[1]*hdr->dim[2]*hdr->bitpix/8; unsigned char *outImg = (unsigned char *)malloc( imgSz); memcpy( &outImg[0],&bImg[0], imgSz); for (int i = 0; i < dim3to7; i++) { //for each volume int fromSlice = dti4D->sliceOrder[i]; //if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice); //printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", fromSlice, i); if ((i < 0) || (fromSlice >= dim3to7)) { printError("Re-ordered slice out-of-volume %d\n", fromSlice); } else if (i != fromSlice) { uint64_t inPos = fromSlice * sliceBytes; uint64_t outPos = i * sliceBytes; memcpy( &bImg[outPos], &outImg[inPos], sliceBytes); } } free(outImg); return bImg; } /*unsigned char * nii_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; } //nii_byteswap() #ifdef myEnableJasper unsigned char * nii_loadImgCoreJasper(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { if (jas_init()) { return NULL; } jas_stream_t *in; jas_image_t *image; jas_setdbglevel(0); if (!(in = jas_stream_fopen(imgname, "rb"))) { printError( "Cannot open input image file %s\n", imgname); return NULL; } //int isSeekable = jas_stream_isseekable(in); jas_stream_seek(in, dcm.imageStart, 0); int infmt = jas_image_getfmt(in); if (infmt < 0) { printError( "Input image has unknown format %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); return NULL; } char opt[] = "\0"; char *inopts = opt; if (!(image = jas_image_decode(in, infmt, inopts))) { printError("Cannot decode image data %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); return NULL; } int numcmpts; int cmpts[4]; switch (jas_clrspc_fam(jas_image_clrspc(image))) { case JAS_CLRSPC_FAM_RGB: if (jas_image_clrspc(image) != JAS_CLRSPC_SRGB) printWarning("Inaccurate color\n"); numcmpts = 3; if ((cmpts[0] = jas_image_getcmptbytype(image, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R))) < 0 || (cmpts[1] = jas_image_getcmptbytype(image, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G))) < 0 || (cmpts[2] = jas_image_getcmptbytype(image, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B))) < 0) { printError("Missing color component\n"); return NULL; } break; case JAS_CLRSPC_FAM_GRAY: if (jas_image_clrspc(image) != JAS_CLRSPC_SGRAY) printWarning("Inaccurate color\n"); numcmpts = 1; if ((cmpts[0] = jas_image_getcmptbytype(image, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y))) < 0) { printError("Missing color component\n"); return NULL; } break; default: printError("Unsupported color space\n"); return NULL; break; } int width = jas_image_cmptwidth(image, cmpts[0]); int height = jas_image_cmptheight(image, cmpts[0]); int prec = jas_image_cmptprec(image, cmpts[0]); int sgnd = jas_image_cmptsgnd(image, cmpts[0]); #ifdef MY_DEBUG printMessage("offset %d w*h %d*%d bpp %d sgnd %d components %d '%s' Jasper=%s\n",dcm.imageStart, width, height, prec, sgnd, numcmpts, imgname, jas_getversion()); #endif for (int cmptno = 0; cmptno < numcmpts; ++cmptno) { if (jas_image_cmptwidth(image, cmpts[cmptno]) != width || jas_image_cmptheight(image, cmpts[cmptno]) != height || jas_image_cmptprec(image, cmpts[cmptno]) != prec || jas_image_cmptsgnd(image, cmpts[cmptno]) != sgnd || jas_image_cmpthstep(image, cmpts[cmptno]) != jas_image_cmpthstep(image, 0) || jas_image_cmptvstep(image, cmpts[cmptno]) != jas_image_cmptvstep(image, 0) || jas_image_cmpttlx(image, cmpts[cmptno]) != jas_image_cmpttlx(image, 0) || jas_image_cmpttly(image, cmpts[cmptno]) != jas_image_cmpttly(image, 0)) { printMessage("The NIfTI format cannot be used to represent an image with this geometry.\n"); return NULL; } } //extract the data int bpp = (prec + 7) >> 3; //e.g. 12 bits requires 2 bytes int imgbytes = bpp * width * height * numcmpts; if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { printError("Catastrophic decompression error\n"); return NULL; } jas_seqent_t v; unsigned char *img = (unsigned char *)malloc(imgbytes); uint16_t * img16ui = (uint16_t*) img; //unsigned 16-bit int16_t * img16i = (int16_t*) img; //signed 16-bit if (sgnd) bpp = -bpp; if (bpp == -1) { printError("Signed 8-bit DICOM?\n"); return NULL; } jas_matrix_t *data; jas_seqent_t *d; data = 0; int cmptno, y, x; int pix = 0; for (cmptno = 0; cmptno < numcmpts; ++cmptno) { if (!(data = jas_matrix_create(1, width))) { free(img); return NULL; } } //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB for (cmptno = 0; cmptno < numcmpts; ++cmptno) { for (y = 0; y < height; ++y) { if (jas_image_readcmpt(image, cmpts[cmptno], 0, y, width, 1, data)) { free(img); return NULL; } d = jas_matrix_getref(data, 0, 0); for (x = 0; x < width; ++x) { v = *d; switch (bpp) { case 1: img[pix] = v; break; case 2: img16ui[pix] = v; break; case -2: img16i[pix] = v; break; } pix ++; ++d; }//for x } //for y } //for each component jas_matrix_destroy(data); jas_image_destroy(image); jas_image_clearfmts(); return img; } //nii_loadImgCoreJasper() #endif struct TJPEG { long offset; long size; }; TJPEG * decode_JPEG_SOF_0XC3_stack (const char *fn, int skipBytes, bool 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; } 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; } #ifndef F_OK #define F_OK 0 /* existence check */ #endif #ifndef myDisableClassicJPEG #ifdef myTurboJPEG //if turboJPEG instead of nanoJPEG for classic JPEG decompression //unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { unsigned char * nii_loadImgJPEG50(char* imgname, struct TDICOMdata dcm) { //decode classic JPEG using nanoJPEG //printMessage("50 offset %d\n", dcm.imageStart); if ((dcm.samplesPerPixel != 1) && (dcm.samplesPerPixel != 3)) { printError("%d components (expected 1 or 3) in a JPEG image '%s'\n", dcm.samplesPerPixel, imgname); return NULL; } if( access(imgname, F_OK ) == -1 ) { printError("Unable to find '%s'\n", imgname); return NULL; } //load compressed data FILE *f = fopen(imgname, "rb"); fseek(f, 0, SEEK_END); long unsigned int _jpegSize = (long unsigned int) ftell(f); _jpegSize = _jpegSize - dcm.imageStart; if (_jpegSize < 8) { printError("File too small\n"); fclose(f); return NULL; } unsigned char* _compressedImage = (unsigned char *)malloc(_jpegSize); fseek(f, dcm.imageStart, SEEK_SET); _jpegSize = (long unsigned int) fread(_compressedImage, 1, _jpegSize, f); fclose(f); int jpegSubsamp, width, height; //printMessage("Decoding with turboJPEG\n"); tjhandle _jpegDecompressor = tjInitDecompress(); tjDecompressHeader2(_jpegDecompressor, _compressedImage, _jpegSize, &width, &height, &jpegSubsamp); int COLOR_COMPONENTS = dcm.samplesPerPixel; //printMessage("turboJPEG h*w %d*%d sampling %d components %d\n", width, height, jpegSubsamp, COLOR_COMPONENTS); if ((jpegSubsamp == TJSAMP_GRAY) && (COLOR_COMPONENTS != 1)) { printError("Grayscale jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); } if ((jpegSubsamp != TJSAMP_GRAY) && (COLOR_COMPONENTS != 3)) { printError("Color jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); } //unsigned char bImg[width*height*COLOR_COMPONENTS]; //!< will contain the decompressed image unsigned char *bImg = (unsigned char *)malloc(width*height*COLOR_COMPONENTS); if (COLOR_COMPONENTS == 1) //TJPF_GRAY tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0/*pitch*/, height, TJPF_GRAY, TJFLAG_FASTDCT); else tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0/*pitch*/, height, TJPF_RGB, TJFLAG_FASTDCT); //printMessage("turboJPEG h*w %d*%d (sampling %d)\n", width, height, jpegSubsamp); tjDestroy(_jpegDecompressor); return bImg; } #else //if turboJPEG else use nanojpeg... //unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { unsigned char * nii_loadImgJPEG50(char* imgname, struct TDICOMdata dcm) { //decode classic JPEG using nanoJPEG //printMessage("50 offset %d\n", dcm.imageStart); if( access(imgname, F_OK ) == -1 ) { printError("Unable to find '%s'\n", imgname); return NULL; } //load compressed data FILE *f = fopen(imgname, "rb"); fseek(f, 0, SEEK_END); int size = (int) ftell(f); size = size - dcm.imageStart; if (size < 8) { printError("File too small '%s'\n", imgname); fclose(f); return NULL; } char *buf = (char *)malloc(size); fseek(f, dcm.imageStart, SEEK_SET); size = (int) fread(buf, 1, size, f); fclose(f); //decode njInit(); if (njDecode(buf, size)) { printError("Unable to decode JPEG image.\n"); return NULL; } free(buf); unsigned char *bImg = (unsigned char *)malloc(njGetImageSize()); memcpy(bImg, njGetImage(), njGetImageSize()); //dest, src, size njDone(); return bImg; } #endif #endif uint32_t rleInt(int lIndex, unsigned char lBuffer[], bool swap) {//read binary 32-bit integer uint32_t retVal = 0; memcpy(&retVal, (char*)&lBuffer[lIndex * 4], 4); if (!swap) return retVal; uint32_t swapVal; char *inInt = ( char* ) & retVal; char *outInt = ( char* ) & swapVal; outInt[0] = inInt[3]; outInt[1] = inInt[2]; outInt[2] = inInt[1]; outInt[3] = inInt[0]; return swapVal; } //rleInt() unsigned char * nii_loadImgPMSCT_RLE1(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { //Transfer Syntax 1.3.46.670589.33.1.4.1 also handled by TomoVision and GDCM's rle2img //https://github.com/malaterre/GDCM/blob/a923f206060e85e8d81add565ae1b9dd7b210481/Examples/Cxx/rle2img.cxx //see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1 if (dcm.imageBytes < 66 ) { //64 for header+ 2 byte minimum image printError("%d is not enough bytes for PMSCT_RLE1 compression '%s'\n", dcm.imageBytes, imgname); return NULL; } int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8); if (bytesPerSample != 2) { //there is an RGB variation of this format, but we have not seen it in the wild printError("PMSCT_RLE1 should be 16-bits per sample (please report on Github and use pmsct_rgb1).\n"); return NULL; } FILE *file = fopen(imgname , "rb"); if (!file) { printError("Unable to open %s\n", imgname); return NULL; } fseek(file, 0, SEEK_END); long fileLen=ftell(file); if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes+dcm.imageStart))) { printMessage("File not large enough to store image data: %s\n", imgname); fclose(file); return NULL; } fseek(file, (long) dcm.imageStart, SEEK_SET); size_t imgsz = nii_ImgBytes(hdr); char *cImg = (char *)malloc(dcm.imageBytes); //compressed input size_t sz = fread(cImg, 1, dcm.imageBytes, file); fclose(file); if (sz < (size_t)dcm.imageBytes) { printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); free(cImg); return NULL; } if( imgsz == dcm.imageBytes ) {// Handle special case that data is not compressed: return (unsigned char *)cImg; } unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output // RLE pass: compressed -> temp (bImg -> tImg) char *tImg = (char *)malloc(imgsz); //temp output int o = 0; for(size_t i = 0; i < dcm.imageBytes; ++i) { if( cImg[i] == (char)0xa5 ) { int repeat = (unsigned char)cImg[i+1] + 1; char value = cImg[i+2]; while(repeat) { tImg[o] = value ; o ++; --repeat; } i+=2; } else { tImg[o] = cImg[i]; o ++; } } //for i free(cImg); int tempsize = o; //Looks like this RLE is pretty ineffective... // printMessage("RLE %d -> %d\n", dcm.imageBytes, o); //Delta encoding pass: temp -> output (tImg -> bImg) unsigned short delta = 0; o = 0; int n16 = (int) imgsz >> 1; unsigned short *bImg16 = (unsigned short *) bImg; for(size_t i = 0; i < tempsize; ++i) { if( tImg[i] == (unsigned char)0x5a ) { unsigned char v1 = (unsigned char)tImg[i+1]; unsigned char v2 = (unsigned char)tImg[i+2]; unsigned short value = (unsigned short)(v2 * 256 + v1); if (o < n16) bImg16[o] = value; o ++; delta = value; i+=2; } else { unsigned short value = (unsigned short)(tImg[i] + delta); if (o < n16) bImg16[o] = value; o ++; delta = value; } } //for i //printMessage("Delta %d -> %d (of %d)\n", tempsize, 2*(o-1), imgsz); free(tImg); return bImg; } // nii_loadImgPMSCT_RLE1() unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { //decompress PackBits run-length encoding https://en.wikipedia.org/wiki/PackBits if (dcm.imageBytes < 66 ) { //64 for header+ 2 byte minimum image printError("%d is not enough bytes for RLE compression '%s'\n", dcm.imageBytes, imgname); return NULL; } FILE *file = fopen(imgname , "rb"); if (!file) { printError("Unable to open %s\n", imgname); return NULL; } fseek(file, 0, SEEK_END); long fileLen=ftell(file); if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes+dcm.imageStart))) { printMessage("File not large enough to store image data: %s\n", imgname); fclose(file); return NULL; } fseek(file, (long) dcm.imageStart, SEEK_SET); size_t imgsz = nii_ImgBytes(hdr); unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input size_t sz = fread(cImg, 1, dcm.imageBytes, file); fclose(file); if (sz < (size_t)dcm.imageBytes) { printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); free(cImg); return NULL; } //read header http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html bool swap = (dcm.isLittleEndian != littleEndianPlatform()); int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8); uint32_t bytesPerSampleRLE = rleInt(0, cImg, swap); if ((bytesPerSample < 0) || (bytesPerSampleRLE != (uint32_t)bytesPerSample)) { printError("RLE header corrupted %d != %d\n", bytesPerSampleRLE, bytesPerSample); free(cImg); return NULL; } unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output for (size_t i = 0; i < imgsz; i++) bImg[i] = 0; for (int i = 0; i < bytesPerSample; i++) { uint32_t offset = rleInt(i+1, cImg, swap); if ((dcm.imageBytes < 0) || (offset > (uint32_t)dcm.imageBytes)) { printError("RLE header error\n"); free(cImg); free(bImg); return NULL; } //save in platform's endian: // The first Segment is generated by stripping off the most significant byte of each Padded Composite Pixel Code... size_t vx = i; if ((dcm.samplesPerPixel == 1) && (littleEndianPlatform())) //endian, except for RGB vx = (bytesPerSample-1) - i; while (vx < imgsz) { int8_t n = cImg[offset]; offset++; //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html if ((n >= 0) && (n <= 127)) { //literal bytes int reps = 1 + (int)n; for (int r = 0; r < reps; r++) { int8_t v = cImg[offset]; offset++; if (vx >= imgsz) ;//printMessage("literal overflow %d %d\n", r, reps); else bImg[vx] = v; vx = vx + bytesPerSample; } } else if ((n <= -1) && (n >= -127)) { //repeated run int8_t v = cImg[offset]; offset++; int reps = -(int)n + 1; for (int r = 0; r < reps; r++) { if (vx >= imgsz) ;//printMessage("repeat overflow %d\n", reps); else bImg[vx] = v; vx = vx + bytesPerSample; } }; //n.b. we ignore -128! } //while vx < imgsz } //for i < bytesPerSample free(cImg); return bImg; } // nii_loadImgRLE() #ifdef myDisableOpenJPEG #ifndef myEnableJasper //avoid compiler warning, see https://stackoverflow.com/questions/3599160/unused-parameter-warnings-in-c #define UNUSED(x) (void)(x) #endif #endif #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) //Support for JPEG-LS //JPEG-LS: Transfer Syntaxes 1.2.840.10008.1.2.4.80 1.2.840.10008.1.2.4.81 #ifdef myEnableJPEGLS1 //use CharLS v1.* requires c++03 //-std=c++03 -DmyEnableJPEGLS1 charls1/header.cpp charls1/jpegls.cpp charls1/jpegmarkersegment.cpp charls1/interface.cpp charls1/jpegstreamwriter.cpp #include "charls1/interface.h" #else //use latest release of CharLS: CharLS 2.x requires c++14 //-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp #include "charls/charls.h" #endif #include "charls/publictypes.h" unsigned char * nii_loadImgJPEGLS(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { //load compressed data FILE *file = fopen(imgname , "rb"); if (!file) { printError("Unable to open %s\n", imgname); return NULL; } fseek(file, 0, SEEK_END); long fileLen=ftell(file); if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes+dcm.imageStart))) { printMessage("File not large enough to store JPEG-LS data: %s\n", imgname); fclose(file); return NULL; } fseek(file, (long) dcm.imageStart, SEEK_SET); unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input size_t sz = fread(cImg, 1, dcm.imageBytes, file); fclose(file); if (sz < (size_t)dcm.imageBytes) { printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); free(cImg); return NULL; } //create buffer for uncompressed data size_t imgsz = nii_ImgBytes(hdr); unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output JlsParameters params = {}; #ifdef myEnableJPEGLS1 if(JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms) != OK ) { #else using namespace charls; if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { #endif printMessage("CharLS failed to read header.\n"); return NULL; } #ifdef myEnableJPEGLS1 if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms) != OK ) { #else if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { #endif free(bImg); printMessage("CharLS failed to read image.\n"); return NULL; } return (bImg); } #endif unsigned char * nii_loadImgXL(char* imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D) { //provided with a filename (imgname) and DICOM header (dcm), creates NIfTI header (hdr) and img if (headerDcm2Nii(dcm, hdr, true) == EXIT_FAILURE) return NULL; //TOFU unsigned char * img; if (dcm.compressionScheme == kCompress50) { #ifdef myDisableClassicJPEG printMessage("Software not compiled to decompress classic JPEG DICOM images\n"); return NULL; #else //img = nii_loadImgJPEG50(imgname, *hdr, dcm); img = nii_loadImgJPEG50(imgname, dcm); if (hdr->datatype ==DT_RGB24) //convert to planar img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB);//do this BEFORE Y-Flip, or RGB order can be flipped #endif } else if (dcm.compressionScheme == kCompressJPEGLS) { #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) img = nii_loadImgJPEGLS(imgname, *hdr, dcm); if (hdr->datatype ==DT_RGB24) //convert to planar img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB);//do this BEFORE Y-Flip, or RGB order can be flipped #else printMessage("Software not compiled to decompress JPEG-LS DICOM images\n"); return NULL; #endif } else if (dcm.compressionScheme == kCompressPMSCT_RLE1) { img = nii_loadImgPMSCT_RLE1(imgname, *hdr, dcm); } else if (dcm.compressionScheme == kCompressRLE) { img = nii_loadImgRLE(imgname, *hdr, dcm); if (hdr->datatype ==DT_RGB24) //convert to planar img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB);//do this BEFORE Y-Flip, or RGB order can be flipped } else if (dcm.compressionScheme == kCompressC3) img = nii_loadImgJPEGC3(imgname, *hdr, dcm, (isVerbose > 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 ((!dcm.isFloat) && (iVaries)) img = nii_iVaries(img, hdr); 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 ((hdr->dim[0] > 3) && (dcm.patientPositionSequentialRepeats > 1) && (dcm.sliceOrder == NULL)) //swizzle 3rd and 4th dimension (Philips stores time as 3rd dimension) //~ img = nii_XYTZ_XYZT(img, hdr,dcm.patientPositionSequentialRepeats); if ((dti4D != NULL) && (dti4D->sliceOrder[0] >= 0)) img = nii_reorderSlicesX(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; 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; } //nii_loadImgXL() int isSQ(uint32_t groupElement) { //Detect sequence VR ("SQ") for implicit tags static const int array_size = 35; uint32_t array[array_size] = {0x2005+(uint32_t(0x140F)<<16), 0x0008+(uint32_t(0x1111)<<16), 0x0008+(uint32_t(0x1115)<<16), 0x0008+(uint32_t(0x1140)<<16), 0x0008+(uint32_t(0x1199)<<16), 0x0008+(uint32_t(0x2218)<<16), 0x0008+(uint32_t(0x9092)<<16), 0x0018+(uint32_t(0x9006)<<16), 0x0018+(uint32_t(0x9042)<<16), 0x0018+(uint32_t(0x9045)<<16), 0x0018+(uint32_t(0x9049)<<16), 0x0018+(uint32_t(0x9112)<<16), 0x0018+(uint32_t(0x9114)<<16), 0x0018+(uint32_t(0x9115)<<16), 0x0018+(uint32_t(0x9117)<<16), 0x0018+(uint32_t(0x9119)<<16), 0x0018+(uint32_t(0x9125)<<16), 0x0018+(uint32_t(0x9152)<<16), 0x0018+(uint32_t(0x9176)<<16), 0x0018+(uint32_t(0x9226)<<16), 0x0018+(uint32_t(0x9239)<<16), 0x0020+(uint32_t(0x9071)<<16), 0x0020+(uint32_t(0x9111)<<16), 0x0020+(uint32_t(0x9113)<<16), 0x0020+(uint32_t(0x9116)<<16), 0x0020+(uint32_t(0x9221)<<16), 0x0020+(uint32_t(0x9222)<<16), 0x0028+(uint32_t(0x9110)<<16), 0x0028+(uint32_t(0x9132)<<16), 0x0028+(uint32_t(0x9145)<<16), 0x0040+(uint32_t(0x0260)<<16), 0x0040+(uint32_t(0x0555)<<16), 0x0040+(uint32_t(0xa170)<<16), 0x5200+(uint32_t(0x9229)<<16), 0x5200+(uint32_t(0x9230)<<16)}; for (int i = 0; i < array_size; i++) { //if (array[i] == groupElement) printMessage(" implicitSQ %04x,%04x\n", groupElement & 65535,groupElement>>16); if (array[i] == groupElement) return 1; } return 0; } //isSQ() int isDICOMfile(const char * fname) { //0=NotDICOM, 1=DICOM, 2=Maybe(not Part 10 compliant) //Someday: it might be worthwhile to detect "IMGF" at offset 3228 to warn user if they attempt to convert Signa data FILE *fp = fopen(fname, "rb"); if (!fp) return 0; fseek(fp, 0, SEEK_END); long fileLen=ftell(fp); if (fileLen < 256) { fclose(fp); return 0; } fseek(fp, 0, SEEK_SET); unsigned char buffer[256]; size_t sz = fread(buffer, 1, 256, fp); fclose(fp); if (sz < 256) return 0; if ((buffer[128] == 'D') && (buffer[129] == 'I') && (buffer[130] == 'C') && (buffer[131] == 'M')) return 1; //valid DICOM if ((buffer[0] == 8) && (buffer[1] == 0) && (buffer[3] == 0)) return 2; //not valid Part 10 file, perhaps DICOM object return 0; } //isDICOMfile() //START RIR 12/2017 Robert I. Reid // Gathering spot for all the info needed to get the b value and direction // for a volume. struct TVolumeDiffusion { struct TDICOMdata* pdd; // The multivolume struct TDTI4D* pdti4D; // permanent records. uint8_t manufacturer; // kMANUFACTURER_UNKNOWN, kMANUFACTURER_SIEMENS, etc. //void set_manufacturer(const uint8_t m) {manufacturer = m; update();} // unnecessary // Everything after this in the structure would be private if it were a C++ // class, but it has been rewritten as a struct for C compatibility. I am // using _ as a hint of that, although _ for privacy is not really a // universal convention in C. Privacy is desired because immediately // any of these are updated _update_tvd() should be called. bool _isAtFirstPatientPosition; // Limit b vals and vecs to 1 per volume. //float bVal0018_9087; // kDiffusion_b_value, always present in Philips/Siemens. //float bVal2001_1003; // kDiffusionBFactor // float dirRL2005_10b0; // kDiffusionDirectionRL // float dirAP2005_10b1; // kDiffusionDirectionAP // float dirFH2005_10b2; // kDiffusionDirectionFH // Philips diffusion scans tend to have a "trace" (average of the diffusion // weighted volumes) volume tacked on, usually but not always at the end, // so b is > 0, but the direction is meaningless. Most software versions // explicitly set the direction to 0, but version 3 does not, making (0x18, // 0x9075) necessary. bool _isPhilipsNonDirectional; //char _directionality0018_9075[16]; // DiffusionDirectionality, not in Philips 2.6. // float _orientation0018_9089[3]; // kDiffusionOrientation, always // // present in Philips/Siemens for // // volumes with a direction. //char _seq0018_9117[64]; // MRDiffusionSequence, not in Philips 2.6. float _dtiV[4]; //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); void set_bValGE(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf); void set_diffusion_directionGE(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf, int axis); void set_bVal(struct TVolumeDiffusion* ptvd, float b); 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; //numDti = 0; }//clear_volume() void set_directionality0018_9075(struct TVolumeDiffusion* ptvd, unsigned char* inbuf) { if(strncmp(( char*)(inbuf), "DIRECTIONAL", 11) && // strncmp = 0 for ==. 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() void 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); } //set_bValGE() // axis: 0 -> x, 1 -> y , 2 -> z void set_diffusion_directionGE(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf, const int axis){ ptvd->_dtiV[axis + 1] = dcmStrFloat(lLength, inbuf); //printf("(0019,10bb..d) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]); _update_tvd(ptvd); }//set_diffusion_directionGE() void dcmMultiFloatDouble (size_t lByteLength, unsigned char lBuffer[], size_t lnFloats, float *lFloats, bool isLittleEndian) { size_t floatlen = lByteLength / lnFloats; for(size_t i = 0; i < lnFloats; ++i) lFloats[i] = dcmFloatDouble((int)floatlen, lBuffer + i * floatlen, isLittleEndian); } //dcmMultiFloatDouble() void set_orientation0018_9089(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf, bool isLittleEndian) { if(ptvd->_isPhilipsNonDirectional){ for(int i = 1; i < 4; ++i) // Deliberately ignore inbuf; it might be nonsense. ptvd->_dtiV[i] = 0.0; } else dcmMultiFloatDouble(lLength, inbuf, 3, ptvd->_dtiV + 1, isLittleEndian); _update_tvd(ptvd); }//set_orientation0018_9089() void set_bVal(struct TVolumeDiffusion* ptvd, const float b) { ptvd->_dtiV[0] = b; _update_tvd(ptvd); }//set_bVal() void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion* ptvd, const bool iafpp) { ptvd->_isAtFirstPatientPosition = iafpp; _update_tvd(ptvd); }//set_isAtFirstPatientPosition_tvd() // Update the diffusion info in dd and *pdti4D for a volume once all the // diffusion info for that volume has been read into pvd. // // Note that depending on the scanner software the diffusion info can arrive in // different tags, in different orders (because of enclosing sequence tags), // and the values in some tags may be invalid, or may be essential, depending // on the presence of other tags. Thus it is best to gather all the diffusion // info for a volume (frame) before taking action on it. // // On the other hand, dd and *pdti4D need to be updated as soon as the // diffusion info is ready, before diffusion info for the next volume is read // in. void _update_tvd(struct TVolumeDiffusion* ptvd) { // Figure out if we have both the b value and direction (if any) for this // volume, and if isFirstPosition. // // GE (software version 27) is liable to NOT include kDiffusion_b_value for the // // slice if it is 0, but should still have kDiffusionBFactor, which comes // // after PatientPosition. // if(isAtFirstPatientPosition && manufacturer == kMANUFACTURER_GE && dtiV[0] < 0) // dtiV[0] = 0; // Implied 0. bool isReady = (ptvd->_isAtFirstPatientPosition && (ptvd->_dtiV[0] >= 0)); if(isReady){ for(int i = 1; i < 4; ++i){ if(ptvd->_dtiV[i] > 1){ isReady = false; break; } } } if(!isReady) return; // If still here, update dd and *pdti4D. ptvd->pdd->CSA.numDti++; if (ptvd->pdd->CSA.numDti == 2) { // First time we know that this is a 4D DTI dataset; for(int i = 0; i < 4; ++i) // Start *pdti4D before ptvd->pdd->CSA.dtiV ptvd->pdti4D->S[0].V[i] = ptvd->pdd->CSA.dtiV[i]; // is updated. } for(int i = 0; i < 4; ++i) // Update pdd ptvd->pdd->CSA.dtiV[i] = ptvd->_dtiV[i]; if((ptvd->pdd->CSA.numDti > 1) && (ptvd->pdd->CSA.numDti < kMaxDTI4D)){ // Update *pdti4D //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI)); for(int i = 0; i < 4; ++i) ptvd->pdti4D->S[ptvd->pdd->CSA.numDti - 1].V[i] = ptvd->_dtiV[i]; } clear_volume(ptvd); // clear the slate for the next volume. }//_update_tvd() //END RIR struct TDCMdim { //DimensionIndexValues uint32_t dimIdx[MAX_NUMBER_OF_DIMENSIONS]; uint32_t diskPos; float triggerDelayTime, TE, intenScale, intenIntercept, intenScalePhilips, RWVScale, RWVIntercept; float V[4]; bool isPhase; bool isReal; bool isImaginary; }; void getFileName( char *pathParent, const char *path) //if path is c:\d1\d2 then filename is 'd2' { const char *filename = strrchr(path, '/'); //UNIX if (filename == 0) { filename = strrchr(path, '\\'); //Windows if (filename == NULL) filename = strrchr(path, ':'); //Windows } //const char *filename = strrchr(path, kPathSeparator); //x if (filename == NULL) {//no path separator strcpy(pathParent,path); return; } filename++; strncpy(pathParent,filename, kDICOMStr-1); //strcpy(pathParent,filename); //<- this can cause overflow if filename longer than kDICOMStr } #ifdef USING_R // True iff dcm1 sorts *before* dcm2 bool compareTDCMdim (const TDCMdim &dcm1, const TDCMdim &dcm2) { for (int i=MAX_NUMBER_OF_DIMENSIONS-1; i >=0; i--){ if(dcm1.dimIdx[i] < dcm2.dimIdx[i]) return true; else if(dcm1.dimIdx[i] > dcm2.dimIdx[i]) return false; } return false; } //compareTDCMdim() #else int compareTDCMdim(void const *item1, void const *item2) { struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1; struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2; //for(int i=0; i < MAX_NUMBER_OF_DIMENSIONS; ++i){ for(int i=MAX_NUMBER_OF_DIMENSIONS-1; i >=0; i--){ if(dcm1->dimIdx[i] < dcm2->dimIdx[i]) return -1; else if(dcm1->dimIdx[i] > dcm2->dimIdx[i]) return 1; } return 0; } //compareTDCMdim() #endif // USING_R struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { struct TDICOMdata d = clear_dicom_data(); d.imageNum = 0; //not set strcpy(d.protocolName, ""); //erase dummy with empty strcpy(d.protocolName, ""); //erase dummy with empty strcpy(d.seriesDescription, ""); //erase dummy with empty strcpy(d.sequenceName, ""); //erase dummy with empty //do not read folders - code specific to GCC (LLVM/Clang seems to recognize a small file size) dti4D->sliceOrder[0] = -1; struct TVolumeDiffusion volDiffusion = initTVolumeDiffusion(&d, dti4D); struct stat s; if( stat(fname,&s) == 0 ) { if( !(s.st_mode & S_IFREG) ){ printMessage( "DICOM read fail: not a valid file (perhaps a directory) %s\n",fname); return d; } } bool isPart10prefix = true; int isOK = isDICOMfile(fname); if (isOK == 0) return d; if (isOK == 2) { d.isExplicitVR = false; isPart10prefix = false; } FILE *file = fopen(fname, "rb"); if (!file) { printMessage("Unable to open file %s\n", fname); return d; } fseek(file, 0, SEEK_END); long fileLen=ftell(file); //Get file length if (fileLen < 256) { printMessage( "File too small to be a DICOM image %s\n", fname); return d; } //Since size of DICOM header is unknown, we will load it in 1mb segments //This uses less RAM and makes is faster for computers with slow disk access //Benefit is largest for 4D images. //To disable caching and load entire file to RAM, compile with "-dmyLoadWholeFileToReadHeader" //To implement the segments, we define these variables: // fileLen = size of file in bytes // MaxBufferSz = maximum size of buffer in bytes // Buffer = array with n elements, where n is smaller of fileLen or MaxBufferSz // lPos = position in Buffer (indexed from 0), 0..(n-1) // lFileOffset = offset of Buffer in file: true file position is lOffset+lPos (initially 0) #ifdef myLoadWholeFileToReadHeader size_t MaxBufferSz = fileLen; #else size_t MaxBufferSz = 1000000; //ideally size of DICOM header, but this varies from 2D to 4D files #endif if (MaxBufferSz > (size_t)fileLen) MaxBufferSz = fileLen; //printf("%d -> %d\n", MaxBufferSz, fileLen); long lFileOffset = 0; fseek(file, 0, SEEK_SET); //Allocate memory unsigned char *buffer=(unsigned char *)malloc(MaxBufferSz+1); if (!buffer) { printError( "Memory exhausted!"); fclose(file); return d; } //Read file contents into buffer size_t sz = fread(buffer, 1, MaxBufferSz, file); if (sz < MaxBufferSz) { printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); fclose(file); return d; } #ifdef myLoadWholeFileToReadHeader fclose(file); #endif //DEFINE DICOM TAGS #define kUnused 0x0001+(0x0001 << 16 ) #define kStart 0x0002+(0x0000 << 16 ) #define kTransferSyntax 0x0002+(0x0010 << 16) #define kImplementationVersionName 0x0002+(0x0013 << 16) #define kSourceApplicationEntityTitle 0x0002+(0x0016 << 16 ) //#define kSpecificCharacterSet 0x0008+(0x0005 << 16 ) //someday we should handle foreign characters... #define kImageTypeTag 0x0008+(0x0008 << 16 ) #define kStudyDate 0x0008+(0x0020 << 16 ) #define kAcquisitionDate 0x0008+(0x0022 << 16 ) #define kAcquisitionDateTime 0x0008+(0x002A << 16 ) #define kStudyTime 0x0008+(0x0030 << 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 kPatientName 0x0010+(0x0010 << 16 ) #define kPatientID 0x0010+(0x0020 << 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 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 kZThick 0x0018+(0x0050 << 16 ) #define kTR 0x0018+(0x0080 << 16 ) #define kTE 0x0018+(0x0081 << 16 ) #define kImagingFrequency 0x0018+(0x0084 << 16 ) //DS #define kTriggerTime 0x0018+(0x1060 << 16 ) //DS //#define kEffectiveTE 0x0018+(0x9082 << 16 ) const uint32_t kEffectiveTE = 0x0018+ (0x9082 << 16); #define kTI 0x0018+(0x0082 << 16) // Inversion time #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 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 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 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 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 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 kMREchoSequence 0x0018+uint32_t(0x9114<< 16 ) //SQ #define kMRAcquisitionPhaseEncodingStepsInPlane 0x0018+uint32_t(0x9231<< 16 ) //US #define kNumberOfImagesInMosaic 0x0019+(0x100A<< 16 ) //US NumberOfImagesInMosaic #define kDwellTime 0x0019+(0x1018<< 16 ) //IS in NSec, see https://github.com/rordenlab/dcm2niix/issues/127 #define kNumberOfDiffusionDirectionGE 0x0019+(0x10E0<< 16) ///DS NumberOfDiffusionDirection:UserData24 #define kLastScanLoc 0x0019+(0x101B<< 16 ) #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 kSharedFunctionalGroupsSequence 0x5200+uint32_t(0x9229<< 16 ) // SQ #define kPerFrameFunctionalGroupsSequence 0x5200+uint32_t(0x9230<< 16 ) // SQ #define kBandwidthPerPixelPhaseEncode 0x0019+(0x1028<< 16 ) //FD //#define kRawDataRunNumberGE 0x0019+(0x10a2<< 16 ) //SL #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 kImagesInAcquisition 0x0020+(0x1002 << 16 ) //IS #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 //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 kPhaseEncodingDirectionPositive 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 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 kDiffusionBFactorGE 0x0043+(0x1039 << 16 ) //IS dicm2nii's SlopInt_6_9 #define kAcquisitionMatrixText 0x0051+(0x100B << 16 ) //LO #define kCoilSiemens 0x0051+(0x100F << 16 ) #define kImaPATModeText 0x0051+(0x1011 << 16 ) #define kLocationsInAcquisition 0x0054+(0x0081 << 16 ) //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 kMRVFrameSequenceUIH 0x0065+(0x1050<< 16 ) //SQ #define kDiffusionGradientDirectionUIH 0x0065+(0x1037<< 16 ) //FD #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 kDiffusionBFactor 0x2001+(0x1003 << 16 )// FL #define kSliceNumberMrPhilips 0x2001+(0x100A << 16 ) //IS Slice_Number_MR #define kSliceOrient 0x2001+(0x100B << 16 )//2001,100B Philips slice orientation (TRANSVERSAL, AXIAL, SAGITTAL) #define kNumberOfSlicesMrPhilips 0x2001+(0x1018 << 16 )//SL 0x2001, 0x1018 ), "Number_of_Slices_MR" //#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 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 ); double TE = 0.0; //most recent echo time recorded bool is2005140FSQ = false; double contentTime = 0.0; int philMRImageDiffBValueNumber = 0; int sqDepth = 0; int acquisitionTimesGE_UIH = 0; int sqDepth00189114 = -1; int nDimIndxVal = -1; //tracks Philips kDimensionIndexValues int locationsInAcquisitionGE = 0; int PETImageIndex = 0; int inStackPositionNumber = 0; int maxInStackPositionNumber = 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; 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); } char vr[2]; //float intenScalePhilips = 0.0; char acquisitionDateTimeTxt[kDICOMStr] = ""; bool isEncapsulatedData = false; int multiBandFactor = 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 isInterpolated = false; bool isIconImageSequence = false; 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; float patientPositionPrivate[4] = {NAN, NAN, NAN, NAN}; float patientPosition[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D //float patientPositionPublic[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D float patientPositionEndPhilips[4] = {NAN, NAN, NAN, NAN}; float patientPositionStartPhilips[4] = {NAN, NAN, NAN, NAN}; //struct TDTI philDTI[kMaxDTI4D]; //for (int i = 0; i < kMaxDTI4D; i++) // philDTI[i].V[0] = -1; //array for storing DimensionIndexValues int numDimensionIndexValues = 0; #ifdef USING_R // Allocating a large array on the stack, as below, vexes valgrind and may cause overflow std::vector dcmDim(kMaxSlice2D); #else TDCMdim dcmDim[kMaxSlice2D]; #endif for (int i = 0; i < kMaxSlice2D; i++) { dcmDim[i].diskPos = i; for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) dcmDim[i].dimIdx[j] = 0; } //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html //The array nestPos tracks explicit lengths for Data Element Tag of Value (FFFE,E000) //a delimiter (fffe,e000) can have an explicit length, in which case there is no delimiter (fffe,e00d) // fffe,e000 can provide explicit lengths, to demonstrate ./dcmconv +ti ex.DCM im.DCM #define kMaxNestPost 128 int nNestPos = 0; size_t nestPos[kMaxNestPost]; while ((d.imageStart == 0) && ((lPos+8+lFileOffset) < fileLen)) { #ifndef myLoadWholeFileToReadHeader //read one segment at a time if ((size_t)(lPos + 128) > MaxBufferSz) { //avoid overreading the file lFileOffset = lFileOffset + lPos; if ((lFileOffset+MaxBufferSz) > (size_t)fileLen) MaxBufferSz = fileLen - lFileOffset; fseek(file, lFileOffset, SEEK_SET); size_t sz = fread(buffer, 1, MaxBufferSz, file); if (sz < MaxBufferSz) { printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); fclose(file); return d; } lPos = 0; } #endif if (d.isLittleEndian) groupElement = buffer[lPos] | (buffer[lPos+1] << 8) | (buffer[lPos+2] << 16) | (buffer[lPos+3] << 24); else groupElement = buffer[lPos+1] | (buffer[lPos] << 8) | (buffer[lPos+3] << 16) | (buffer[lPos+2] << 24); if ((isSwitchToBigEndian) && ((groupElement & 0xFFFF) != 2)) { isSwitchToBigEndian = false; d.isLittleEndian = false; groupElement = buffer[lPos+1] | (buffer[lPos] << 8) | (buffer[lPos+3] << 16) | (buffer[lPos+2] << 24); }//transfer syntax requests switching endian after group 0002 if ((isSwitchToImplicitVR) && ((groupElement & 0xFFFF) != 2)) { isSwitchToImplicitVR = false; d.isExplicitVR = false; } //transfer syntax requests switching VR after group 0001 //uint32_t group = (groupElement & 0xFFFF); lPos += 4; 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 (groupElement == kItemDelimitationTag) { //end of item with undefined length sqDepth--; unNest = true; } 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_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 ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPosition[1])) ) { for (int k = 0; k < 4; k++) patientPositionEndPhilips[k] = patientPosition[k]; } else if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPositionPrivate[1])) ) { for (int k = 0; k < 4; k++) patientPositionEndPhilips[k] = patientPositionPrivate[k]; } patientPosition[1] = NAN; patientPositionPrivate[1] = NAN; } inStackPositionNumber = 0; if (numDimensionIndexValues >= kMaxSlice2D) { printError("Too many slices to track dimensions. Only up to %d are supported\n", kMaxSlice2D); break; } int ndim = nDimIndxVal; for (int i = 0; i < ndim; i++) dcmDim[numDimensionIndexValues].dimIdx[i] = d.dimensionIndexValues[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; dcmDim[numDimensionIndexValues].triggerDelayTime = d.triggerDelayTime; dcmDim[numDimensionIndexValues].V[0] = -1.0; #ifdef MY_DEBUG if (numDimensionIndexValues < 19) { printMessage("dimensionIndexValues0020x9157[%d] = [", numDimensionIndexValues); for (int i = 0; i < ndim; i++) printMessage("%d ", d.dimensionIndexValues[i]); printMessage("]\n"); //printMessage("B0= %g num=%d\n", B0Philips, gradNum); } else return d; #endif //next: add diffusion if reported if (B0Philips >= 0.0) { //diffusion parameters // Philips does not always provide 2005,1413 (MRImageGradientOrientationNumber) and sometimes after dimensionIndexValues /*int gradNum = 0; for (int i = 0; i < ndim; i++) if (d.dimensionIndexValues[i] > 0) gradNum = d.dimensionIndexValues[i]; if (gradNum <= 0) break; With Philips 51.0 both ADC and B=0 are saved as same direction, though they vary in another dimension (0018,9075) CS [ISOTROPIC] (0020,9157) UL 1\2\1\33 << ADC MAP (0018,9075) CS [NONE] (0020,9157) UL 1\1\2\33 next two lines attempt to skip ADC maps we could also increment gradNum for ADC if we wanted... */ if (isPhilipsDerived) { //gradNum ++; B0Philips = 2000.0; vRLPhilips = 0.0; vAPPhilips = 0.0; vFHPhilips = 0.0; } if (B0Philips == 0.0) { //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); vRLPhilips = 0.0; vAPPhilips = 0.0; vFHPhilips = 0.0; } //if ((MRImageGradientOrientationNumber > 0) && ((gradNum != MRImageGradientOrientationNumber)) break; /*if (gradNum < minGradNum) minGradNum = gradNum; if (gradNum >= maxGradNum) maxGradNum = gradNum; if (gradNum >= kMaxDTI4D) { printError("Number of DTI gradients exceeds 'kMaxDTI4D (%d).\n", kMaxDTI4D); } else { gradNum = gradNum - 1;//index from 0 philDTI[gradNum].V[0] = B0Philips; philDTI[gradNum].V[1] = vRLPhilips; philDTI[gradNum].V[2] = vAPPhilips; philDTI[gradNum].V[3] = vFHPhilips; }*/ dcmDim[numDimensionIndexValues].V[0] = B0Philips; dcmDim[numDimensionIndexValues].V[1] = vRLPhilips; dcmDim[numDimensionIndexValues].V[2] = vAPPhilips; dcmDim[numDimensionIndexValues].V[3] = vFHPhilips; isPhilipsDerived = false; //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); //!!! 16032018 : next line as well as definition of B0Philips may need to be set to zero if Philips omits DiffusionBValue tag for B=0 B0Philips = -1.0; //Philips may skip reporting B-values for B=0 volumes, so zero these vRLPhilips = 0.0; vAPPhilips = 0.0; vFHPhilips = 0.0; //MRImageGradientOrientationNumber = 0; }//diffusion parameters numDimensionIndexValues ++; nDimIndxVal = -1; //we need DimensionIndexValues } //record dimensionIndexValues slice information } //groupElement == kItemDelimitationTag : delimit item exits folder if (groupElement == kItemTag) { uint32_t slen = dcmInt(4,&buffer[lPos],d.isLittleEndian); uint32_t kUndefinedLen = 0xFFFFFFFF; if (slen != kUndefinedLen) { nNestPos++; if (nNestPos >= kMaxNestPost) nNestPos = kMaxNestPost - 1; nestPos[nNestPos] = slen+lFileOffset+lPos; } lLength = 4; sqDepth++; //return d; } else if (( (groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) && (!isEncapsulatedData)) { vr[0] = 'N'; vr[1] = 'A'; lLength = 4; } else if (d.isExplicitVR) { vr[0] = buffer[lPos]; vr[1] = buffer[lPos+1]; if (buffer[lPos+1] < 'A') {//implicit vr with 32-bit length if (d.isLittleEndian) lLength = buffer[lPos] | (buffer[lPos+1] << 8) | (buffer[lPos+2] << 16) | (buffer[lPos+3] << 24); else lLength = buffer[lPos+3] | (buffer[lPos+2] << 8) | (buffer[lPos+1] << 16) | (buffer[lPos] << 24); lPos += 4; } else if ( ((buffer[lPos] == 'U') && (buffer[lPos+1] == 'N')) || ((buffer[lPos] == 'U') && (buffer[lPos+1] == 'T')) || ((buffer[lPos] == 'O') && (buffer[lPos+1] == 'B')) || ((buffer[lPos] == 'O') && (buffer[lPos+1] == 'W')) ) { //VR= UN, OB, OW, SQ || ((buffer[lPos] == 'S') && (buffer[lPos+1] == 'Q')) 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 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); uint32_t kUndefinedLen = 0xFFFFFFFF; //printError(" SPECIAL >>>>t%04x,%04x %08x %08x\n", groupElement & 65535,groupElement>>16, special, slen); //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 } } //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 switch ( groupElement ) { case kTransferSyntax: { char transferSyntax[kDICOMStr]; 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 printMessage("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 ((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 { printMessage("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, "dcm4che") != NULL) ) // isDcm4Che = 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 kImageTypeTag: { dcmStr (lLength, &buffer[lPos], d.imageType); int slen; slen = (int) strlen(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, "_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 (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, "_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, "_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: d.manufacturer = dcmStrManufacturer (lLength, &buffer[lPos]); volDiffusion.manufacturer = d.manufacturer; break; case kInstitutionName: dcmStr(lLength, &buffer[lPos], d.institutionName); break; case kInstitutionAddress: 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; 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 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 kStudyTime : 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 kPatientID : dcmStr (lLength, &buffer[lPos], d.patientID); break; case kPatientBirthDate : dcmStr (lLength, &buffer[lPos], d.patientBirthDate); break; case kPatientSex : d.patientSex = toupper(buffer[lPos]); //first character is either 'R'ow or 'C'ol 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 < 5) || (strstr(d.softwareVersions, "XA10") == NULL) ) break; d.isXA10A = true; break; } case kProtocolName : { //if ((strlen(d.protocolName) < 1) || (d.manufacturer != kMANUFACTURER_GE)) //GE uses a generic session name here: do not overwrite kProtocolNameGE dcmStr (lLength, &buffer[lPos], d.protocolName); //see also kSequenceName break; } case kPatientOrient : dcmStr (lLength, &buffer[lPos], d.patientOrient); 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: { // char dateTime[kDICOMStr]; // dcmStr (lLength, &buffer[lPos], dateTime); // printf("%s\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 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 kDwellTime : d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); 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;*/ 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 kBandwidthPerPixelPhaseEncode: d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); 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.rawDataRunNumberGE = dcmInt(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); break; case kImagePositionPatient : { if (is2005140FSQ) { dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &patientPositionPrivate[0]); break; } patientPositionNum++; isAtFirstPatientPosition = true; 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 (isVerbose == 1) //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]); 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_PHILIPS) && (d.manufacturer != kMANUFACTURER_BRUKER)) break; inStackPositionNumber = dcmInt(4,&buffer[lPos],d.isLittleEndian); //printf("<%d>\n",inStackPositionNumber); if (inStackPositionNumber > maxInStackPositionNumber) maxInStackPositionNumber = inStackPositionNumber; 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) 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; 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: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; if (acquisitionTimesGE_UIH >= kMaxEPI3D) break; d.CSA.sliceTiming[acquisitionTimesGE_UIH] = dcmStrFloat(lLength, &buffer[lPos]); //printf("%d %g\n", acquisitionTimesGE_UIH, d.CSA.sliceTiming[acquisitionTimesGE_UIH]); acquisitionTimesGE_UIH ++; break; case kPhaseEncodingDirectionPositive: { if (d.manufacturer != kMANUFACTURER_SIEMENS) break; int ph = dcmStrInt(lLength, &buffer[lPos]); if (ph == 0) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; if (ph == 1) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; break; } //case kRealDwellTime : //https://github.com/rordenlab/dcm2niix/issues/240 // if (d.manufacturer != kMANUFACTURER_SIEMENS) break; // d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); // break; case kBandwidthPerPixelPhaseEncode21: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); break; case kCoilElements: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; dcmStr (lLength, &buffer[lPos], d.coilElements); break; //group 21: GE case kLocationsInAcquisitionGE: locationsInAcquisitionGE = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); break; case kRTIA_timer: if (d.manufacturer != kMANUFACTURER_GE) break; //see dicm2nii slice timing from 0021,105E DS RTIA_timer d.rtia_timerGE = dcmStrFloat(lLength, &buffer[lPos]); //RefAcqTimes = t/10; end % in ms //printf("%s\t%g\n", fname, d.rtia_timerGE); break; case kProtocolDataBlockGE : if (d.manufacturer != kMANUFACTURER_GE) break; d.protocolBlockLengthGE = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); d.protocolBlockStartGE = (int)lPos+(int)lFileOffset+4; //printError("ProtocolDataBlockGE %d @ %d\n", d.protocolBlockLengthGE, d.protocolBlockStartGE); break; case 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); d.CSA.dtiV[0] = v[0]; d.CSA.numDti = 1; //printf("%d>>>%g\n", lPos, 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 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 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 kImagingFrequency : d.imagingFrequency = dcmStrFloat(lLength, &buffer[lPos]); break; case kTriggerTime: //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) break; d.CSA.sliceTiming[acquisitionTimesGE_UIH] = dcmStrFloat(lLength, &buffer[lPos]); //printf("%g\n", d.CSA.sliceTiming[acquisitionTimesGE_UIH]); acquisitionTimesGE_UIH ++; 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 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]; } break; case kFlipAngle : d.flipAngle = dcmStrFloat(lLength, &buffer[lPos]); break; case kRadionuclideTotalDose : d.radionuclideTotalDose = 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 kReceiveCoilName : dcmStr (lLength, &buffer[lPos], d.coilName); if (strlen(d.coilName) < 1) break; d.coilCrc =(long)abs( (long)mz_crc32((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 kIntercept : d.intenIntercept = dcmStrFloat(lLength, &buffer[lPos]); 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 =(long)abs( (long)mz_crc32((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 kIconImageSequence: isIconImageSequence = true; 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 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); break; } 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); break; case kSequenceName : { //if (strlen(d.protocolName) < 1) //precedence given to kProtocolName and kProtocolNameGE 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; break; case kPMSCT_RLE1 : if (d.compressionScheme != kCompressPMSCT_RLE1) break; d.imageStart = (int)lPos + (int)lFileOffset; d.imageBytes = lLength; break; case kDiffusionBFactor : if (d.manufacturer != kMANUFACTURER_PHILIPS) break; B0Philips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); 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 kDiffusion_bValue: // 0018, 9087 // Note that this is ahead of kPatientPosition (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 kPatientPosition (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_SIEMENS) || (d.manufacturer == kMANUFACTURER_PHILIPS)) { 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]; 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 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 kNumberOfSlicesMrPhilips : if (d.manufacturer != kMANUFACTURER_PHILIPS) break; locationsInAcquisitionPhilips = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); //printMessage("====> locationsInAcquisitionPhilips\t%d\n", locationsInAcquisitionPhilips); break; case kDiffusionDirectionRL: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; vRLPhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); break; case kDiffusionDirectionAP: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; vAPPhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); break; case kDiffusionDirectionFH: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; vFHPhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); 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: readCSAImageHeader(&buffer[lPos], lLength, &d.CSA, isVerbose); //, 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); //printMessage("RWVScale %g\n", d.RWVScale); if (isSameFloat(1.0, d.intenScale)) //give precedence to standard value d.intenScale = d.RWVScale; break; case kUserDefineDataGE: { //0043,102A if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 128)) break; #define MY_DEBUG_GE // <- uncomment this to use following code to infer GE phase encoding direction #ifdef MY_DEBUG_GE int isVerboseX = isVerbose; //for debugging only - in standard release we will enable user defined "isVerbose" //int isVerboseX = 2; if (isVerboseX > 1) printMessage(" UserDefineDataGE file offset/length %ld %u\n", lFileOffset+lPos, lLength); if (lLength < 916) { //minimum size is hdr_offset=0, read 0x0394 printMessage(" GE header too small to be valid (A)\n"); break; } //debug code to export binary data /* char str[kDICOMStr]; sprintf(str, "%s_ge.bin",fname); FILE *pFile = fopen(str, "wb"); fwrite(&buffer[lPos], 1, lLength, pFile); fclose (pFile); */ if ((size_t)(lPos + lLength) > MaxBufferSz) { //we could re-read the buffer in this case, however in practice GE headers are concise so we never see this issue printMessage(" GE header overflows buffer\n"); break; } uint16_t hdr_offset = dcmInt(2,&buffer[lPos+24],true); if (isVerboseX > 1) printMessage(" header offset: %d\n", hdr_offset); if (lLength < (hdr_offset+916)) { //minimum size is hdr_offset=0, read 0x0394 printMessage(" GE header too small to be valid (B)\n"); break; } //size_t hdr = lPos+hdr_offset; float version = dcmFloat(4,&buffer[lPos + hdr_offset],true); if (isVerboseX > 1) printMessage(" version %g\n", version); if (version < 5.0 || version > 40.0) { //printMessage(" GE header file format incorrect %g\n", version); break; } //char const *hdr = &buffer[lPos + hdr_offset]; char *hdr = (char *)&buffer[lPos + hdr_offset]; int epi_chk_off = 0x003a; int pepolar_off = 0x0030; int kydir_off = 0x0394; if (version >= 25.002) { hdr += 0x004c; kydir_off -= 0x008c; } //int seqOrInter =dcmInt(2,(unsigned char*)(hdr + pepolar_off-638),true); //int seqOrInter2 =dcmInt(2,(unsigned char*)(hdr + kydir_off-638),true); //printf("%d %d<<<\n", seqOrInter,seqOrInter2); //check if EPI if (true) { //int check = *(short const *)(hdr + epi_chk_off) & 0x800; int check =dcmInt(2,(unsigned char*)hdr + epi_chk_off,true) & 0x800; if (check == 0) { if (isVerboseX > 1) printMessage("%s: Warning: Data is not EPI\n", fname); break; } } //Check for PE polarity // int flag1 = *(short const *)(hdr + pepolar_off) & 0x0004; //Check for ky direction (view order) // int flag2 = *(int const *)(hdr + kydir_off); int phasePolarityFlag = dcmInt(2,(unsigned char*)hdr + pepolar_off,true) & 0x0004; //Check for ky direction (view order) int sliceOrderFlag = dcmInt(2,(unsigned char*)hdr + kydir_off,true); if (isVerboseX > 1) printMessage(" GE phasePolarity/sliceOrder flags %d %d\n", phasePolarityFlag, sliceOrderFlag); if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_FLIPPED) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) { //https://cfmriweb.ucsd.edu/Howto/3T/operatingtips.html if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; else d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; } //if (sliceOrderFlag == kGE_SLICE_ORDER_TOP_DOWN) // d.sliceOrderGE = kGE_SLICE_ORDER_TOP_DOWN; //if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) // d.sliceOrderGE = kGE_SLICE_ORDER_BOTTOM_UP; #endif break; } case kEffectiveEchoSpacingGE: if (d.manufacturer == kMANUFACTURER_GE) d.effectiveEchoSpacingGE = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); break; case kDiffusionBFactorGE : if (d.manufacturer == kMANUFACTURER_GE) set_bValGE(&volDiffusion, lLength, &buffer[lPos]); 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; //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); isOrient = true; break; } case kImagesInAcquisition : imagesInAcquisition = dcmStrInt(lLength, &buffer[lPos]); break; case kImageStart: //if ((!geiisBug) && (!isIconImageSequence)) //do not exit for proprietary thumbnails if (isIconImageSequence) { int imgBytes = (d.xyzDim[1] * d.xyzDim[2] * int(d.bitsAllocated / 8)); if (imgBytes == lLength) isIconImageSequence = false; if (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 (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 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]=='O') && (vr[1]=='B')) isStr = xxx; //if ((vr[0]=='O') && (vr[1]=='D')) isStr = xxx; //if ((vr[0]=='O') && (vr[1]=='F')) isStr = xxx; //if ((vr[0]=='O') && (vr[1]=='W')) isStr = xxx; if ((vr[0]=='P') && (vr[1]=='N')) isStr = true; if ((vr[0]=='S') && (vr[1]=='H')) isStr = true; if ((vr[0]=='S') && (vr[1]=='T')) isStr = true; if ((vr[0]=='T') && (vr[1]=='M')) isStr = true; if ((vr[0]=='U') && (vr[1]=='I')) isStr = true; if ((vr[0]=='U') && (vr[1]=='T')) isStr = true; } else isStr = (lLength > 12); //implicit encoding: not always true as binary vectors may exceed 12 bytes, but often true if (lLength > 128) { sprintf(str, "%s<%d bytes> ", str, lLength); printMessage("%s\n", str); } else if (isStr) { //if length is greater than 8 bytes (+4 hdr) the MIGHT be a string char tagStr[kDICOMStr]; tagStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr 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] == '^') || (tagStr[pos] < 33) || (tagStr[pos] == '*') || (tagStr[pos] == '|') || (tagStr[pos] == '?')) tagStr[pos] = 'x'; } 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); lPos = lPos + (lLength); //printMessage("%d\n",d.imageStart); } //while d.imageStart == 0 free (buffer); 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 //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.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); } //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]; strncpy(acquisitionDateTxt, acquisitionDateTimeTxt, kYYYYMMDDlen); acquisitionDateTxt[kYYYYMMDDlen] = '\0'; // IMPORTANT! d.acquisitionDate = atof(acquisitionDateTxt); char acquisitionTimeTxt[kDICOMStr]; int timeLen = (int)strlen(acquisitionDateTimeTxt) - kYYYYMMDDlen; strncpy(acquisitionTimeTxt, &acquisitionDateTimeTxt[kYYYYMMDDlen], timeLen); acquisitionTimeTxt[timeLen] = '\0'; // IMPORTANT! d.acquisitionTime = atof(acquisitionTimeTxt); } d.dateTime = (atof(d.studyDate)* 1000000) + atof(d.studyTime); //printMessage("slices in Acq %d %d\n",d.locationsInAcquisition,locationsInAcquisitionPhilips); 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) ) { //printMessage("Check number of slices, discrepancy between tags (0054,0081; 0020,1002; 0021,104F)\n"); if (d.locationsInAcquisition < locationsInAcquisitionGE) d.locationsInAcquisition = 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 ((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 ((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) && (strlen(d.seriesDescription) > 1)) //GE uses a generic session name here: do not overwrite kProtocolNameGE strcpy(d.protocolName, d.seriesDescription); if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1)) strcpy(d.protocolName, d.seriesDescription); if ((strlen(d.protocolName) > 1) && (isMoCo)) strcat (d.protocolName,"_MoCo"); //disambiguate MoCo https://github.com/neurolabusc/MRIcroGL/issues/31 if ((strlen(d.protocolName) < 1) && (strlen(d.sequenceName) > 1) && (d.manufacturer != kMANUFACTURER_SIEMENS)) strcpy(d.protocolName, d.sequenceName); //protocolName (0018,1030) optional, sequence name (0018,0024) is not a good substitute for Siemens as it can vary per volume: *ep_b0 *ep_b1000#1, *ep_b1000#2, etc https://www.nitrc.org/forum/forum.php?thread_id=8771&forum_id=4703 // if (!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 ((locationsInAcquisitionPhilips > 0) && ((d.xyzDim[3] % locationsInAcquisitionPhilips) == 0)) { d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips; d.xyzDim[3] = locationsInAcquisitionPhilips; } else if ((numberOfDynamicScans > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % numberOfDynamicScans) == 0)) { d.xyzDim[3] = d.xyzDim[3] / numberOfDynamicScans; d.xyzDim[4] = numberOfDynamicScans; } if ((maxInStackPositionNumber > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % maxInStackPositionNumber) == 0)) { d.xyzDim[4] = d.xyzDim[3] / maxInStackPositionNumber; d.xyzDim[3] = maxInStackPositionNumber; } if ((!isnan(patientPositionStartPhilips[1])) && (!isnan(patientPositionEndPhilips[1]))) { for (int k = 0; k < 4; k++) { d.patientPosition[k] = patientPositionStartPhilips[k]; d.patientPositionLast[k] = patientPositionEndPhilips[k]; } } if (!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 ((numDimensionIndexValues > 1) && (numDimensionIndexValues == numberOfFrames)) { //Philips enhanced datasets can have custom slice orders and pack images with different TE, Phase/Magnitude/Etc. if (isVerbose > 1) { // int mn[MAX_NUMBER_OF_DIMENSIONS]; int mx[MAX_NUMBER_OF_DIMENSIONS]; for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) { mx[j] = dcmDim[0].dimIdx[j]; mn[j] = mx[j]; for (int i = 0; i < numDimensionIndexValues; i++) { if (mx[j] < dcmDim[i].dimIdx[j]) mx[j] = dcmDim[i].dimIdx[j]; if (mn[j] > dcmDim[i].dimIdx[j]) mn[j] = dcmDim[i].dimIdx[j]; } } printMessage(" DimensionIndexValues (0020,9157), dimensions with variability:\n"); for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) if (mn[i] != mx[i]) printMessage(" Dimension %d Range: %d..%d\n", i, mn[i], mx[i]); } //verbose > 1 if (d.manufacturer != kMANUFACTURER_BRUKER) { //only single sample Bruker - perhaps use 0020,9057 to identify if space or time is 3rd dimension //sort dimensions #ifdef USING_R std::sort(dcmDim.begin(), dcmDim.begin() + numberOfFrames, compareTDCMdim); #else qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdim); #endif } //for (int i = 0; i < numberOfFrames; i++) // printf("%d -> %d %d %d %d\n", i, dcmDim[i].diskPos, dcmDim[i].dimIdx[1], dcmDim[i].dimIdx[2], dcmDim[i].dimIdx[3]); for (int i = 0; i < numberOfFrames; i++) dti4D->sliceOrder[i] = dcmDim[i].diskPos; if ((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++) { //dti4D->gradDynVol[i] = 0; //only PAR/REC dti4D->TE[i] = dcmDim[j+(i * d.xyzDim[3])].TE; dti4D->intenScale[i] = dcmDim[j+(i * d.xyzDim[3])].intenScale; dti4D->intenIntercept[i] = dcmDim[j+(i * d.xyzDim[3])].intenIntercept; dti4D->isPhase[i] = dcmDim[j+(i * d.xyzDim[3])].isPhase; dti4D->isReal[i] = dcmDim[j+(i * d.xyzDim[3])].isReal; dti4D->isImaginary[i] = dcmDim[j+(i * d.xyzDim[3])].isImaginary; dti4D->intenScalePhilips[i] = dcmDim[j+(i * d.xyzDim[3])].intenScalePhilips; dti4D->RWVIntercept[i] = dcmDim[j+(i * d.xyzDim[3])].RWVIntercept; dti4D->RWVScale[i] = dcmDim[j+(i * d.xyzDim[3])].RWVScale; dti4D->triggerDelayTime[i] = dcmDim[j+(i * d.xyzDim[3])].triggerDelayTime; dti4D->S[i].V[0] = dcmDim[j+(i * d.xyzDim[3])].V[0]; dti4D->S[i].V[1] = dcmDim[j+(i * d.xyzDim[3])].V[1]; dti4D->S[i].V[2] = dcmDim[j+(i * d.xyzDim[3])].V[2]; dti4D->S[i].V[3] = dcmDim[j+(i * d.xyzDim[3])].V[3]; if (dti4D->TE[i] != d.TE) isTEvaries = true; if (dti4D->intenScale[i] != d.intenScale) isScaleVaries = true; if (dti4D->intenIntercept[i] != d.intenIntercept) isScaleVaries = 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((isScaleVaries) || (isTEvaries)) d.isScaleOrTEVaries = true; if (isTEvaries) d.isMultiEcho = true; //if echoVaries,count number of echoes /*int echoNum = 1; for (int i = 1; i < d.xyzDim[4]; i++) { if (dti4D->TE[i-1] != dti4D->TE[i]) }*/ if ((isVerbose) && (d.isScaleOrTEVaries)) { printMessage("Parameters vary across 3D volumes packed in single DICOM file:\n"); for (int i = 0; i < d.xyzDim[4]; i++) printMessage(" %d TE=%g Slope=%g Inter=%g PhilipsScale=%g Phase=%d\n", i, dti4D->TE[i], dti4D->intenScale[i], dti4D->intenIntercept[i], dti4D->intenScalePhilips[i], dti4D->isPhase[i] ); } } if ((d.xyzDim[3] == maxInStackPositionNumber) && (maxInStackPositionNumber > 1) && (d.zSpacing <= 0.0)) { float dx = sqrt( pow(d.patientPosition[1]-d.patientPositionLast[1],2)+ pow(d.patientPosition[2]-d.patientPositionLast[2],2)+ pow(d.patientPosition[3]-d.patientPositionLast[3],2)); dx = dx / (maxInStackPositionNumber - 1); if ((dx > 0.0) && (!isSameFloatGE(dx, d.xyzMM[3])) ) //patientPosition has some rounding error d.xyzMM[3] = dx; } //d.zSpacing <= 0.0: Bruker does not populate 0018,0088 https://github.com/rordenlab/dcm2niix/issues/241 } //if numDimensionIndexValues > 1 : enhanced DICOM /* //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 (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 ((d.isValid) && (d.imageNum == 0)) { //Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers printError("Instance number (0020,0013) not found: %s\n", fname); 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)){ // long timeCRC = abs( (long)mz_crc32((unsigned char*) &contentTime, sizeof(double))); //} if ((isInterpolated) && (d.imageNum <= 1)) printWarning("interpolated protocol '%s' may be unsuitable for dwidenoise/mrdegibbs. %s\n", d.protocolName, fname); if (numDimensionIndexValues < MAX_NUMBER_OF_DIMENSIONS) //https://github.com/rordenlab/dcm2niix/issues/221 d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS-1] = (uint32_t)abs( (long)mz_crc32((unsigned char*) &d.seriesInstanceUID, strlen(d.seriesInstanceUID))); if (d.seriesNum < 1) //https://github.com/rordenlab/dcm2niix/issues/218 d.seriesNum = (long)abs( (long)mz_crc32((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 //printf("%g\t\t%g\t%g\t%g\t%s\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3], fname); //printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz); return d; } // readDICOM() struct TDICOMdata readDICOM(char * fname) { TDTI4D unused; return readDICOMv(fname, false, kCompressSupport, &unused); } // readDICOM() dcm2niix-1.0.20181125/console/nii_dicom.h000066400000000000000000000225121337661136700175550ustar00rootroot00000000000000#include //requires VS 2015 or later #include #include #include "nifti1_io_core.h" #ifndef USING_R #include "nifti1.h" #endif #ifndef MRIpro_nii_dcm_h #define MRIpro_nii_dcm_h #ifdef __cplusplus extern "C" { #endif #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) #define kLSsuf " (JP-LS:CharLS)" #else #define kLSsuf "" #endif #ifdef myEnableJasper #define kJP2suf " (JP2:JasPer)" #else #ifdef myDisableOpenJPEG #define kJP2suf "" #else #define kJP2suf " (JP2:OpenJPEG)" #endif #endif #if defined(__ICC) || defined(__INTEL_COMPILER) #define kCCsuf " IntelCC" STR(__INTEL_COMPILER) #elif defined(_MSC_VER) #define kCCsuf " MSC" STR(_MSC_VER) #elif defined(__clang__) #define kCCsuf " Clang" STR(__clang_major__) "." STR(__clang_minor__) "." STR(__clang_patchlevel__) #elif defined(__GNUC__) || defined(__GNUG__) #define kCCsuf " GCC" STR(__GNUC__) "." STR(__GNUC_MINOR__) "." STR(__GNUC_PATCHLEVEL__) #else #define kCCsuf " CompilerNA" //unknown compiler! #endif #define kDCMvers "v1.0.20181125 " kJP2suf kLSsuf kCCsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic static const int kMaxDTI4D = 18000; //maximum number of DTI directions for 4D (Philips) images, also maximum number of 3D slices for Philips 3D and 4D images static const int kMaxSlice2D = 64000; //maximum number of 2D slices in 4D (Philips) images #define kDICOMStr 64 #define kDICOMStrLarge 256 #define kMANUFACTURER_UNKNOWN 0 #define kMANUFACTURER_SIEMENS 1 #define kMANUFACTURER_GE 2 #define kMANUFACTURER_PHILIPS 3 #define kMANUFACTURER_TOSHIBA 4 #define kMANUFACTURER_UIH 5 #define kMANUFACTURER_BRUKER 6 //note: note a complete modality list, e.g. XA,PX, etc #define kMODALITY_UNKNOWN 0 #define kMODALITY_CR 1 #define kMODALITY_CT 2 #define kMODALITY_MR 3 #define kMODALITY_PT 4 #define kMODALITY_US 5 //GE phase encoding #define kGE_PHASE_ENCODING_POLARITY_UNKNOWN -1 #define kGE_PHASE_ENCODING_POLARITY_UNFLIPPED 0 #define kGE_PHASE_ENCODING_POLARITY_FLIPPED 4 #define kGE_SLICE_ORDER_UNKNOWN -1 #define kGE_SLICE_ORDER_TOP_DOWN 0 #define kGE_SLICE_ORDER_BOTTOM_UP 2 //#define kGE_PHASE_DIRECTION_CENTER_OUT_REV 3 //#define kGE_PHASE_DIRECTION_CENTER_OUT 4 #define kEXIT_NO_VALID_FILES_FOUND 2 static const int kSliceOrientUnknown = 0; static const int kSliceOrientTra = 1; static const int kSliceOrientSag = 2; static const int kSliceOrientCor = 3; static const int kSliceOrientMosaicNegativeDeterminant = 4; static const int kCompressNone = 0; static const int kCompressYes = 1; static const int kCompressC3 = 2; //obsolete JPEG lossless static const int kCompress50 = 3; //obsolete JPEG lossy static const int kCompressRLE = 4; //run length encoding static const int kCompressPMSCT_RLE1 = 5; //see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1 static const int kCompressJPEGLS = 5; //LoCo JPEG-LS #ifdef myEnableJasper static const int kCompressSupport = kCompressYes; //JASPER for JPEG2000 #else #ifdef myDisableOpenJPEG static const int kCompressSupport = kCompressNone; //no decompressor #else static const int kCompressSupport = kCompressYes; //OPENJPEG for JPEG2000 #endif #endif // Maximum number of dimensions for .dimensionIndexValues, i.e. possibly the // number of axes in the output .nii. static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; struct TDTI { float V[4]; //int totalSlicesIn4DOrder; }; struct TDTI4D { struct TDTI S[kMaxDTI4D]; int sliceOrder[kMaxSlice2D]; // [7,3,2] means the first slice on disk should be moved to 7th position int gradDynVol[kMaxDTI4D]; //used to parse dimensions of Philips data, e.g. file with multiple dynamics, echoes, phase+magnitude float triggerDelayTime[kMaxDTI4D], TE[kMaxDTI4D], RWVScale[kMaxDTI4D], RWVIntercept[kMaxDTI4D], intenScale[kMaxDTI4D], intenIntercept[kMaxDTI4D], intenScalePhilips[kMaxDTI4D]; bool isReal[kMaxDTI4D]; bool isImaginary[kMaxDTI4D]; bool isPhase[kMaxDTI4D]; }; #ifdef _MSC_VER //Microsoft nomenclature for packed structures is different... #pragma pack(2) typedef struct { char name[64]; //null-terminated int32_t vm; char vr[4]; // possibly nul-term string int32_t syngodt;// ?? int32_t nitems;// number of items in CSA int32_t xx;// maybe == 77 or 205 } TCSAtag; //Siemens csa tag structure typedef struct { int32_t xx1, xx2_Len, xx3_77, xx4; } TCSAitem; //Siemens csa item structure #pragma pack() #else typedef struct __attribute__((packed)) { char name[64]; //null-terminated int32_t vm; char vr[4]; // possibly nul-term string int32_t syngodt;// ?? int32_t nitems;// number of items in CSA int32_t xx;// maybe == 77 or 205 } TCSAtag; //Siemens csa tag structure typedef struct __attribute__((packed)) { int32_t xx1, xx2_Len, xx3_77, xx4; } TCSAitem; //Siemens csa item structure #endif struct TCSAdata { float sliceTiming[kMaxEPI3D], dtiV[4], sliceNormV[4], bandwidthPerPixelPhaseEncode, sliceMeasurementDuration; int numDti, SeriesHeader_offset, SeriesHeader_length, multiBandFactor, sliceOrder, slice_start, slice_end, mosaicSlices, protocolSliceNumber1, phaseEncodingDirectionPositive; bool isPhaseMap; }; struct TDICOMdata { long seriesNum; int xyzDim[5]; uint32_t coilCrc; int numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, compressionScheme; float imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, 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 ecat_isotope_halflife, ecat_dosage; double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionAddress[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageType[kDICOMStr], institutionalDepartmentName[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr],seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr],sequenceVariant[kDICOMStr],scanningSequence[kDICOMStr], patientBirthDate[kDICOMStr], patientAge[kDICOMStr], studyDate[kDICOMStr],studyTime[kDICOMStr]; char imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; struct TCSAdata CSA; bool isCoilVaries, isNonParallelSlices, isSegamiOasis, isXA10A, isScaleOrTEVaries, isDerived, isXRay, isMultiEcho, isValid, is3DAcq, is2DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase, isHasImaginary, isHasReal, isHasMagnitude,isHasMixed, isFloat, isResampled, isLocalizer; char phaseEncodingRC, patientSex; }; size_t nii_ImgBytes(struct nifti_1_header hdr); int isSameFloatGE (float a, float b); void getFileName( char *pathParent, const char *path); struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, 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_flipZ(unsigned char* bImg, struct nifti_1_header *h); //*unsigned char * nii_reorderSlices(unsigned char* bImg, struct nifti_1_header *h, struct TDTI4D *dti4D); void changeExt (char *file_name, const char* ext); unsigned char * nii_planar2rgb(unsigned char* bImg, struct nifti_1_header *hdr, int isPlanar); int isDICOMfile(const char * fname); //0=not DICOM, 1=DICOM, 2=NOTSURE(not part 10 compliant) void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose); int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose); int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm) ; unsigned char * nii_loadImgXL(char* imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D); #ifdef __cplusplus } #endif #endif dcm2niix-1.0.20181125/console/nii_dicom_batch.cpp000066400000000000000000006175601337661136700212660ustar00rootroot00000000000000//#define myNoSave //do not save images to disk #ifdef _MSC_VER #include #define getcwd _getcwd #define chdir _chrdir #include "io.h" //#include #define MiniZ #else #include #ifdef myDisableMiniZ #undef MiniZ #else #define MiniZ #endif #endif #if defined(__APPLE__) && defined(__MACH__) #endif #ifndef myDisableZLib #ifdef MiniZ #include "miniz.c" //single file clone of libz #else #include #endif #else #undef MiniZ #endif #include "tinydir.h" #include "print.h" #include "nifti1_io_core.h" #ifndef USING_R #include "nifti1.h" #endif #include "nii_dicom_batch.h" #include "nii_foreign.h" #include "nii_dicom.h" #include //toupper #include #include #include //requires VS 2015 or later #include #include #include #include #include #include //#ifdef myEnableOtsu // #include "nii_ostu_ml.h" //provide better brain crop, but artificially reduces signal variability in air //#endif #include // clock_t, clock, CLOCKS_PER_SEC #include "nii_ortho.h" #if defined(_WIN64) || defined(_WIN32) #include //write to registry #endif #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #if defined(_WIN64) || defined(_WIN32) const char kPathSeparator ='\\'; const char kFileSep[2] = "\\"; #else const char kPathSeparator ='/'; const char kFileSep[2] = "/"; #endif #ifdef USING_R #include "ImageList.h" #undef isnan #define isnan ISNAN #endif struct TDCMsort { uint64_t indx, img; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; }; struct TSearchList { unsigned long numItems, maxItems; char **str; }; #ifndef PATH_MAX #define PATH_MAX 4096 #endif void dropFilenameFromPath(char *path) { // const char *dirPath = strrchr(path, '/'); //UNIX if (dirPath == 0) dirPath = strrchr(path, '\\'); //Windows if (dirPath == NULL) { strcpy(path,""); } else path[dirPath - path] = 0; // please make sure there is enough space in TargetDirectory if (strlen(path) == 0) { //file name did not specify path, assume relative path and return current working directory //strcat (path,"."); //relative path - use cwd <- not sure if this works on Windows! char cwd[PATH_MAX]; char* ok = getcwd(cwd, sizeof(cwd)); if (ok !=NULL) strcat (path,cwd); } } void dropTrailingFileSep(char *path) { // size_t len = strlen(path) - 1; if (len <= 0) return; if (path[len] == '/') path[len] = '\0'; else if (path[len] == '\\') path[len] = '\0'; } bool is_fileexists(const char * filename) { FILE * fp = NULL; if ((fp = fopen(filename, "r"))) { fclose(fp); return true; } return false; } #ifndef S_ISDIR #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #endif #ifndef S_ISREG #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) #endif bool is_fileNotDir(const char* path) { //returns false if path is a folder; requires #include struct stat buf; stat(path, &buf); return S_ISREG(buf.st_mode); } //is_file() bool is_exe(const char* path) { //requires #include struct stat buf; if (stat(path, &buf) != 0) return false; //file does not eist if (!S_ISREG(buf.st_mode)) return false; //not regular file, e.g. '..' return (buf.st_mode & 0111) ; //return (S_ISREG(buf.st_mode) && (buf.st_mode & 0111) ); } //is_exe() #if defined(_WIN64) || defined(_WIN32) //Windows does not support lstat int is_dir(const char *pathname, int follow_link) { struct stat s; if ((NULL == pathname) || (0 == strlen(pathname))) return 0; int err = stat(pathname, &s); if(-1 == err) return 0; // does not exist else { if(S_ISDIR(s.st_mode)) { return 1; // it's a dir } else { return 0;// exists but is no dir } } }// is_dir() #else //if windows else Unix int is_dir(const char *pathname, int follow_link) { struct stat s; int retval; if ((NULL == pathname) || (0 == strlen(pathname))) return 0; // does not exist retval = follow_link ? stat(pathname, &s) : lstat(pathname, &s); if ((-1 != retval) && (S_ISDIR(s.st_mode))) return 1; // it's a dir return 0; // exists but is no dir }// is_dir() #endif void geCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx){ //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) return; if (d->CSA.numDti < 1) return; if ((toupper(d->patientOrient[0])== 'H') && (toupper(d->patientOrient[1])== 'F') && (toupper(d->patientOrient[2])== 'S')) ; //participant was head first supine else { printMessage("GE DTI directions require head first supine acquisition\n"); return; } bool col = false; if (d->phaseEncodingRC == 'C') col = true; else if (d->phaseEncodingRC != 'R') { printWarning("Unable to determine DTI gradients, 0018,1312 should be either R or C"); return; } if (abs(sliceDir) != 3) printWarning("GE DTI only tested for axial acquisitions (solution: use Xiangrui Li's dicm2nii)\n"); //GE vectors from Xiangrui Li' dicm2nii, validated with datasets from https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging ivec3 flp; if (abs(sliceDir) == 1) flp = setiVec3(1, 1, 0); //SAGITTAL else if (abs(sliceDir) == 2) flp = setiVec3(0, 1, 1); //CORONAL else if (abs(sliceDir) == 3) flp = setiVec3(0, 0, 1); //AXIAL else { printMessage("Impossible GE slice orientation!"); flp = setiVec3(0, 0, 1); //AXIAL??? } if (sliceDir < 0) flp.v[2] = 1 - flp.v[2]; 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) printMessage(" 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 https://github.com/rordenlab/dcm2niix/issues/163 float bVal = vx[i].V[0] * (vLen * vLen); 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]/vLen; vx[i].V[2] = vx[i].V[2]/vLen; vx[i].V[3] = vx[i].V[3]/vLen; } if (!col) { //rows need to be swizzled //see Stanford dataset Ax_DWI_Tetrahedral_7 unable to resolve between possible solutions // http://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging float swap = vx[i].V[1]; vx[i].V[1] = vx[i].V[2]; vx[i].V[2] = swap; vx[i].V[2] = -vx[i].V[2]; //because of transpose? } for (int v = 0; v < 3; v++) if (flp.v[v] == 1) vx[i].V[v+1] = -vx[i].V[v+1]; vx[i].V[2] = -vx[i].V[2]; //we read out Y-direction opposite order as dicm2nii, see also opts.isFlipY } //These next lines are only so files appear identical to old versions of dcm2niix: // dicm2nii and dcm2niix generate polar opposite gradient directions. // this does not matter, since intensity is the normal of the gradient vector. for (int i = 0; i < d->CSA.numDti; i++) for (int v = 1; v < 4; v++) vx[i].V[v] = -vx[i].V[v]; //These next lines convert any "-0" values to "0" for (int i = 0; i < d->CSA.numDti; i++) for (int v = 1; v < 4; v++) if (isSameFloat(vx[i].V[v],-0)) vx[i].V[v] = 0.0f; }// geCorrectBvecs() void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx){ //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->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\n", 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 (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)) { 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); }// siemensPhilipsCorrectBvecs() bool isNanPosition(struct TDICOMdata d) { //in 2007 some Siemens RGB DICOMs did not include the PatientPosition 0020,0032 tag if (isnan(d.patientPosition[1])) return true; if (isnan(d.patientPosition[2])) return true; if (isnan(d.patientPosition[3])) return true; return false; }// isNanPosition() bool isSamePosition(struct TDICOMdata d, struct TDICOMdata d2){ if ( isNanPosition(d) || isNanPosition(d2)) return false; if (!isSameFloat(d.patientPosition[1],d2.patientPosition[1])) return false; if (!isSameFloat(d.patientPosition[2],d2.patientPosition[2])) return false; if (!isSameFloat(d.patientPosition[3],d2.patientPosition[3])) return false; return true; }// isSamePosition() void nii_SaveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, char * dcmname) { if (!opts.isCreateText) return; char txtname[2048] = {""}; strcpy (txtname,pathoutname); strcat (txtname,".txt"); //printMessage("Saving text %s\n",txtname); FILE *fp = fopen(txtname, "w"); fprintf(fp, "%s\tField Strength:\t%g\tProtocolName:\t%s\tScanningSequence00180020:\t%s\tTE:\t%g\tTR:\t%g\tSeriesNum:\t%ld\tAcquNum:\t%d\tImageNum:\t%d\tImageComments:\t%s\tDateTime:\t%f\tName:\t%s\tConvVers:\t%s\tDoB:\t%s\tGender:\t%c\tAge:\t%s\tDimXYZT:\t%d\t%d\t%d\t%d\tCoil:\t%d\tEchoNum:\t%d\tOrient(6)\t%g\t%g\t%g\t%g\t%g\t%g\tbitsAllocated\t%d\tInputName\t%s\n", pathoutname, d.fieldStrength, d.protocolName, d.scanningSequence, d.TE, d.TR, d.seriesNum, d.acquNum, d.imageNum, d.imageComments, d.dateTime, d.patientName, kDCMvers, d.patientBirthDate, d.patientSex, d.patientAge, h->dim[1], h->dim[2], h->dim[3], h->dim[4], d.coilCrc,d.echoNum, d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6], d.bitsAllocated, dcmname); fclose(fp); }// nii_SaveText() #define myReadAsciiCsa #ifdef myReadAsciiCsa //read from the ASCII portion of the Siemens CSA series header // this is not recommended: poorly documented // it is better to stick to the binary portion of the Siemens CSA image header #if defined(_WIN64) || defined(_WIN32) //https://opensource.apple.com/source/Libc/Libc-1044.1.2/string/FreeBSD/memmem.c /*- * Copyright (c) 2005 Pascal Gloor * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ const void * memmem(const char *l, size_t l_len, const char *s, size_t s_len) { char *cur, *last; const char *cl = (const char *)l; const char *cs = (const char *)s; /* we need something to compare */ if (l_len == 0 || s_len == 0) return NULL; /* "s" must be smaller or equal to "l" */ if (l_len < s_len) return NULL; /* special case where s_len == 1 */ if (s_len == 1) return memchr(l, (int)*cs, l_len); /* the last position where its possible to find "s" in "l" */ last = (char *)cl + l_len - s_len; for (cur = (char *)cl; cur <= last; cur++) if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) return cur; return NULL; } //n.b. memchr returns "const void *" not "void *" for Windows C++ https://msdn.microsoft.com/en-us/library/d7zdhf37.aspx #endif //for systems without memmem int 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 0; int i = (int)strlen(key); while( ( i< remLength) && (keyPos[i] != 0x0A) ) { if( keyPos[i] >= '0' && keyPos[i] <= '9' ) ret = (10 * ret) + keyPos[i] - '0'; i++; } return ret; } //readKey() float readKeyFloat(const char * key, char * buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); if (!keyPos) return 0.0; char str[kDICOMStr]; strcpy(str, ""); char tmpstr[2]; tmpstr[1] = 0; int i = (int)strlen(key); while( ( i< remLength) && (keyPos[i] != 0x0A) ) { if( (keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] <= '.') || (keyPos[i] <= '-') ) { tmpstr[0] = keyPos[i]; strcat (str, tmpstr); } i++; } if (strlen(str) < 1) return 0.0; return atof(str); } //readKeyFloat() void readKeyStr(const char * key, char * buffer, int remLength, char* outStr) { //if key is CoilElementID.tCoilID the string 'CoilElementID.tCoilID = ""Head_32""' returns 'Head32' strcpy(outStr, ""); char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); if (!keyPos) return; int i = (int)strlen(key); int outLen = 0; char tmpstr[2]; tmpstr[1] = 0; bool isQuote = false; while( ( i < remLength) && (keyPos[i] != 0x0A) ) { if ((isQuote) && (keyPos[i] != '"') && (outLen < kDICOMStrLarge)) { tmpstr[0] = keyPos[i]; strcat (outStr, tmpstr); outLen ++; } if (keyPos[i] == '"') { if (outLen > 0) break; isQuote = true; } i++; } } //readKeyStr() int phoenixOffsetCSASeriesHeader(unsigned char *buff, int lLength) { //returns offset to ASCII Phoenix data if (lLength < 36) return 0; if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0') ) return EXIT_FAILURE; int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2 int lnTag = buff[lPos]+(buff[lPos+1]<<8)+(buff[lPos+2]<<16)+(buff[lPos+3]<<24); if ((buff[lPos+4] != 77) || (lnTag < 1)) return 0; lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0 TCSAtag tagCSA; TCSAitem itemCSA; for (int lT = 1; lT <= lnTag; lT++) { memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag //if (!littleEndianPlatform()) // nifti_swap_4bytes(1, &tagCSA.nitems); //printf("%d CSA of %s %d\n",lPos, tagCSA.name, tagCSA.nitems); lPos +=sizeof(tagCSA); if (strcmp(tagCSA.name, "MrPhoenixProtocol") == 0) return lPos; for (int lI = 1; lI <= tagCSA.nitems; lI++) { memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); lPos +=sizeof(itemCSA); //if (!littleEndianPlatform()) // nifti_swap_4bytes(1, &itemCSA.xx2_Len); lPos += ((itemCSA.xx2_Len +3)/4)*4; } } return 0; } // phoenixOffsetCSASeriesHeader() void siemensCsaAscii(const char * filename, int csaOffset, int csaLength, float* delayTimeInTR, float* phaseOversampling, float* phaseResolution, float* txRefAmp, float* shimSetting, int* baseResolution, int* interp, int* partialFourier, int* echoSpacing, int* parallelReductionFactorInPlane, 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 *delayTimeInTR = 0.0; *phaseOversampling = 0.0; *phaseResolution = 0.0; *txRefAmp = 0.0; *baseResolution = 0; *interp = 0; *partialFourier = 0; *echoSpacing = 0; for (int i = 0; i < 8; i++) shimSetting[i] = 0.0; strcpy(coilID, ""); strcpy(consistencyInfo, ""); strcpy(coilElements, ""); strcpy(pulseSequenceDetails, ""); strcpy(fmriExternalInfo, ""); strcpy(wipMemBlock, ""); strcpy(protocolName, ""); if ((csaOffset < 0) || (csaLength < 8)) return; FILE * pFile = fopen ( filename, "rb" ); if(pFile==NULL) return; fseek (pFile , 0 , SEEK_END); long lSize = ftell (pFile); if (lSize < (csaOffset+csaLength)) { fclose (pFile); return; } fseek(pFile, csaOffset, SEEK_SET); char * buffer = (char*) malloc (csaLength); if(buffer == NULL) return; size_t result = fread (buffer,1,csaLength,pFile); if ((int)result != csaLength) return; //next bit complicated: restrict to ASCII portion to avoid buffer overflow errors in BINARY portion int startAscii = phoenixOffsetCSASeriesHeader((unsigned char *)buffer, csaLength); int csaLengthTrim = csaLength; char * bufferTrim = buffer; if ((startAscii > 0) && (startAscii < csaLengthTrim) ) { //ignore binary data at start bufferTrim += startAscii; csaLengthTrim -= startAscii; } char keyStr[] = "### ASCCONV BEGIN"; //skip to start of ASCII often "### ASCCONV BEGIN ###" but also "### ASCCONV BEGIN object=MrProtDataImpl@MrProtocolData" char *keyPos = (char *)memmem(bufferTrim, csaLengthTrim, keyStr, strlen(keyStr)); if (keyPos) { //We could detect multi-echo MPRAGE here, e.g. "lContrasts = 4"- but ideally we want an earlier detection csaLengthTrim -= (keyPos-bufferTrim); //FmriExternalInfo listed AFTER AscConvEnd and uses different delimiter || // char keyStrExt[] = "FmriExternalInfo"; // readKeyStr(keyStrExt, keyPos, csaLengthTrim, fmriExternalInfo); #define myCropAtAscConvEnd #ifdef myCropAtAscConvEnd char keyStrEnd[] = "### ASCCONV END"; char *keyPosEnd = (char *)memmem(keyPos, csaLengthTrim, keyStrEnd, strlen(keyStrEnd)); if ((keyPosEnd) && ((keyPosEnd - keyPos) < csaLengthTrim)) //ignore binary data at end csaLengthTrim = (int)(keyPosEnd - keyPos); #endif char keyStrES[] = "sFastImaging.lEchoSpacing"; *echoSpacing = readKey(keyStrES, keyPos, csaLengthTrim); char keyStrBase[] = "sKSpace.lBaseResolution"; *baseResolution = readKey(keyStrBase, keyPos, csaLengthTrim); char keyStrInterp[] = "sKSpace.uc2DInterpolation"; *interp = readKey(keyStrInterp, keyPos, csaLengthTrim); char keyStrPF[] = "sKSpace.ucPhasePartialFourier"; *partialFourier = readKey(keyStrPF, keyPos, csaLengthTrim); //char keyStrETD[] = "sFastImaging.lEchoTrainDuration"; //*echoTrainDuration = readKey(keyStrETD, keyPos, csaLengthTrim); char keyStrAF[] = "sPat.lAccelFactPE"; *parallelReductionFactorInPlane = readKey(keyStrAF, keyPos, csaLengthTrim); //char keyStrEF[] = "sFastImaging.lEPIFactor"; //ret = readKey(keyStrEF, keyPos, csaLengthTrim); char keyStrCoil[] = "sCoilElementID.tCoilID"; readKeyStr(keyStrCoil, keyPos, csaLengthTrim, coilID); char keyStrCI[] = "sProtConsistencyInfo.tMeasuredBaselineString"; readKeyStr(keyStrCI, keyPos, csaLengthTrim, consistencyInfo); char keyStrCS[] = "sCoilSelectMeas.sCoilStringForConversion"; readKeyStr(keyStrCS, keyPos, csaLengthTrim, coilElements); char keyStrSeq[] = "tSequenceFileName"; readKeyStr(keyStrSeq, keyPos, csaLengthTrim, pulseSequenceDetails); char keyStrWipMemBlock[] = "sWipMemBlock.tFree"; readKeyStr(keyStrWipMemBlock, keyPos, csaLengthTrim, wipMemBlock); char keyStrPn[] = "tProtocolName"; readKeyStr(keyStrPn, keyPos, csaLengthTrim, protocolName); char keyStrDelay[] = "lDelayTimeInTR"; *delayTimeInTR = readKeyFloat(keyStrDelay, keyPos, csaLengthTrim); char keyStrOver[] = "sKSpace.dPhaseOversamplingForDialog"; *phaseOversampling = readKeyFloat(keyStrOver, keyPos, csaLengthTrim); char keyStrPhase[] = "sKSpace.dPhaseResolution"; *phaseResolution = readKeyFloat(keyStrPhase, keyPos, csaLengthTrim); char keyStrAmp[] = "sTXSPEC.asNucleusInfo[0].flReferenceAmplitude"; *txRefAmp = readKeyFloat(keyStrAmp, keyPos, csaLengthTrim); //lower order shims: newer sequences char keyStrSh0[] = "sGRADSPEC.asGPAData[0].lOffsetX"; shimSetting[0] = readKeyFloat(keyStrSh0, keyPos, csaLengthTrim); char keyStrSh1[] = "sGRADSPEC.asGPAData[0].lOffsetY"; shimSetting[1] = readKeyFloat(keyStrSh1, keyPos, csaLengthTrim); char keyStrSh2[] = "sGRADSPEC.asGPAData[0].lOffsetZ"; shimSetting[2] = readKeyFloat(keyStrSh2, keyPos, csaLengthTrim); //lower order shims: older sequences char keyStrSh0s[] = "sGRADSPEC.lOffsetX"; if (shimSetting[0] == 0.0) shimSetting[0] = readKeyFloat(keyStrSh0s, keyPos, csaLengthTrim); char keyStrSh1s[] = "sGRADSPEC.lOffsetY"; if (shimSetting[1] == 0.0) shimSetting[1] = readKeyFloat(keyStrSh1s, keyPos, csaLengthTrim); char keyStrSh2s[] = "sGRADSPEC.lOffsetZ"; if (shimSetting[2] == 0.0) shimSetting[2] = readKeyFloat(keyStrSh2s, keyPos, csaLengthTrim); //higher order shims: older sequences char keyStrSh3[] = "sGRADSPEC.alShimCurrent[0]"; shimSetting[3] = readKeyFloat(keyStrSh3, keyPos, csaLengthTrim); char keyStrSh4[] = "sGRADSPEC.alShimCurrent[1]"; shimSetting[4] = readKeyFloat(keyStrSh4, keyPos, csaLengthTrim); char keyStrSh5[] = "sGRADSPEC.alShimCurrent[2]"; shimSetting[5] = readKeyFloat(keyStrSh5, keyPos, csaLengthTrim); char keyStrSh6[] = "sGRADSPEC.alShimCurrent[3]"; shimSetting[6] = readKeyFloat(keyStrSh6, keyPos, csaLengthTrim); char keyStrSh7[] = "sGRADSPEC.alShimCurrent[4]"; shimSetting[7] = readKeyFloat(keyStrSh7, keyPos, csaLengthTrim); } fclose (pFile); free (buffer); return; } // siemensCsaAscii() #endif //myReadAsciiCsa() #ifndef myDisableZLib //Uncomment next line to decode GE Protocol Data Block, for caveats see https://github.com/rordenlab/dcm2niix/issues/163 // #define myReadGeProtocolBlock #endif #ifdef myReadGeProtocolBlock int geProtocolBlock(const char * filename, int geOffset, int geLength, int isVerbose, int* sliceOrder, int* viewOrder) { *sliceOrder = 0; *viewOrder = 0; int ret = EXIT_FAILURE; if ((geOffset < 0) || (geLength < 20)) return ret; FILE * pFile = fopen ( filename, "rb" ); if(pFile==NULL) return ret; fseek (pFile , 0 , SEEK_END); long lSize = ftell (pFile); if (lSize < (geOffset+geLength)) { fclose (pFile); return ret; } fseek(pFile, geOffset, SEEK_SET); uint8_t * pCmp = (uint8_t*) malloc (geLength); //uint8_t -> mz_uint8 if(pCmp == NULL) return ret; size_t result = fread (pCmp,1,geLength,pFile); if ((int)result != geLength) return ret; int cmpSz = geLength; //http://www.forensicswiki.org/wiki/Gzip // always little endia! http://www.onicos.com/staff/iz/formats/gzip.html if (cmpSz < 20) return ret; if ((pCmp[0] != 31) || (pCmp[1] != 139) || (pCmp[2] != 8)) return ret; //check signature and deflate algorithm uint8_t flags = pCmp[3]; bool isFNAME = ((flags & 0x08) == 0x08); bool isFCOMMENT = ((flags & 0x10) == 0x10); uint32_t hdrSz = 10; if (isFNAME) {//skip null-terminated string FNAME for (hdrSz = hdrSz; 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; hdrSz++; } uint32_t unCmpSz = ((uint32_t)pCmp[cmpSz-4])+((uint32_t)pCmp[cmpSz-3] << 8)+((uint32_t)pCmp[cmpSz-2] << 16)+((uint32_t)pCmp[cmpSz-1] << 24); //printf(">> %d %d %zu %zu %zu\n", isFNAME, isFCOMMENT, cmpSz, unCmpSz, hdrSz); z_stream s; memset (&s, 0, sizeof (z_stream)); #ifdef myDisableMiniZ #define MZ_DEFAULT_WINDOW_BITS 15 // Window bits #endif inflateInit2(&s, -MZ_DEFAULT_WINDOW_BITS); uint8_t *pUnCmp = (uint8_t *)malloc((size_t)unCmpSz); s.avail_out = unCmpSz; s.next_in = pCmp+ hdrSz; s.avail_in = cmpSz-hdrSz-8; s.next_out = (uint8_t *) pUnCmp; #ifdef myDisableMiniZ ret = inflate(&s, Z_SYNC_FLUSH); if (ret != Z_STREAM_END) { free(pUnCmp); return EXIT_FAILURE; } #else ret = mz_inflate(&s, MZ_SYNC_FLUSH); if (ret != MZ_STREAM_END) { free(pUnCmp); return EXIT_FAILURE; } #endif //https://groups.google.com/forum/#!msg/comp.protocols.dicom/mxnCkv8A-i4/W_uc6SxLwHQJ // DISCOVERY MR750 / 24\MX\MR Software release:DV24.0_R01_1344.a) are now storing an XML file // if ((pUnCmp[0] == '<') && (pUnCmp[1] == '?')) printWarning("New XML-based GE Protocol Block is not yet supported: please report issue on dcm2niix Github page\n"); char keyStrSO[] = "SLICEORDER"; *sliceOrder = readKey(keyStrSO, (char *) pUnCmp, unCmpSz); char keyStrVO[] = "VIEWORDER"; //"MATRIXX"; *viewOrder = readKey(keyStrVO, (char *) pUnCmp, unCmpSz); if (isVerbose > 1) { printMessage("GE Protocol Block %s bytes %d compressed, %d uncompressed @ %d\n", filename, geLength, unCmpSz, geOffset); printMessage(" ViewOrder %d SliceOrder %d\n", *viewOrder, *sliceOrder); printMessage("%s\n", pUnCmp); } free(pUnCmp); return EXIT_SUCCESS; } #endif //myReadGeProtocolBlock() void json_Str(FILE *fp, const char *sLabel, char *sVal) { if (strlen(sVal) < 1) return; //fprintf(fp, sLabel, sVal ); //convert \ ' " characters to _ see https://github.com/rordenlab/dcm2niix/issues/131 for (size_t pos = 0; pos < strlen(sVal); pos ++) { if ((sVal[pos] == '\'') || (sVal[pos] == '"') || (sVal[pos] == '\\')) sVal[pos] = '_'; } fprintf(fp, sLabel, sVal ); /*char outname[PATH_MAX] = {""}; char appendChar[2] = {"\\"}; char passChar[2] = {"\\"}; for (int pos = 0; posmanufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) return; if (strlen(d->protocolName) > 0) return; int baseResolution, interpInt, partialFourier, echoSpacing, parallelReductionFactorInPlane; float pf = 1.0f; //partial fourier float phaseOversampling, delayTimeInTR, phaseResolution, txRefAmp, shimSetting[8]; char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge]; siemensCsaAscii(filename, d->CSA.SeriesHeader_offset, d->CSA.SeriesHeader_length, &delayTimeInTR, &phaseOversampling, &phaseResolution, &txRefAmp, shimSetting, &baseResolution, &interpInt, &partialFourier, &echoSpacing, ¶llelReductionFactorInPlane, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock); strcpy(d->protocolName, protocolName); } void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename) { //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.\n"); if (!opts.isCreateBIDS) return; char txtname[2048] = {""}; strcpy (txtname,pathoutname); strcat (txtname,".json"); FILE *fp = fopen(txtname, "w"); fprintf(fp, "{\n"); switch (d.modality) { case kMODALITY_CR: fprintf(fp, "\t\"Modality\": \"CR\",\n" ); break; case kMODALITY_CT: fprintf(fp, "\t\"Modality\": \"CT\",\n" ); break; case kMODALITY_MR: fprintf(fp, "\t\"Modality\": \"MR\",\n" ); break; case kMODALITY_PT: fprintf(fp, "\t\"Modality\": \"PT\",\n" ); break; case kMODALITY_US: fprintf(fp, "\t\"Modality\": \"US\",\n" ); break; }; 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_UIH: fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n" ); break; }; json_Str(fp, "\t\"ManufacturersModelName\": \"%s\",\n", d.manufacturersModelName); json_Str(fp, "\t\"InstitutionName\": \"%s\",\n", d.institutionName); json_Str(fp, "\t\"InstitutionalDepartmentName\": \"%s\",\n", d.institutionalDepartmentName); json_Str(fp, "\t\"InstitutionAddress\": \"%s\",\n", d.institutionAddress); json_Str(fp, "\t\"DeviceSerialNumber\": \"%s\",\n", d.deviceSerialNumber ); json_Str(fp, "\t\"StationName\": \"%s\",\n", d.stationName ); if (!opts.isAnonymizeBIDS) { json_Str(fp, "\t\"SeriesInstanceUID\": \"%s\",\n", d.seriesInstanceUID); json_Str(fp, "\t\"StudyInstanceUID\": \"%s\",\n", d.studyInstanceUID); json_Str(fp, "\t\"ReferringPhysicianName\": \"%s\",\n", d.referringPhysicianName); json_Str(fp, "\t\"StudyID\": \"%s\",\n", d.studyID); //Next lines directly reveal patient identity json_Str(fp, "\t\"PatientName\": \"%s\",\n", d.patientName); json_Str(fp, "\t\"PatientID\": \"%s\",\n", d.patientID); if (strlen(d.patientBirthDate) == 8) { //DICOM DA YYYYMMDD -> ISO 8601 "YYYY-MM-DD" int ayear,amonth,aday; sscanf(d.patientBirthDate, "%4d%2d%2d", &ayear, &amonth, &aday); fprintf(fp, "\t\"PatientBirthDate\": "); fprintf(fp, (ayear >= 0 && ayear <= 9999) ? "\"%4d" : "\"%+4d", ayear); fprintf(fp, "-%02d-%02d\",\n", amonth, aday); } if (d.patientSex != '?') fprintf(fp, "\t\"PatientSex\": \"%c\",\n", d.patientSex); json_Float(fp, "\t\"PatientWeight\": %g,\n", d.patientWeight); //d.patientBirthDate //convert from DICOM YYYYMMDD to JSON //d.patientAge //4-digit Age String: nnnD, nnnW, nnnM, nnnY; } json_Str(fp, "\t\"BodyPartExamined\": \"%s\",\n", d.bodyPartExamined); json_Str(fp, "\t\"PatientPosition\": \"%s\",\n", d.patientOrient); // 0018,5100 = PatientPosition in DICOM json_Str(fp, "\t\"ProcedureStepDescription\": \"%s\",\n", d.procedureStepDescription); json_Str(fp, "\t\"SoftwareVersions\": \"%s\",\n", d.softwareVersions); //json_Str(fp, "\t\"MRAcquisitionType\": \"%s\",\n", d.mrAcquisitionType); if (d.is2DAcq) fprintf(fp, "\t\"MRAcquisitionType\": \"2D\",\n"); if (d.is3DAcq) fprintf(fp, "\t\"MRAcquisitionType\": \"3D\",\n"); json_Str(fp, "\t\"SeriesDescription\": \"%s\",\n", d.seriesDescription); json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", d.protocolName); json_Str(fp, "\t\"ScanningSequence\": \"%s\",\n", d.scanningSequence); json_Str(fp, "\t\"SequenceVariant\": \"%s\",\n", d.sequenceVariant); json_Str(fp, "\t\"ScanOptions\": \"%s\",\n", d.scanOptions); json_Str(fp, "\t\"SequenceName\": \"%s\",\n", d.sequenceName); if (strlen(d.imageType) > 0) { fprintf(fp, "\t\"ImageType\": [\""); bool isSep = false; for (size_t i = 0; i < strlen(d.imageType); i++) { if (d.imageType[i] != '_') { if (isSep) fprintf(fp, "\", \""); isSep = false; fprintf(fp, "%c", d.imageType[i]); } else isSep = true; } fprintf(fp, "\"],\n"); } if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc) fprintf(fp, "\t\"RawImage\": false,\n"); if (d.seriesNum > 0) fprintf(fp, "\t\"SeriesNumber\": %ld,\n", d.seriesNum); //Chris Gorgolewski: BIDS standard specifies ISO8601 date-time format (Example: 2016-07-06T12:49:15.679688) //Lines below directly save DICOM values if (d.acquisitionTime > 0.0 && d.acquisitionDate > 0.0){ long acquisitionDate = d.acquisitionDate; double acquisitionTime = d.acquisitionTime; char acqDateTimeBuf[64]; //snprintf(acqDateTimeBuf, sizeof acqDateTimeBuf, "%+08ld%+08f", acquisitionDate, acquisitionTime); snprintf(acqDateTimeBuf, sizeof acqDateTimeBuf, "%+08ld%+013.5f", acquisitionDate, acquisitionTime); //CR 20170404 add zero pad so 1:23am appears as +012300.00000 not +12300.00000 //printMessage("acquisitionDateTime %s\n",acqDateTimeBuf); int ayear,amonth,aday,ahour,amin; double asec; int count = 0; sscanf(acqDateTimeBuf, "%5d%2d%2d%3d%2d%lf%n", &ayear, &amonth, &aday, &ahour, &amin, &asec, &count); //CR 20170404 %lf not %f for double precision //printf("-%02d-%02dT%02d:%02d:%02.6f\",\n", amonth, aday, ahour, amin, asec); if (count) { // ISO 8601 specifies a sign must exist for distant years. //report time of the day only format, https://www.cs.tut.fi/~jkorpela/iso8601.html fprintf(fp, "\t\"AcquisitionTime\": \"%02d:%02d:%02.6f\",\n",ahour, amin, asec); //report date and time together if (!opts.isAnonymizeBIDS) { fprintf(fp, "\t\"AcquisitionDateTime\": "); fprintf(fp, (ayear >= 0 && ayear <= 9999) ? "\"%4d" : "\"%+4d", ayear); fprintf(fp, "-%02d-%02dT%02d:%02d:%02.6f\",\n", amonth, aday, ahour, amin, asec); } } //if (count) } //if acquisitionTime and acquisitionDate recorded // if (d.acquisitionTime > 0.0) fprintf(fp, "\t\"AcquisitionTime\": %f,\n", d.acquisitionTime ); // if (d.acquisitionDate > 0.0) fprintf(fp, "\t\"AcquisitionDate\": %8.0f,\n", d.acquisitionDate ); if (d.acquNum > 0) fprintf(fp, "\t\"AcquisitionNumber\": %d,\n", d.acquNum); json_Str(fp, "\t\"ImageComments\": \"%s\",\n", d.imageComments); json_Str(fp, "\t\"ConversionComments\": \"%s\",\n", opts.imageComments); //if conditionals: the following values are required for DICOM MRI, but not available for CT json_Float(fp, "\t\"TriggerDelayTime\": %g,\n", d.triggerDelayTime ); if (d.RWVScale != 0) { fprintf(fp, "\t\"PhilipsRWVSlope\": %g,\n", d.RWVScale ); fprintf(fp, "\t\"PhilipsRWVIntercept\": %g,\n", d.RWVIntercept ); } if ((d.intenScalePhilips != 0) && (d.manufacturer == kMANUFACTURER_PHILIPS)) { //for details, see PhilipsPrecise() fprintf(fp, "\t\"PhilipsRescaleSlope\": %g,\n", d.intenScale ); fprintf(fp, "\t\"PhilipsRescaleIntercept\": %g,\n", d.intenIntercept ); fprintf(fp, "\t\"PhilipsScaleSlope\": %g,\n", d.intenScalePhilips ); fprintf(fp, "\t\"UsePhilipsFloatNotDisplayScaling\": %d,\n", opts.isPhilipsFloatNotDisplayScaling); } //PET ISOTOPE MODULE ATTRIBUTES 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); //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.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 ); 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; int viewOrderGE = -1; int sliceOrderGE = -1; //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" ); } #ifdef myReadGeProtocolBlock if ((d.manufacturer == kMANUFACTURER_GE) && (d.protocolBlockStartGE> 0) && (d.protocolBlockLengthGE > 19)) { 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); if (ok != EXIT_SUCCESS) printWarning("Unable to decode GE protocol block\n"); printMessage(" ViewOrder %d SliceOrder %d\n", viewOrderGE, sliceOrderGE); } //read protocolBlockGE #endif #ifdef myReadAsciiCsa if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.SeriesHeader_offset > 0) && (d.CSA.SeriesHeader_length > 0)) { int baseResolution, interpInt, partialFourier, echoSpacing, parallelReductionFactorInPlane; float pf = 1.0f; //partial fourier float delayTimeInTR, phaseResolution, txRefAmp, shimSetting[8]; char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge]; siemensCsaAscii(filename, d.CSA.SeriesHeader_offset, d.CSA.SeriesHeader_length, &delayTimeInTR, &phaseOversampling, &phaseResolution, &txRefAmp, shimSetting, &baseResolution, &interpInt, &partialFourier, &echoSpacing, ¶llelReductionFactorInPlane, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock); if (partialFourier > 0) { //https://github.com/ismrmrd/siemens_to_ismrmrd/blob/master/parameter_maps/IsmrmrdParameterMap_Siemens_EPI_FLASHREF.xsl if (partialFourier == 1) pf = 0.5; // 4/8 if (partialFourier == 2) pf = 0.625; // 5/8 if (partialFourier == 4) pf = 0.75; if (partialFourier == 8) pf = 0.875; fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); } if (interpInt > 0) { interp = true; fprintf(fp, "\t\"Interpolation2D\": %d,\n", interp); } if (baseResolution > 0) fprintf(fp, "\t\"BaseResolution\": %d,\n", baseResolution ); if (shimSetting[0] != 0.0) { fprintf(fp, "\t\"ShimSetting\": [\n"); for (int i = 0; i < 8; i++) { if (i != 0) fprintf(fp, ",\n"); fprintf(fp, "\t\t%g", shimSetting[i]); } fprintf(fp, "\t],\n"); } //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 json_Float(fp, "\t\"TxRefAmp\": %g,\n", txRefAmp); json_Float(fp, "\t\"PhaseResolution\": %g,\n", phaseResolution); json_Float(fp, "\t\"PhaseOversampling\": %g,\n", phaseOversampling); //usec -> sec json_Float(fp, "\t\"VendorReportedEchoSpacing\": %g,\n", 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); json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", coilElements); if (strcmp(coilElements,d.coilName) != 0) json_Str(fp, "\t\"CoilString\": \"%s\",\n", d.coilName); strcpy(d.coilName, ""); json_Str(fp, "\t\"PulseSequenceDetails\": \"%s\",\n", pulseSequenceDetails); json_Str(fp, "\t\"FmriExternalInfo\": \"%s\",\n", fmriExternalInfo); json_Str(fp, "\t\"WipMemBlock\": \"%s\",\n", wipMemBlock); if (strlen(d.protocolName) < 1) //insert protocol name if it exists in CSA but not DICOM header: https://github.com/nipy/heudiconv/issues/80 json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", protocolName); json_Str(fp, "\t\"ConsistencyInfo\": \"%s\",\n", consistencyInfo); if (parallelReductionFactorInPlane > 0) {//AccelFactorPE -> phase encoding if (d.accelFactPE < 1.0) { //value not found in DICOM header, but WAS found in CSA ascii d.accelFactPE = parallelReductionFactorInPlane; //value found in ASCII but not in DICOM (0051,1011) //fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); } if (parallelReductionFactorInPlane != (int)(d.accelFactPE)) printWarning("ParallelReductionFactorInPlane reported in DICOM [0051,1011] (%d) does not match CSA series value %d\n", (int)(d.accelFactPE), parallelReductionFactorInPlane); } } else { //e.g. Siemens Vida does not have CSA header, but has many attributes json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", d.coilElements); if (strcmp(d.coilElements,d.coilName) != 0) json_Str(fp, "\t\"CoilString\": \"%s\",\n", d.coilName); if ((!d.is3DAcq) && (d.phaseEncodingLines > d.echoTrainLength) && (d.echoTrainLength > 1)) { //ETL is > 1, as some GE files list 1, as an example see series mr_0005 in dcm_qa_nih float pf = (float)d.phaseEncodingLines; if (d.accelFactPE > 1) pf = (float)pf / (float)d.accelFactPE; //estimate: not sure if we round up or down pf = (float)d.echoTrainLength / (float)pf; if (pf < 1.0) //e.g. if difference between lines and echo length not all explained by iPAT (SENSE/GRAPPA) fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); } //compute partial Fourier: not reported in XA10, so infer //printf("PhaseLines=%d EchoTrainLength=%d SENSE=%g\n", d.phaseEncodingLines, d.echoTrainLength, d.accelFactPE); //n.b. we can not distinguish pF from SENSE/GRAPPA for UIH } #endif if (d.CSA.multiBandFactor > 1) //AccelFactorSlice fprintf(fp, "\t\"MultibandAccelerationFactor\": %d,\n", d.CSA.multiBandFactor); json_Float(fp, "\t\"PercentPhaseFOV\": %g,\n", d.phaseFieldofView); 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) fprintf(fp, "\t\"PhaseEncodingSteps\": %d,\n", d.phaseEncodingSteps ); if (d.phaseEncodingLines > 0) 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[2] == h->dim[2]) //phase encoding does not matter reconMatrixPE = h->dim[2]; else if (d.phaseEncodingRC =='R') reconMatrixPE = h->dim[2]; else if (d.phaseEncodingRC =='C') reconMatrixPE = h->dim[1]; } if (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); //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; if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); if (d.effectiveEchoSpacingGE > 0.0) effectiveEchoSpacing = d.effectiveEchoSpacingGE / 1000000.0; json_Float(fp, "\t\"EffectiveEchoSpacing\": %g,\n", effectiveEchoSpacing); // Calculate true echo spacing (should match what Siemens reports on the console) // i.e., should match "echoSpacing" extracted from the ASCII CSA header, when that exists double trueESfactor = 1.0; if (d.accelFactPE > 1.0) trueESfactor /= d.accelFactPE; if (phaseOversampling > 0.0) trueESfactor *= (1.0 + phaseOversampling); float derivedEchoSpacing = 0.0; derivedEchoSpacing = bandwidthPerPixelPhaseEncode * trueESfactor * reconMatrixPE; if (derivedEchoSpacing != 0) derivedEchoSpacing = 1/derivedEchoSpacing; json_Float(fp, "\t\"DerivedVendorReportedEchoSpacing\": %g,\n", derivedEchoSpacing); //TotalReadOutTime: Really should be called "EffectiveReadOutTime", by analogy with "EffectiveEchoSpacing". // But BIDS spec calls it "TotalReadOutTime". // So, we DO NOT USE EchoTrainLength, because not trying to compute the actual (physical) readout time. // Rather, the point of computing "EffectiveEchoSpacing" properly is so that this // "Total(Effective)ReadOutTime" can be computed straightforwardly as the product of the // EffectiveEchoSpacing and the size of the *reconstructed* matrix in the PE direction. // see https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide#A--datain // FSL definition is start of first line until start of last line. // Other than the use of (n-1), the value is basically just 1.0/bandwidthPerPixelPhaseEncode. // https://github.com/rordenlab/dcm2niix/issues/130 if ((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 json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", d.acquisitionDuration / 1000.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 (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!d.is3DAcq) && (phPos < 0)) { //when phase encoding axis is known but we do not know phase encoding polarity // https://github.com/rordenlab/dcm2niix/issues/163 // This will typically correspond with InPlanePhaseEncodingDirectionDICOM if (d.phaseEncodingRC == 'C') //Values should be "R"ow, "C"olumn or "?"Unknown fprintf(fp, "\t\"PhaseEncodingAxis\": \"j\",\n"); else if (d.phaseEncodingRC == 'R') fprintf(fp, "\t\"PhaseEncodingAxis\": \"i\",\n"); } if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!d.is3DAcq) && (phPos >= 0)) { if (d.phaseEncodingRC == 'C') //Values should be "R"ow, "C"olumn or "?"Unknown fprintf(fp, "\t\"PhaseEncodingDirection\": \"j"); else if (d.phaseEncodingRC == 'R') fprintf(fp, "\t\"PhaseEncodingDirection\": \"i"); else fprintf(fp, "\t\"PhaseEncodingDirection\": \"?"); //phaseEncodingDirectionPositive has one of three values: UNKNOWN (-1), NEGATIVE (0), POSITIVE (1) //However, DICOM and NIfTI are reversed in the j (ROW) direction //Equivalent to dicm2nii's "if flp(iPhase), phPos = ~phPos; end" //for samples see https://github.com/rordenlab/dcm2niix/issues/125 if (phPos < 0) fprintf(fp, "?"); //unknown else if ((phPos == 0) && (d.phaseEncodingRC != 'C')) fprintf(fp, "-"); else if ((d.phaseEncodingRC == 'C') && (phPos == 1) && (opts.isFlipY)) fprintf(fp, "-"); else if ((d.phaseEncodingRC == 'C') && (phPos == 0) && (!opts.isFlipY)) fprintf(fp, "-"); fprintf(fp, "\",\n"); } //only save PhaseEncodingDirection if BOTH direction and POLARITY are known //Slice Timing UIH or GE >>>> //in theory, we should also report XA10 slice times here, but see series 24 of https://github.com/rordenlab/dcm2niix/issues/236 if (((d.manufacturer == kMANUFACTURER_UIH) || (d.manufacturer == kMANUFACTURER_GE) || (d.isXA10A)) && (d.CSA.sliceTiming[0] >= 0.0)) { fprintf(fp, "\t\"SliceTiming\": [\n"); for (int i = 0; i < h->dim[3]; i++) { if (i != 0) fprintf(fp, ",\n"); if (d.CSA.protocolSliceNumber1 < 0) fprintf(fp, "\t\t%g", d.CSA.sliceTiming[(h->dim[3]-1) - i]); else fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i]); } fprintf(fp, "\t],\n"); } //Slice Timing Siemens if ((!d.isXA10A) && (d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.sliceTiming[0] >= 0.0)) { fprintf(fp, "\t\"SliceTiming\": [\n"); if (d.CSA.protocolSliceNumber1 > 1) { //https://github.com/rordenlab/dcm2niix/issues/40 //equivalent to dicm2nii "s.SliceTiming = s.SliceTiming(end:-1:1);" for (int i = (h->dim[3]-1); i >= 0; i--) { if (d.CSA.sliceTiming[i] < 0.0) break; if (i != (h->dim[3]-1)) fprintf(fp, ",\n"); fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0 ); } } else { for (int i = 0; i < h->dim[3]; i++) { if (i != 0) fprintf(fp, ",\n"); fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0 ); } } fprintf(fp, "\t],\n"); } //DICOM orientation and phase encoding: useful for 3D undistortion. Original DICOM values: DICOM not NIfTI space, ignores if 3D image re-oriented 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" ); // Finish up with info on the conversion tool fprintf(fp, "\t\"ConversionSoftware\": \"dcm2niix\",\n"); fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMvers ); //fprintf(fp, "\t\"DicomConversion\": [\"dcm2niix\", \"%s\"]\n", kDCMvers ); fprintf(fp, "}\n"); fclose(fp); }// nii_SaveBIDS() 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)) ) ); } unsigned char * removeADC(struct nifti_1_header *hdr, unsigned char *inImg, int numADC) { //for speed we just clip the number of volumes, the realloc routine would be nice // we do not want to copy input to a new smaller array since 4D DTI datasets can be huge // and that would require almost twice as much RAM if (numADC < 1) return inImg; hdr->dim[4] = hdr->dim[4] - numADC; if (hdr->dim[4] < 2) hdr->dim[0] = 3; //e.g. 4D 2-volume DWI+ADC becomes 3D DWI if ADC is removed return inImg; } //removeADC() //#define naive_reorder_vols //for simple, fast re-ordering that consumes a lot of RAM #ifdef naive_reorder_vols unsigned char * reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int * volOrderIndex) { //reorder volumes to place ADC at end and (optionally) B=0 at start // volOrderIndex[0] reports location of desired first volume // naive solution creates an output buffer that doubles RAM usage (2 *numVol) int numVol = hdr->dim[4]; int numVolBytes = hdr->dim[1]*hdr->dim[2]*hdr->dim[3]*(hdr->bitpix/8); if ((!volOrderIndex) || (numVol < 1) || (numVolBytes < 1)) return inImg; unsigned char *outImg = (unsigned char *)malloc(numVolBytes * numVol); int outPos = 0; for (int i = 0; i < numVol; i++) { memcpy(&outImg[outPos], &inImg[volOrderIndex[i] * numVolBytes], numVolBytes); // dest, src, bytes outPos += numVolBytes; } //for each volume free(volOrderIndex); free(inImg); return outImg; } //reorderVolumes() #else // naive_reorder_vols unsigned char * reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int * volOrderIndex) { //reorder volumes to place ADC at end and (optionally) B=0 at start // volOrderIndex[0] reports location of desired first volume // complicated by fact that 4D DTI data is often huge // simple solutions would create an output buffer that would double RAM usage (2 *numVol) // here we bubble-sort volumes in place to use numVols+1 memory int numVol = hdr->dim[4]; int numVolBytes = hdr->dim[1]*hdr->dim[2]*hdr->dim[3]*(hdr->bitpix/8); int * inPos = (int *) malloc(numVol * sizeof(int)); for (int i = 0; i < numVol; i++) inPos[i] = i; unsigned char *tempVol = (unsigned char *)malloc(numVolBytes); int outPos = 0; for (int o = 0; o < numVol; o++) { int i = inPos[volOrderIndex[o]]; //input volume if (i == o) continue; //volume in correct order memcpy(&tempVol[0], &inImg[o * numVolBytes], numVolBytes); //make temp memcpy(&inImg[o * numVolBytes], &inImg[i * numVolBytes], numVolBytes); //copy volume to desire location dest, src, bytes memcpy(&inImg[i * numVolBytes], &tempVol[0], numVolBytes); //copy unsorted volume inPos[o] = i; outPos += numVolBytes; } //for each volume free(inPos); free(volOrderIndex); free(tempVol); return inImg; } //reorderVolumes() #endif // naive_reorder_vols float * bvals; //global variable for cmp_bvals int cmp_bvals(const void *a, const void *b){ int ia = *(int *)a; int ib = *(int *)b; //return bvals[ia] > bvals[ib] ? -1 : bvals[ia] < bvals[ib]; return bvals[ia] < bvals[ib] ? -1 : bvals[ia] > bvals[ib]; } // cmp_bvals() bool isAllZeroFloat(float v1, float v2, float v3) { if (!isSameFloatGE(v1, 0.0)) return false; if (!isSameFloatGE(v2, 0.0)) return false; if (!isSameFloatGE(v3, 0.0)) return false; return true; } int * nii_SaveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmList[], struct TDCMopts opts, int sliceDir, struct TDTI4D *dti4D, int * numADC) { //reports non-zero if any volumes should be excluded (e.g. philip stores an ADC maps) //to do: works with 3D mosaics and 4D files, must remove repeated volumes for 2D sequences.... *numADC = 0; if (opts.isOnlyBIDS) return NULL; uint64_t indx0 = dcmSort[0].indx; //first volume int numDti = dcmList[indx0].CSA.numDti; 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; //do not save files without variability 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; } for (int i = 0; i < numDti; i++) printMessage("bxyz %g %g %g %g\n",vx[i].V[0],vx[i].V[1],vx[i].V[2],vx[i].V[3]); //Stutters XINAPSE7 seem to save B=0 as B=2000, but these are not derived? https://github.com/rordenlab/dcm2niix/issues/182 bool bZeroBvec = false; for (int i = 0; i < numDti; i++) {//check if all bvalues match first volume if (isAllZeroFloat(vx[i].V[1], vx[i].V[2], vx[i].V[3])) { vx[i].V[0] = 0; //printWarning("volume %d might be B=0\n", i); bZeroBvec = true; } } if (bZeroBvec) printWarning("Assuming volumes without gradients are actually B=0\n"); else { printWarning("No bvec/bval files created. Only one B-value reported for all volumes: %g\n",vx[0].V[0]); free(vx); return NULL; } } //report values: //for (int i = 1; i < numDti; i++) //check if all bvalues match first volume // printMessage("%d bval= %g bvec= %g %g %g\n",i, vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); int minB0idx = 0; float minB0 = vx[0].V[0]; for (int i = 0; i < numDti; i++) if (vx[i].V[0] < minB0) { minB0 = vx[i].V[0]; minB0idx = i; } float maxB0 = vx[0].V[0]; for (int i = 0; i < numDti; i++) if (vx[i].V[0] > maxB0) maxB0 = vx[i].V[0]; //for CMRR sequences unweighted volumes are not actually B=0 but they have B near zero if (minB0 > 50) printWarning("This diffusion series does not have a B0 (reference) volume\n"); if ((!opts.isSortDTIbyBVal) && (minB0idx > 0)) printMessage("Note: B0 not the first volume in the series (FSL eddy reference volume is %d)\n", minB0idx); float kADCval = maxB0 + 1; //mark as unusual *numADC = 0; bvals = (float *) malloc(numDti * sizeof(float)); int numGEwarn = 0; bool isGEADC = (dcmList[indx0].numberOfDiffusionDirectionGE == 0); for (int i = 0; i < numDti; i++) { bvals[i] = vx[i].V[0]; //printMessage("---bxyz %g %g %g %g\n",vx[i].V[0],vx[i].V[1],vx[i].V[2],vx[i].V[3]); //Philips includes derived isotropic images //if (((dcmList[indx0].manufacturer == kMANUFACTURER_GE) || (dcmList[indx0].manufacturer == kMANUFACTURER_PHILIPS)) && (isADCnotDTI(vx[i]))) { if (((dcmList[indx0].manufacturer == kMANUFACTURER_GE)) && (isADCnotDTI(vx[i]))) { numGEwarn += 1; if (isGEADC) { //e.g. GE Trace where bval=900, bvec=0,0,0 *numADC = *numADC + 1; //printWarning("GE ADC volume %d\n", i+1); bvals[i] = kADCval; } else vx[i].V[0] = 0; //e.g. GE raw B=0 where bval=900, bvec=0,0,0 } //see issue 245 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)) { //all isotropic/ADC images - no valid bvecs *numADC = 0; free(bvals); free(vx); return NULL; } if (*numADC > 0) { // DWIs (i.e. short diffusion scans with too few directions to // calculate tensors...they typically acquire b=0 + 3 b > 0 so // the isotropic trace or MD can be calculated) often come as // b=0 and trace pairs, with the b=0 and trace in either order, // and often as "ORIGINAL", even though the trace is not. // The bval file is needed for downstream processing to know // * which is the b=0 and which is the trace, and // * what b is for the trace, // so dcm2niix should *always* write the bval and bvec files, // AND include the b for the trace for DWIs. // One hackish way to accomplish that is to set *numADC = 0 // when *numADC == 1 && numDti == 2. // - Rob Reid, 2017-11-29. if ((*numADC == 1) && ((numDti - *numADC) < 2)){ *numADC = 0; printMessage("Note: this appears to be a b=0+trace DWI; ADC/trace removal has been disabled.\n"); } else{ if ((numDti - *numADC) < 2) { if (!dcmList[indx0].isDerived) //no need to warn if images are derived Trace/ND pair printWarning("No bvec/bval files created: only single value after ADC excluded\n"); *numADC = 0; free(bvals); free(vx); return NULL; } printMessage("Note: %d volumes appear to be ADC or trace images that will be removed to allow processing\n", *numADC); } } //sort ALL including ADC int * volOrderIndex = (int *) malloc(numDti * sizeof(int)); for (int i = 0; i < numDti; i++) volOrderIndex[i] = i; if (opts.isSortDTIbyBVal) qsort(volOrderIndex, numDti, sizeof(*volOrderIndex), cmp_bvals); else if (*numADC > 0) { int o = 0; for (int i = 0; i < numDti; i++) { if (bvals[i] < kADCval) { volOrderIndex[o] = i; o++; } //if not ADC } //for each volume } //if sort else if has ADC free(bvals); //save VX as sorted TDTI * vxOrig = (TDTI *)malloc(numDti * sizeof(TDTI)); for (int i = 0; i < numDti; i++) vxOrig[i] = vx[i]; //remove ADC numDti = numDti - *numADC; free(vx); vx = (TDTI *)malloc(numDti * sizeof(TDTI)); for (int i = 0; i < numDti; i++) vx[i] = vxOrig[volOrderIndex[i]]; free(vxOrig); //if no ADC or sequential, the is no need to re-order volumes bool isSequential = true; for (int i = 1; i < (numDti + *numADC); i++) if (volOrderIndex[i] <= volOrderIndex[i-1]) isSequential = false; if (isSequential) { free(volOrderIndex); volOrderIndex = NULL; } if (!isSequential) printMessage("DTI volumes re-ordered by ascending b-value\n"); dcmList[indx0].CSA.numDti = numDti; //warning structure not changed outside scope! geCorrectBvecs(&dcmList[indx0],sliceDir, vx); siemensPhilipsCorrectBvecs(&dcmList[indx0],sliceDir, vx); 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 ImageList *images = (ImageList *) opts.imageList; images->addDeferredAttribute("bValues", bValues); images->addDeferredAttribute("bVectors", bVectors, numDti, 3); #else 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); strcpy(txtname,pathoutname); strcat (txtname,".bvec"); //printMessage("Saving DTI %s\n",txtname); fp = fopen(txtname, "w"); if (fp == NULL) { free(vx); return volOrderIndex; } for (int v = 1; v < 4; v++) { for (int i = 0; i < (numDti-1); i++) { if (opts.isCreateBIDS) { fprintf(fp, "%g ", vx[i].V[v]); } else { fprintf(fp, "%g\t", vx[i].V[v]); } } fprintf(fp, "%g\n", vx[numDti-1].V[v]); } fclose(fp); #endif free(vx); return volOrderIndex; }// nii_SaveDTI() float sqr(float v){ return v*v; }// sqr() float intersliceDistance(struct TDICOMdata d1, struct TDICOMdata d2) { //some MRI scans have gaps between slices, some CT have overlapping slices. Comparing adjacent slices provides measure for dx between slices if ( isNanPosition(d1) || isNanPosition(d2)) return d1.xyzMM[3]; float tilt = 1.0; //printMessage("0020,0032 %g %g %g -> %g %g %g\n",d1.patientPosition[1],d1.patientPosition[2],d1.patientPosition[3],d2.patientPosition[1],d2.patientPosition[2],d2.patientPosition[3]); if (d1.gantryTilt != 0) tilt = (float) cos(d1.gantryTilt * M_PI/180); //for CT scans with gantry tilt, we need to compute distance between slices, not distance along bed return tilt * sqrt( sqr(d1.patientPosition[1]-d2.patientPosition[1])+ sqr(d1.patientPosition[2]-d2.patientPosition[2])+ sqr(d1.patientPosition[3]-d2.patientPosition[3])); } //intersliceDistance() 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; //#ifdef _MSC_VER TDCMsort * dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); //#else // struct TDCMsort dcmSortIn[nConvert]; //#endif 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++; } //#ifdef _MSC_VER free(dcmSortIn); //#endif } //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; bool iVaries = false; 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 (fabs (dcmList[indx].intenScale - iScale) > FLT_EPSILON) iVaries = true; if (fabs (dcmList[indx].intenIntercept- iInter) > FLT_EPSILON) iVaries = true; } return iVaries; } //intensityScaleVaries() /*unsigned char * nii_bgr2rgb(unsigned char* bImg, struct nifti_1_header *hdr) { //DICOM planarappears to be BBB..B,GGG..G,RRR..R, NIfTI RGB saved in planes RRR..RGGG..GBBBB..B // see http://www.barre.nom.fr/medical/samples/index.html US-RGB-8-epicard if (hdr->datatype != DT_RGB24) return bImg; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int sliceBytes24 = hdr->dim[1]*hdr->dim[2] * hdr->bitpix/8; int sliceBytes8 = hdr->dim[1]*hdr->dim[2]; //Byte bImg[ bSz ]; //[img getBytes:&bImg length:bSz]; unsigned char slice24[sliceBytes24]; int sliceOffsetR = 0; for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24); memcpy( &bImg[sliceOffsetR], &slice24[sliceBytes8*2], sliceBytes8); sliceOffsetR += sliceBytes8; memcpy( &bImg[sliceOffsetR], &slice24[sliceBytes8], sliceBytes8); sliceOffsetR += sliceBytes8; memcpy( &bImg[sliceOffsetR], &slice24[0], sliceBytes8); sliceOffsetR += sliceBytes8; } //for each slice return bImg; } */ 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; return false; } //niiExists() #ifndef W_OK #define W_OK 2 /* write mode check */ #endif 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) { if (getcwd(pth, sizeof(pth)) != NULL) { #ifdef USE_CWD_IF_OUTDIR_NO_WRITE //optional: fall back to current working directory w =access(pth,W_OK); if (w != 0) { printError("You do not have write permissions for the directory %s\n",opts.outdir); return EXIT_FAILURE; } printWarning("%s write permission denied. Saving to working directory %s \n", opts.outdir, pth); #else printError("You do not have write permissions for the directory %s\n",opts.outdir); return EXIT_FAILURE; #endif } } } char inname[PATH_MAX] = {""};//{"test%t_%av"}; //% a = acquisition, %n patient name, %t time strcpy(inname, opts.filename); char outname[PATH_MAX] = {""}; char newstr[256]; if (strlen(inname) < 1) { strcpy(inname, "T%t_N%n_S%s"); } size_t start = 0; size_t pos = 0; bool isCoilReported = false; bool isEchoReported = false; bool isSeriesReported = 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 == '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_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 == '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') { #ifdef mySegmentByAcq sprintf(newstr, "%d", dcm.acquNum); strcat (outname,newstr); #else printWarning("Ignoring '%%u' in output filename (recompile to segment by acquisition)\n"); #endif } if (f == 'V') { if (dcm.manufacturer == kMANUFACTURER_BRUKER) strcat (outname,"Bruker"); else if (dcm.manufacturer == kMANUFACTURER_GE) strcat (outname,"GE"); else if (dcm.manufacturer == kMANUFACTURER_PHILIPS) strcat (outname,"Philips"); else if (dcm.manufacturer == kMANUFACTURER_SIEMENS) strcat (outname,"Siemens"); else if (dcm.manufacturer == kMANUFACTURER_TOSHIBA) strcat (outname,"Toshiba"); else if (dcm.manufacturer == kMANUFACTURER_UIH) strcat (outname,"UIH"); else strcat (outname,"NA"); } if (f == 'X') strcat (outname,dcm.studyID); if (f == 'Z') strcat (outname,dcm.sequenceName); if ((f >= '0') && (f <= '9')) { if ((pos start) { //append any trailing characters strncpy(&newstr[0], &inname[0] + start, pos - start); newstr[pos - start] = '\0'; strcat (outname,newstr); } if ((!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 ((!isEchoReported) && (dcm.isMultiEcho) && (dcm.echoNum >= 1)) { //multiple echoes saved as same series #else if ((!isEchoReported) && (dcm.isMultiEcho)) { //multiple echoes saved as same series #endif 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 ((!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); } /*if (dcm.maxGradDynVol > 0) { //Philips segmented sprintf(newstr, "_v%04d", dcm.gradDynVol+1); //+1 as indexed from zero strcat (outname,newstr); }*/ if (dcm.isHasImaginary) { strcat (outname,"_imaginary"); //has phase map } if (dcm.isHasReal) { strcat (outname,"_real"); //has phase map } if (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 (dcm.triggerDelayTime >= 1) { sprintf(newstr, "_t%d", (int)roundf(dcm.triggerDelayTime)); strcat (outname,newstr); } 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] == '*') || (outname[pos] == '|') || (outname[pos] == '?')) outname[pos] = '_'; #if defined(_WIN64) || defined(_WIN32) const char kForeignPathSeparator ='/'; #else const char kForeignPathSeparator ='\\'; #endif for (int pos = 0; pos= 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.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.isGz) strcat(niiFilename,".nii.gz'"); else strcat(niiFilename,".nii'"); }// nii_createDummyFilename() #ifndef myDisableZLib #ifndef MiniZ unsigned long mz_compressBound(unsigned long source_len) { return compressBound(source_len); } unsigned long mz_crc32(unsigned long crc, const unsigned char *ptr, size_t buf_len) { return crc32(crc, ptr, (uInt) buf_len); } #endif #ifndef MZ_UBER_COMPRESSION //defined in miniz, not defined in zlib #define MZ_UBER_COMPRESSION 9 #endif #ifndef MZ_DEFAULT_LEVEL #define MZ_DEFAULT_LEVEL 6 #endif void writeNiiGz (char * baseName, struct nifti_1_header hdr, unsigned char* src_buffer, unsigned long src_len, int gzLevel) { //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); strcat (fname,".nii.gz"); unsigned long hdrPadBytes = sizeof(hdr) + 4; //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; } //add header unsigned char *pHdr = (unsigned char *)malloc(hdrPadBytes); pHdr[hdrPadBytes-1] = 0; pHdr[hdrPadBytes-2] = 0; pHdr[hdrPadBytes-3] = 0; pHdr[hdrPadBytes-4] = 0; memcpy(pHdr,&hdr, sizeof(hdr)); strm.avail_in = (unsigned int)hdrPadBytes; // size of input strm.next_in = (uint8_t *)pHdr; // input header -- TPX strm.next_in = (Bytef *)pHdr; uint32_t deflate(&strm, Z_NO_FLUSH); //add image strm.avail_in = (unsigned int)src_len; // size of input strm.next_in = (uint8_t *)src_buffer; // input image -- TPX strm.next_in = (Bytef *)src_buffer; deflate(&strm, Z_FINISH); //Z_NO_FLUSH; //finish up deflateEnd(&strm); unsigned long file_crc32 = mz_crc32(0L, Z_NULL, 0); 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); 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) { hdr.vox_offset = 352; // Extract the basename from the full file path // R always uses '/' as the path separator, so this should work on all platforms char *start = niiFilename + strlen(niiFilename); while (*start != '/') 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) { ImageList *images = (ImageList *) opts.imageList; 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; } if (strlen(data.manufacturersModelName) > 0) images->addAttribute("scannerModelName", data.manufacturersModelName); if (strlen(data.imageType) > 0) images->addAttribute("imageType", data.imageType); 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.CSA.bandwidthPerPixelPhaseEncode > 0.0) && (header.dim[2] > 0) && (header.dim[1] > 0)) { if (data.phaseEncodingRC =='C') images->addAttribute("dwellTime", 1.0/data.CSA.bandwidthPerPixelPhaseEncode/header.dim[2]); else if (data.phaseEncodingRC == 'R') images->addAttribute("dwellTime", 1.0/data.CSA.bandwidthPerPixelPhaseEncode/header.dim[1]); } if (data.phaseEncodingRC == 'C') images->addAttribute("phaseEncodingDirection", "j"); else if (data.phaseEncodingRC == 'R') images->addAttribute("phaseEncodingDirection", "i"); if (data.CSA.phaseEncodingDirectionPositive != -1) images->addAttribute("phaseEncodingSign", data.CSA.phaseEncodingDirectionPositive == 0 ? -1 : 1); } #else int nii_saveNII(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts) { if (opts.isOnlyBIDS) return EXIT_SUCCESS; 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 writeNiiGz (niiFilename, hdr, im, imgsz, opts.gzLevel); return EXIT_SUCCESS; } #endif #endif char fname[2048] = {""}; strcpy (fname,niiFilename); strcat (fname,".nii"); FILE *fp = fopen(fname, "wb"); if (!fp) return EXIT_FAILURE; 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.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 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, "\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' #if defined(_WIN64) || defined(_WIN32) //using CreateProcess instead of system to run in background (avoids screen flicker) DWORD exitCode; PROCESS_INFORMATION ProcessInfo = {0}; STARTUPINFO startupInfo= {0}; startupInfo.cb = sizeof(startupInfo); //StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field if(CreateProcess(NULL, command, NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,NULL, NULL,&startupInfo,&ProcessInfo)) { //printMessage("compression --- %s\n",command); WaitForSingleObject(ProcessInfo.hProcess,INFINITE); CloseHandle(ProcessInfo.hThread); CloseHandle(ProcessInfo.hProcess); } else printMessage("compression failed %s\n",command); #else //if win else linux int ret = system(command); if (ret == -1) printWarning("Failed to execute: %s\n",command); #endif //else linux printMessage("compress: %s\n",command); } return EXIT_SUCCESS; }// nii_saveNII() #endif int nii_saveNII3D(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts) { //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) == 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); }*/ void nii_storeIntegerScaleFactor(int scale, struct nifti_1_header *hdr) { //appends NIfTI header description field with " isN" where N is integer scaling char newstr[256]; sprintf(newstr, " is%d", scale); if ((strlen(newstr)+strlen(hdr->descrip)) < 80) strcat (hdr->descrip,newstr); } void nii_scale16bitSigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { //lossless scaling of INT16 data: e.g. input with range -100...3200 and scl_slope=1 // will be stored as -1000...32000 with scl_slope 0.1 if (hdr->datatype != DT_INT16) return; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int nVox = hdr->dim[1]*hdr->dim[2]* dim3to7; if (nVox < 1) return; int16_t * img16 = (int16_t*) img; int16_t max16 = img16[0]; int16_t min16 = max16; for (int i=0; i < nVox; i++) { if (img16[i] < min16) min16 = img16[i]; if (img16[i] > max16) max16 = img16[i]; } int kMx = 32000; //actually 32767 - maybe a bit of padding for interpolation ringing int scale = kMx / (int)max16; if (abs(min16) > max16) scale = kMx / (int)abs(min16); if (scale < 2) { if (isVerbose) printMessage("Sufficient 16-bit range: raw %d..%d\n", min16, max16); return; //already uses dynamic range } hdr->scl_slope = hdr->scl_slope/ scale; for (int i=0; i < nVox; i++) img16[i] = img16[i] * scale; printMessage("Maximizing 16-bit range: raw %d..%d is%d\n", min16, max16, scale); nii_storeIntegerScaleFactor(scale, hdr); } void nii_scale16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose){ //lossless scaling of UINT16 data: e.g. input with range 0...3200 and scl_slope=1 // will be stored as 0...64000 with scl_slope 0.05 if (hdr->datatype != DT_UINT16) return; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int nVox = hdr->dim[1]*hdr->dim[2]* dim3to7; if (nVox < 1) return; uint16_t * img16 = (uint16_t*) img; uint16_t max16 = img16[0]; for (int i=0; i < nVox; i++) if (img16[i] > max16) max16 = img16[i]; int kMx = 64000; //actually 65535 - maybe a bit of padding for interpolation ringing int scale = kMx / (int)max16; if (scale < 2) { if (isVerbose > 0) printMessage("Sufficient unsigned 16-bit range: raw max %d\n", max16); return; //already uses dynamic range } hdr->scl_slope = hdr->scl_slope/ scale; for (int i=0; i < nVox; i++) img16[i] = img16[i] * scale; printMessage("Maximizing 16-bit range: raw max %d is%d\n", max16, scale); nii_storeIntegerScaleFactor(scale, hdr); } #define UINT16_TO_INT16_IF_LOSSLESS #ifdef UINT16_TO_INT16_IF_LOSSLESS void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose){ //default NIfTI 16-bit is signed, set to unusual 16-bit unsigned if required... if (hdr->datatype != DT_UINT16) return; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int nVox = hdr->dim[1]*hdr->dim[2]* dim3to7; if (nVox < 1) return; unsigned short * img16 = (unsigned short*) img; unsigned short max16 = img16[0]; //clock_t start = clock(); for (int i=0; i < nVox; i++) if (img16[i] > max16) max16 = img16[i]; //printMessage("max16= %d vox=%d %fms\n",max16, nVox, ((double)(clock()-start))/1000); if (max16 > 32767) { if (isVerbose > 0) printMessage("Note: rare 16-bit UNSIGNED integer image. Older tools may require 32-bit conversion\n"); } else hdr->datatype = DT_INT16; } //nii_check16bitUnsigned() #else void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose){ if (hdr->datatype != DT_UINT16) return; if (isVerbose < 1) return; printMessage("Note: rare 16-bit UNSIGNED integer image. Older tools may require 32-bit conversion\n"); } #endif 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)) { 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); } unsigned char * nii_saveNII3DtiltFloat32(char * niiFilename, struct nifti_1_header * hdr, unsigned char* im, struct TDCMopts opts, 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... 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 // printMessage("gantry tilt pixels per mm %g\n",GNTtanPx); float * imIn32 = ( float*) im; //create new output image: larger due to skew // compute how many pixels slice must be extended due to skew int s = hdrIn.dim[3] - 1; //top slice float maxSliceMM = fabs(s * hdrIn.pixdim[3]); if (sliceMMarray != NULL) maxSliceMM = fabs(sliceMMarray[s]); int pxOffset = ceil(fabs(GNTtanPx*maxSliceMM)); // printMessage("Tilt extends slice by %d pixels", pxOffset); hdr->dim[2] = hdr->dim[2] + pxOffset; 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 darkest observed value float minVoxVal = imIn32[0]; for (int v = 0; v < (nVox2DIn * hdrIn.dim[3]); v++) if (imIn32[v] < minVoxVal) minVoxVal = imIn32[v]; for (int v = 0; v < (nVox2D * hdrIn.dim[3]); v++) imOut32[v] = minVoxVal; //copy skewed voxels for (int s = 0; s < hdrIn.dim[3]; s++) { //for each slice float sliceMM = s * hdrIn.pixdim[3]; if (sliceMMarray != NULL) sliceMM = sliceMMarray[s]; //variable slice thicknesses //sliceMM -= mmMidZ; //adjust so tilt relative to middle slice if (GNTtanPx < 0) sliceMM -= maxSliceMM; float Offset = GNTtanPx*sliceMM; float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset float fracLo = 1.0f - fracHi; for (int r = 0; r < hdr->dim[2]; r++) { //for each row of output float rI = (float)r - Offset; //input row if ((rI >= 0.0) && (rI < hdrIn.dim[2])) { int rLo = floor(rI); int rHi = rLo + 1; if (rHi >= hdrIn.dim[2]) rHi = rLo; rLo = (rLo * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row below rHi = (rHi * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row above int rOut = (r * hdrIn.dim[1]) + (s * nVox2D); //offset to output row for (int c = 0; c < hdrIn.dim[1]; c++) { //for each row imOut32[rOut+c] = round( ( ((float)imIn32[rLo+c])*fracLo) + ((float)imIn32[rHi+c])*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); return imOut; }// nii_saveNII3DtiltFloat32() unsigned char * nii_saveNII3Dtilt(char * niiFilename, struct nifti_1_header * hdr, unsigned char* im, struct TDCMopts opts, 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, 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... 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 // printMessage("gantry tilt pixels per mm %g\n",GNTtanPx); short * imIn16 = ( short*) im; //create new output image: larger due to skew // compute how many pixels slice must be extended due to skew int s = hdrIn.dim[3] - 1; //top slice float maxSliceMM = fabs(s * hdrIn.pixdim[3]); if (sliceMMarray != NULL) maxSliceMM = fabs(sliceMMarray[s]); int pxOffset = ceil(fabs(GNTtanPx*maxSliceMM)); // printMessage("Tilt extends slice by %d pixels", pxOffset); hdr->dim[2] = hdr->dim[2] + pxOffset; 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 darkest observed value int minVoxVal = imIn16[0]; for (int v = 0; v < (nVox2DIn * hdrIn.dim[3]); v++) if (imIn16[v] < minVoxVal) minVoxVal = imIn16[v]; for (int v = 0; v < (nVox2D * hdrIn.dim[3]); v++) imOut16[v] = minVoxVal; //copy skewed voxels for (int s = 0; s < hdrIn.dim[3]; s++) { //for each slice float sliceMM = s * hdrIn.pixdim[3]; if (sliceMMarray != NULL) sliceMM = sliceMMarray[s]; //variable slice thicknesses //sliceMM -= mmMidZ; //adjust so tilt relative to middle slice if (GNTtanPx < 0) sliceMM -= maxSliceMM; float Offset = GNTtanPx*sliceMM; float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset float fracLo = 1.0f - fracHi; for (int r = 0; r < hdr->dim[2]; r++) { //for each row of output float rI = (float)r - Offset; //input row if ((rI >= 0.0) && (rI < hdrIn.dim[2])) { int rLo = floor(rI); int rHi = rLo + 1; if (rHi >= hdrIn.dim[2]) rHi = rLo; rLo = (rLo * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row below rHi = (rHi * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row above int rOut = (r * hdrIn.dim[1]) + (s * nVox2D); //offset to output row for (int c = 0; c < hdrIn.dim[1]; c++) { //for each row imOut16[rOut+c] = round( ( ((float)imIn16[rLo+c])*fracLo) + ((float)imIn16[rHi+c])*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); return imOut; }// nii_saveNII3Dtilt() 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; } 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) // <- only allow consistent slice direction if ((dx < mn) && (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 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 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); } void PhilipsPrecise(struct TDICOMdata * d, bool isPhilipsFloatNotDisplayScaling, struct nifti_1_header *h, int verbose) { if (d->manufacturer != kMANUFACTURER_PHILIPS) return; //not Philips if (!isSameFloatGE(0.0, d->RWVScale)) { h->scl_slope = d->RWVScale; h->scl_inter = d->RWVIntercept; printMessage("Using RWVSlope:RWVIntercept = %g:%g\n",d->RWVScale,d->RWVIntercept); printMessage(" Philips Scaling Values RS:RI:SS = %g:%g:%g (see PMC3998685)\n",d->intenScale,d->intenIntercept,d->intenScalePhilips); if (verbose == 0) return; printMessage("Potential Alternative Intensity Scalings\n"); printMessage(" R = raw value, P = precise value, D = displayed value\n"); printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); printMessage(" D = R * RS + RI , P = D/(RS * SS)\n"); return; } if (d->intenScalePhilips == 0) return; //no Philips Precise //we will report calibrated "FP" values http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/ float l0 = PhilipsPreciseVal (0, d->intenScale, d->intenIntercept, d->intenScalePhilips); float l1 = PhilipsPreciseVal (1, d->intenScale, d->intenIntercept, d->intenScalePhilips); float intenScaleP = d->intenScale; float intenInterceptP = d->intenIntercept; if (l0 != l1) { intenInterceptP = l0; intenScaleP = l1-l0; } if (isSameFloat(d->intenIntercept,intenInterceptP) && isSameFloat(d->intenScale, intenScaleP)) return; //same result for both methods: nothing to do or report! printMessage("Philips Scaling Values RS:RI:SS = %g:%g:%g (see PMC3998685)\n",d->intenScale,d->intenIntercept,d->intenScalePhilips); if (verbose > 0) { printMessage(" R = raw value, P = precise value, D = displayed value\n"); printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); printMessage(" D = R * RS + RI , P = D/(RS * SS)\n"); printMessage(" D scl_slope:scl_inter = %g:%g\n", d->intenScale,d->intenIntercept); printMessage(" P scl_slope:scl_inter = %g:%g\n", intenScaleP,intenInterceptP); } //#define myUsePhilipsPrecise if (isPhilipsFloatNotDisplayScaling) { if (verbose > 0) printMessage(" Using P values ('-p n ' for D values)\n"); //to change DICOM: //d->intenScale = intenScaleP; //d->intenIntercept = intenInterceptP; //to change NIfTI h->scl_slope = intenScaleP; h->scl_inter = intenInterceptP; d->intenScalePhilips = 0; //so we never run this TWICE! } else if (verbose > 0) printMessage(" Using D values ('-p y ' for P values)\n"); } //PhilipsPrecise() void smooth1D(int num, double * im) { if (num < 3) return; double * src = (double *) malloc(sizeof(double)*num); memcpy(&src[0], &im[0], num * sizeof(double)); //memcpy( dest, src, bytes) double frac = 0.25; for (int i = 1; i < (num-1); i++) im[i] = (src[i-1]*frac) + (src[i]*frac*2) + (src[i+1]*frac); free(src); }// smooth1D() int nii_saveCrop(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts) { //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 /*#ifdef myEnableOtsu // This code removes noise "haze" yielding better volume rendering and smaller gz compressed files // However, it may disrupt "differennce of gaussian" segmentation estimates // Therefore feature was removed from dcm2niix, which aims for lossless conversion kThresh = 0.0001; if (hdr.datatype == DT_UINT16) maskBackgroundU16 (imu16, hdr.dim[1],hdr.dim[2],hdr.dim[3], 5,2, true); else maskBackground16 (im16, hdr.dim[1],hdr.dim[2],hdr.dim[3], 5,2, true); #endif*/ int ventralCrop = 0; //find max value for each slice int slices = hdr.dim[3]; double * sliceSums = (double *) malloc(sizeof(double)*slices); double maxSliceVal = 0.0; for (int i = (slices-1); i >= 0; i--) { sliceSums[i] = 0; int sliceStart = i * nVox2D; if (hdr.datatype == DT_UINT16) for (int j = 0; j < nVox2D; j++) sliceSums[i] += imu16[j+sliceStart]; else for (int j = 0; j < nVox2D; j++) sliceSums[i] += im16[j+sliceStart]; if (sliceSums[i] > maxSliceVal) maxSliceVal = sliceSums[i]; } if (maxSliceVal <= 0) { free(sliceSums); return EXIT_FAILURE; } smooth1D(slices, sliceSums); for (int i = 0; i < slices; i++) sliceSums[i] = sliceSums[i] / maxSliceVal; //so brightest slice has value 1 //dorsal crop: eliminate slices with more than 5% brightness int dorsalCrop; for (dorsalCrop = (slices-1); dorsalCrop >= 1; dorsalCrop--) if (sliceSums[dorsalCrop-1] > kThresh) break; if (dorsalCrop <= 1) { free(sliceSums); return EXIT_FAILURE; } /* //find brightest band within 90mm of top of head int ventralMaxSlice = dorsalCrop - round(90 /fabs(hdr.pixdim[3])); //brightest stripe within 90mm of apex if (ventralMaxSlice < 0) ventralMaxSlice = 0; int maxSlice = dorsalCrop; for (int i = ventralMaxSlice; i < dorsalCrop; i++) if (sliceSums[i] > sliceSums[maxSlice]) maxSlice = i; //now find ventralMaxSlice = maxSlice - round(45 /fabs(hdr.pixdim[3])); //gap at least 60mm if (ventralMaxSlice < 0) { free(sliceSums); return EXIT_FAILURE; } int ventralMinSlice = maxSlice - round(90/fabs(hdr.pixdim[3])); //gap no more than 120mm if (ventralMinSlice < 0) ventralMinSlice = 0; for (int i = (ventralMaxSlice-1); i >= ventralMinSlice; i--) if (sliceSums[i] > sliceSums[ventralMaxSlice]) ventralMaxSlice = i; //finally: find minima between these two points... int minSlice = ventralMaxSlice; for (int i = ventralMaxSlice; i < maxSlice; i++) if (sliceSums[i] < sliceSums[minSlice]) minSlice = i; //printMessage("%d %d %d\n", ventralMaxSlice, minSlice, maxSlice); int gap = round((maxSlice-minSlice)*0.8);//add 40% for cerebellum if ((minSlice-gap) > 1) ventralCrop = minSlice-gap; free(sliceSums); if (ventralCrop > dorsalCrop) return EXIT_FAILURE; //FindDVCrop2 const double kMaxDVmm = 180.0; double sliceMM = hdr.pixdim[3] * (dorsalCrop-ventralCrop); if (sliceMM > kMaxDVmm) { //decide how many more ventral slices to remove sliceMM = sliceMM - kMaxDVmm; sliceMM = sliceMM / hdr.pixdim[3]; ventralCrop = ventralCrop + round(sliceMM); }*/ const double kMaxDVmm = 169.0; ventralCrop = dorsalCrop - round( kMaxDVmm / hdr.pixdim[3]); if (ventralCrop < 0) ventralCrop = 0; //apply crop printMessage(" Cropping from slice %d to %d (of %d)\n", ventralCrop, dorsalCrop, slices); struct nifti_1_header hdrX = hdr; slices = dorsalCrop - ventralCrop + 1; hdrX.dim[3] = slices; //translate origin to account for missing slices hdrX.srow_x[3] += hdr.srow_x[2]*ventralCrop; hdrX.srow_y[3] += hdr.srow_y[2]*ventralCrop; hdrX.srow_z[3] += hdr.srow_z[2]*ventralCrop; //convert data unsigned char *imX; imX = (unsigned char *)malloc( (nVox2D * slices) * 2);//sizeof( short) ); short * imX16 = ( short*) imX; for (int s=0; s < slices; s++) { int sIn = s+ventralCrop; int sOut = s; sOut = sOut * nVox2D; sIn = sIn * nVox2D; memcpy(&imX16[sOut], &im16[sIn], nVox2D* sizeof(unsigned short)); //memcpy( dest, src, bytes) } char niiFilenameCrop[2048] = {""}; strcat(niiFilenameCrop,niiFilename); strcat(niiFilenameCrop,"_Crop"); const int returnCode = nii_saveNII3D(niiFilenameCrop, hdrX, imX, opts); free(imX); return returnCode; }// nii_saveCrop() float dicomTimeToSec (float dicomTime) { //convert HHMMSS to seconds, 135300.024 -> 135259.731 are 0.293 sec apart char acqTimeBuf[64]; snprintf(acqTimeBuf, sizeof acqTimeBuf, "%+013.5f", (double)dicomTime); int ahour,amin; double asec; int count = 0; sscanf(acqTimeBuf, "%3d%2d%lf%n", &ahour, &amin, &asec, &count); if (!count) return -1; return (ahour * 3600)+(amin * 60) + asec; } float acquisitionTimeDifference(struct TDICOMdata * d1, struct TDICOMdata * d2) { if (d1->acquisitionDate != d2->acquisitionDate) return -1; //to do: scans running across midnight float sec1 = dicomTimeToSec(d1->acquisitionTime); float sec2 = dicomTimeToSec(d2->acquisitionTime); //printMessage("%g\n",d2->acquisitionTime); if ((sec1 < 0) || (sec2 < 0)) return -1; return (sec2 - sec1); } void checkDateTimeOrder(struct TDICOMdata * d, struct TDICOMdata * d1) { if (d->acquisitionDate < d1->acquisitionDate) return; //d1 occurred on later date if (d->acquisitionTime <= d1->acquisitionTime) return; //d1 occurred on later (or same) time if (d->imageNum > d1->imageNum) printWarning("Images not sorted in ascending instance number (0020,0013)\n"); else printWarning("Images sorted by instance number [0020,0013](%d..%d), but AcquisitionTime [0008,0032] suggests a different order (%g..%g) \n", d->imageNum,d1->imageNum, d->acquisitionTime,d1->acquisitionTime); } void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1) { //detect images with slice timing errors. https://github.com/rordenlab/dcm2niix/issues/126 if ((d->TR < 0.0) || (d->CSA.sliceTiming[0] < 0.0)) return; //no slice timing int nSlices = 0; while ((nSlices < kMaxEPI3D) && (d->CSA.sliceTiming[nSlices] >= 0.0)) nSlices++; if (nSlices < 1) return; bool isSliceTimeHHMMSS = (d->manufacturer == kMANUFACTURER_UIH); //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) {//convert HHMMSS to Sec 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]; } 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("UIH acquisition crossed midnight: check slice timing\n"); for (int i = 0; i < nSlices; i++) if (d->CSA.sliceTiming[i] > kNoonSec) d->CSA.sliceTiming[i] = d->CSA.sliceTiming[i] - kMidnightSec; minT = d->CSA.sliceTiming[0]; for (int i = 0; i < nSlices; i++) if (d->CSA.sliceTiming[i] < minT) minT = d->CSA.sliceTiming[i]; } for (int i = 0; i < nSlices; i++) d->CSA.sliceTiming[i] = d->CSA.sliceTiming[i] - minT; } //XA10/UIH: HHMMSS -> Sec float minT = d->CSA.sliceTiming[0]; float maxT = minT; for (int i = 0; i < kMaxEPI3D; i++) { if (d->CSA.sliceTiming[i] < 0.0) break; if (d->CSA.sliceTiming[i] < minT) minT = d->CSA.sliceTiming[i]; if (d->CSA.sliceTiming[i] > maxT) maxT = d->CSA.sliceTiming[i]; } if (isSliceTimeHHMMSS) //convert HHMMSS to Sec for (int i = 0; i < kMaxEPI3D; i++) d->CSA.sliceTiming[i] = dicomTimeToSec(d->CSA.sliceTiming[i]); if ((minT != maxT) && (maxT <= d->TR)) 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 //check if 2nd image has valud 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 (minT1 < 0.0) { //https://github.com/neurolabusc/MRIcroGL/issues/31 printWarning("Siemens MoCo? Bogus slice timing (range %g..%g, TR=%gms)\n", minT1, maxT1, d->TR); return; } if ((minT1 == maxT1) || (maxT1 >= d->TR)) { //both first and second image corrupted printWarning("CSA slice timing appears corrupted (range %g..%g, TR=%gms)\n", minT1, maxT1, d->TR); return; } //1st image corrupted, but 2nd looks ok - substitute values from 2nd image for (int i = 0; i < kMaxEPI3D; i++) { d->CSA.sliceTiming[i] = d1->CSA.sliceTiming[i]; if (d1->CSA.sliceTiming[i] < 0.0) break; } d->CSA.multiBandFactor = d1->CSA.multiBandFactor; printMessage("CSA slice timing based on 2nd volume, 1st volume corrupted (CMRR bug, range %g..%g, TR=%gms)\n", minT, maxT, d->TR); }//checkSliceTiming 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 ((dcmList[indx].isXA10A) && (dcmList[indx].CSA.mosaicSlices < 0)) { printMessage("Siemens XA10 Mosaics are not primary images and lack vital data.\n"); printMessage(" See https://github.com/rordenlab/dcm2niix/issues/236\n"); #ifdef mySaveXA10Mosaics int n; printMessage("INPUT REQUIRED FOR %s\n", dcmList[indx].imageBaseName); printMessage("PLEASE ENTER NUMBER OF SLICES IN MOSAIC:\n"); scanf ("%d",&n); for (int i = 0; i < nConvert; i++) dcmList[dcmSort[i].indx].CSA.mosaicSlices = n; #endif } if (nConvert > 1) indx1 = dcmSort[1].indx; 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; } #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; //if (iVaries) img = nii_iVaries(img, &hdr0); 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]); if (nConvert > 1) { //next: determine gantry tilt if (dcmList[indx0].gantryTilt != 0.0f) printMessage(" Warning: 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); } if ((nAcq == 1 ) && (dcmList[indx0].locationsInAcquisition > 0)) nAcq = nConvert/dcmList[indx0].locationsInAcquisition; 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; } 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); } } //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].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 (dcmList[indx0].modality == kMODALITY_PT) { bool trVaries = false; bool dayVaries = false; float tr = -1; 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; if (trDiff <= 0) continue; if (tr < 0) tr = trDiff; if (trDiff < 0) dayVaries = true; if (!isSameFloatGE(tr,trDiff)) trVaries = true; } if (trVaries) { if (dayVaries) printWarning("Seconds between volumes varies (perhaps run through midnight)\n"); else printWarning("Seconds between volumes varies\n"); // saveAs3D = true; // printWarning("Creating independent volumes as time between volumes varies\n"); printMessage(" OnsetTime = ["); for (int i = 0; i < nConvert; i++) if (isSamePosition(dcmList[indx0],dcmList[dcmSort[i].indx])) { float trDiff = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); printMessage(" %g", trDiff); } printMessage(" ]\n"); } //if trVaries } //if PET //next: detect variable inter-slice distance float dx = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); 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; printMessage("Dims %d %d %d %d %d\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], nAcq); printWarning("Interslice distance varies in this volume (incompatible with NIfTI format).\n"); printMessage(" Distance from first slice:\n"); printMessage("dx=[0"); for (int i = 1; i < nConvert; i++) { float dx0 = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]); printMessage(" %g", dx0); sliceMMarray[i] = dx0; } printMessage("]\n"); int imageNumRange = 1 + abs( dcmList[dcmSort[nConvert-1].indx].imageNum - dcmList[dcmSort[0].indx].imageNum); if ((imageNumRange > 1) && (imageNumRange != nConvert)) { 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); printMessage("instance=["); for (int i = 0; i < nConvert; i++) { printMessage(" %d", dcmList[dcmSort[i].indx].imageNum); } printMessage("]\n"); } //imageNum not sequential } } if ((hdr0.dim[4] > 0) && (dxVaries) && (dx == 0.0) && ((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]); 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 printMessage(" Warning: 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 (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; for (int i = 1; i < nConvert; i++) { //stack additional images indx = dcmSort[i].indx; //double time2 = dcmList[dcmSort[i].indx].acquisitionTime; //if (time != time2) // printWarning("%g\n", time2); //time = time2; //if (headerDcm2Nii(dcmList[indx], &hdrI) == EXIT_FAILURE) return EXIT_FAILURE; img = nii_loadImgXL(nameList->str[indx], &hdrI, dcmList[indx],iVaries, opts.compressFlag, opts.isVerbose, dti4D); if (img == NULL) return EXIT_FAILURE; if ((hdr0.dim[1] != hdrI.dim[1]) || (hdr0.dim[2] != hdrI.dim[2]) || (hdr0.bitpix != hdrI.bitpix)) { printError("Image dimensions differ %s %s",nameList->str[dcmSort[0].indx], nameList->str[indx]); free(imgM); free(img); return EXIT_FAILURE; } memcpy(&imgM[(uint64_t)i*imgsz], &img[0], imgsz); free(img); } 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]); } //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 if ((dcmList[dcmSort[0].indx].isXA10A) && (hdr0.dim[4] < 2)) dcmList[dcmSort[0].indx].CSA.sliceTiming[0] = -1.0; //XA10A slice timing often not correct for 1st volume if (((dcmList[dcmSort[0].indx].isXA10A)) && (nConvert == (hdr0.dim[4])) && (hdr0.dim[3] < (kMaxEPI3D-1)) && (hdr0.dim[3] > 1) && (hdr0.dim[4] > 1)) { float mn = dcmList[dcmSort[1].indx].CSA.sliceTiming[0]; //for (int v = 0; v < hdr0.dim[4]; v++) // for (int z = 0; z < hdr0.dim[3]; z++) // printf("%g\n",dcmList[dcmSort[v].indx].CSA.sliceTiming[z]); //get slice timing from second volume for (int v = 0; v < hdr0.dim[3]; v++) { dcmList[dcmSort[0].indx].CSA.sliceTiming[v] = dcmList[dcmSort[1].indx].CSA.sliceTiming[v]; if (dcmList[dcmSort[0].indx].CSA.sliceTiming[v] < mn) mn = dcmList[dcmSort[0].indx].CSA.sliceTiming[v]; } if (mn < 0.0) mn = 0.0; int mb = 0; for (int v = 0; v < hdr0.dim[3]; v++) { dcmList[dcmSort[0].indx].CSA.sliceTiming[v] -= mn; if (isSameFloatGE(dcmList[dcmSort[0].indx].CSA.sliceTiming[v], 0.0)) mb ++; } if ((dcmList[dcmSort[0].indx].CSA.multiBandFactor < 2) && (mb > 1)) dcmList[dcmSort[0].indx].CSA.multiBandFactor = mb; //for (int v = 0; v < hdr0.dim[3]; v++) // printf("XA10sliceTiming\t%d\t%g\n", v, dcmList[dcmSort[0].indx].CSA.sliceTiming[v]); } //UIH 2D slice timing if (((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UIH)) && (nConvert == (hdr0.dim[3]*hdr0.dim[4])) && (hdr0.dim[3] < (kMaxEPI3D-1)) && (hdr0.dim[3] > 1) && (hdr0.dim[4] > 1)) { for (int v = 0; v < hdr0.dim[3]; v++) dcmList[dcmSort[0].indx].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].acquisitionTime; } //GE check slice timing >>> bool GEsliceTiming_x0018x1060 = false; if ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE) && (hdr0.dim[3] < (kMaxEPI3D-1)) && (hdr0.dim[3] > 1) && (hdr0.dim[4] > 1)) { //GE: 1st method for "epi" PSD GEsliceTiming_x0018x1060 = true; for (int v = 0; v < hdr0.dim[3]; v++) { if (dcmList[dcmSort[v].indx].CSA.sliceTiming[0] < 0) GEsliceTiming_x0018x1060 = false; dcmList[dcmSort[0].indx].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].CSA.sliceTiming[0] / 1000.0; //ms -> sec } //0018,1060 provides time at end of acquisition, not start... if (GEsliceTiming_x0018x1060) { float minT = dcmList[dcmSort[0].indx].CSA.sliceTiming[0]; for (int v = 0; v < hdr0.dim[3]; v++) if (dcmList[dcmSort[0].indx].CSA.sliceTiming[v] < minT) minT = dcmList[dcmSort[0].indx].CSA.sliceTiming[v]; for (int v = 0; v < hdr0.dim[3]; v++) dcmList[dcmSort[0].indx].CSA.sliceTiming[v] = dcmList[dcmSort[0].indx].CSA.sliceTiming[v] - minT; } //adjust: first slice is time = 0.0 } //GE slice timing from 0018,1060 if ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE) && (!GEsliceTiming_x0018x1060) && (hdr0.dim[3] < (kMaxEPI3D-1)) && (hdr0.dim[3] > 1) && (hdr0.dim[4] > 1)) { //GE: 2nd method for "epiRT" PSD //ignore bogus values of first volume https://neurostars.org/t/getting-missing-ge-information-required-by-bids-for-common-preprocessing/1357/6 // this necessarily requires at last two volumes, hence dim[4] > 1 int j = hdr0.dim[3]; //since first volume is bogus, we define the volume start time as the first slice in the second volume float minTime = dcmList[dcmSort[j].indx].rtia_timerGE; float maxTime = minTime; for (int v = 0; v < hdr0.dim[3]; v++) { if (dcmList[dcmSort[v+j].indx].rtia_timerGE < minTime) minTime = dcmList[dcmSort[v+j].indx].rtia_timerGE; if (dcmList[dcmSort[v+j].indx].rtia_timerGE > maxTime) maxTime = dcmList[dcmSort[v+j].indx].rtia_timerGE; } //compare all slice times in 2nd volume to start time for this volume if (maxTime != minTime) { for (int v = 0; v < hdr0.dim[3]; v++) dcmList[dcmSort[0].indx].CSA.sliceTiming[v] = dcmList[dcmSort[v+j].indx].rtia_timerGE - minTime; dcmList[dcmSort[0].indx].CSA.sliceTiming[hdr0.dim[3]] = -1; //detect multi-band int nZero = 0; for (int v = 0; v < hdr0.dim[3]; v++) if (isSameFloatGE(dcmList[dcmSort[0].indx].CSA.sliceTiming[hdr0.dim[3]], 0.0)) nZero ++; if ((nZero > 1) && (nZero < hdr0.dim[3]) && ((hdr0.dim[3] % nZero) == 0)) dcmList[dcmSort[0].indx].CSA.multiBandFactor = nZero; //report timines if (opts.isVerbose > 1) { printf("GE slice timing\n"); printf("\tTime\tX\tY\tZ\tInstance\n"); for (int v = 0; v < hdr0.dim[3]; v++) { if (v == (hdr0.dim[3]-1)) printf("...\n"); if ((v < 4) || (v == (hdr0.dim[3]-1))) printf("\t%g\t%g\t%g\t%g\t%d\n", dcmList[dcmSort[0].indx].CSA.sliceTiming[v], dcmList[dcmSort[v+j].indx].patientPosition[1], dcmList[dcmSort[v+j].indx].patientPosition[2], dcmList[dcmSort[v+j].indx].patientPosition[3], dcmList[dcmSort[v+j].indx].imageNum); } //for v } //verbose > 1 } //if maxTime != minTIme } //GE slice timing from 0021,105E if ((segVol >= 0) && (hdr0.dim[4] > 1)) { int inVol = hdr0.dim[4]; int nVol = 0; for (int v = 0; v < inVol; v++) if (dti4D->gradDynVol[v] == segVol) nVol ++; if (nVol < 1) { printError("Series %d does not exist\n", segVol); return EXIT_FAILURE; } size_t imgsz4D = imgsz; if (nVol < 2) hdr0.dim[0] = 3; //3D hdr0.dim[4] = 1; size_t imgsz3D = nii_ImgBytes(hdr0); unsigned char *img4D = (unsigned char *)malloc(imgsz4D); memcpy(&img4D[0], &imgM[0], imgsz4D); free(imgM); imgM = (unsigned char *)malloc(imgsz3D * nVol); int outVol = 0; for (int v = 0; v < inVol; v++) { if ((dti4D->gradDynVol[v] == segVol) && (outVol < nVol)) { memcpy(&imgM[outVol * imgsz3D], &img4D[v * imgsz3D], imgsz3D); outVol ++; } } hdr0.dim[4] = nVol; imgsz = nii_ImgBytes(hdr0); free(img4D); saveAs3D = false; } if (strlen(dcmList[dcmSort[0].indx].protocolName) < 1) rescueProtocolName(&dcmList[dcmSort[0].indx], nameList->str[dcmSort[0].indx]); char pathoutname[2048] = {""}; if (nii_createFilename(dcmList[dcmSort[0].indx], pathoutname, opts) == EXIT_FAILURE) { free(imgM); return EXIT_FAILURE; } if (strlen(pathoutname) <1) { free(imgM); return EXIT_FAILURE; } // 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 if (segVol >= 0) { printMessage("\t%ld.%d\t%s\n", dcmList[dcmSort[0].indx].seriesNum, segVol-1, pathoutname); } else { printMessage("\t%ld\t%s\n", dcmList[dcmSort[0].indx].seriesNum, pathoutname); } printMessage(" %s\n",nameList->str[dcmSort[0].indx]); return EXIT_SUCCESS; } checkSliceTiming(&dcmList[indx0], &dcmList[indx1]); int sliceDir = 0; if (hdr0.dim[3] > 1)sliceDir = headerDcm2Nii2(dcmList[dcmSort[0].indx],dcmList[dcmSort[nConvert-1].indx] , &hdr0, true); //UNCOMMENT NEXT TWO LINES TO RE-ORDER MOSAIC WHERE CSA's protocolSliceNumber does not start with 1 if (dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1 > 1) { printWarning("Weird CSA 'ProtocolSliceNumber' (System/Miscellaneous/ImageNumbering reversed): VALIDATE SLICETIMING AND BVECS\n"); //https://www.healthcare.siemens.com/siemens_hwem-hwem_ssxa_websites-context-root/wcm/idc/groups/public/@global/@imaging/@mri/documents/download/mdaz/nzmy/~edisp/mri_60_graessner-01646277.pdf //see https://github.com/neurolabusc/dcm2niix/issues/40 sliceDir = -1; //not sure how to handle negative determinants? } if (sliceDir < 0) { 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! if ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UIH) && (dcmList[dcmSort[0].indx].CSA.sliceTiming[0] >= 0.0) ) dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1 = -1; if ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE) && (dcmList[dcmSort[0].indx].CSA.sliceTiming[0] >= 0.0) ) dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1 = -1; } // skip converting if user has specified one or more series, but has not specified this one if (opts.numSeries > 0) { int i = 0; float seriesNum = (float) dcmList[dcmSort[0].indx].seriesNum; if (segVol > 0) seriesNum = seriesNum + ((float) segVol - 1.0) / 10.0; //n.b. we will have problems if segVol > 9. However, 9 distinct TEs/scalings/PhaseMag seems unlikely for (; i < opts.numSeries; i++) { if (isSameFloatGE(opts.seriesNumber[i], seriesNum)) { //if (opts.seriesNumber[i] == dcmList[dcmSort[0].indx].seriesNum) { break; } } if (i == opts.numSeries) { return EXIT_SUCCESS; } } //move before headerDcm2Nii2 checkSliceTiming(&dcmList[indx0], &dcmList[indx1]); //nii_SaveBIDS(pathoutname, dcmList[dcmSort[0].indx], opts, dti4D, &hdr0, nameList->str[dcmSort[0].indx]); nii_SaveBIDS(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[dcmSort[0].indx]); if (opts.isOnlyBIDS) { //note we waste time loading every image, however this ensures hdr0 matches actual output free(imgM); return EXIT_SUCCESS; } 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); PhilipsPrecise(&dcmList[dcmSort[0].indx], opts.isPhilipsFloatNotDisplayScaling, &hdr0, opts.isVerbose); if ((opts.isMaximize16BitRange) && (hdr0.datatype == DT_INT16)) { nii_scale16bitSigned(imgM, &hdr0, opts.isVerbose); //allow INT16 to use full dynamic range } else if ((opts.isMaximize16BitRange) && (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) && (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 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]); //~ if (!dcmList[dcmSort[0].indx].isSlicesSpatiallySequentialPhilips) //~ nii_reorderSlices(imgM, &hdr0, dti4D); if (hdr0.dim[3] < 2) printWarning("Check that 2D images are not mirrored.\n"); #ifndef USING_R else fflush(stdout); //GUI buffers printf, display all results #endif if ((dcmList[dcmSort[0].indx].is3DAcq) && (hdr0.dim[3] > 1) && (hdr0.dim[0] < 4)) imgM = nii_setOrtho(imgM, &hdr0); //printMessage("ortho %d\n", echoInt (33)); else if (opts.isFlipY)//(FLIP_Y) //(dcmList[indx0].CSA.mosaicSlices < 2) && imgM = nii_flipY(imgM, &hdr0); else printMessage("DICOM row order preserved: may appear upside down in tools that ignore spatial transforms\n"); #ifndef myNoSave // Indicates success or failure of the (last) save int returnCode = EXIT_FAILURE; //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); else { if (volOrderIndex) //reorder volumes imgM = reorderVolumes(&hdr0, imgM, volOrderIndex); #ifndef USING_R if ((opts.isIgnoreDerivedAnd2D) && (numADC > 0)) printMessage("Ignoring derived diffusion image(s). Better isotropic and ADC maps can be generated later processing.\n"); if ((!opts.isIgnoreDerivedAnd2D) && (numADC > 0)) {//ADC maps can disrupt analysis: save a copy with the ADC map, and another without char pathoutnameADC[2048] = {""}; strcat(pathoutnameADC,pathoutname); strcat(pathoutnameADC,"_ADC"); if (opts.isSave3D) nii_saveNII3D(pathoutnameADC, hdr0, imgM, opts); else nii_saveNII(pathoutnameADC, hdr0, imgM, opts); } #endif imgM = removeADC(&hdr0, imgM, numADC); #ifndef USING_R if (opts.isSave3D) returnCode = nii_saveNII3D(pathoutname, hdr0, imgM, opts); else returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts); #endif } #endif if (dcmList[indx0].gantryTilt != 0.0) { if (dcmList[indx0].isResampled) { printMessage("Tilt correction skipped: 0008,2111 reports RESAMPLED\n"); } else if (opts.isTiltCorrect) { imgM = nii_saveNII3Dtilt(pathoutname, &hdr0, imgM,opts, 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, sliceMMarray); free(sliceMMarray); } if ((opts.isCrop) && (dcmList[indx0].is3DAcq) && (hdr0.dim[3] > 1) && (hdr0.dim[0] < 4))//for T1 scan: && (dcmList[indx0].TE < 25) returnCode = nii_saveCrop(pathoutname, hdr0, imgM,opts); //n.b. must be run AFTER nii_setOrtho()! #ifdef USING_R // Note that for R, only one image should be created per series // Hence the logical OR here if (returnCode == EXIT_SUCCESS || nii_saveNII(pathoutname,hdr0,imgM,opts) == EXIT_SUCCESS) nii_saveAttributes(dcmList[dcmSort[0].indx], hdr0, opts); #endif free(imgM); return returnCode;//EXIT_SUCCESS; }// saveDcm2NiiCore() int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D) { //this wrapper does nothing if all the images share the same echo time and scale // however, it segments images when these properties vary uint64_t indx = dcmSort[0].indx; if ((!dcmList[indx].isScaleOrTEVaries) || (dcmList[indx].xyzDim[4] < 2)) return saveDcm2NiiCore(nConvert, dcmSort, dcmList, nameList, opts, dti4D, -1); if ((dcmList[indx].xyzDim[4]) && (dti4D->sliceOrder[0] < 0)) { printError("Unexpected error for image with varying echo time or intensity scaling\n"); return EXIT_FAILURE; } int ret = EXIT_SUCCESS; //check for repeated echoes - count unique number of echoes //code below checks for multi-echoes - not required if maxNumberOfEchoes reported in PARREC int echoNum[kMaxDTI4D]; int echo = 1; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) echoNum[i] = 0; echoNum[0] = 1; for (int i = 1; i < dcmList[indx].xyzDim[4]; i++) { for (int j = 0; j < i; j++) if (dti4D->TE[i] == dti4D->TE[j]) echoNum[i] = echoNum[j]; if (echoNum[i] == 0) { echo++; echoNum[i] = echo; } } if (echo > 1) dcmList[indx].isMultiEcho = true; //check for repeated volumes int series = 1; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) dti4D->gradDynVol[i] = 0; dti4D->gradDynVol[0] = 1; for (int i = 1; i < dcmList[indx].xyzDim[4]; i++) { for (int j = 0; j < i; j++) if (isSameFloatGE(dti4D->triggerDelayTime[i], dti4D->triggerDelayTime[j]) && (dti4D->intenIntercept[i] == dti4D->intenIntercept[j]) && (dti4D->intenScale[i] == dti4D->intenScale[j]) && (dti4D->isReal[i] == dti4D->isReal[j]) && (dti4D->isImaginary[i] == dti4D->isImaginary[j]) && (dti4D->isPhase[i] == dti4D->isPhase[j]) && (dti4D->TE[i] == dti4D->TE[j])) dti4D->gradDynVol[i] = dti4D->gradDynVol[j]; if (dti4D->gradDynVol[i] == 0) { series++; dti4D->gradDynVol[i] = series; } } //bvec/bval saved for each series (real, phase, magnitude, imaginary) https://github.com/rordenlab/dcm2niix/issues/219 TDTI4D dti4Ds; dti4Ds = *dti4D; bool isHasDti = (dcmList[indx].CSA.numDti > 0); if ((isHasDti) && (dcmList[indx].CSA.numDti == dcmList[indx].xyzDim[4])) { int nDti = 0; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { if (dti4D->gradDynVol[i] == 1) { dti4Ds.S[nDti].V[0] = dti4Ds.S[i].V[0]; dti4Ds.S[nDti].V[1] = dti4Ds.S[i].V[1]; dti4Ds.S[nDti].V[2] = dti4Ds.S[i].V[2]; dti4Ds.S[nDti].V[3] = dti4Ds.S[i].V[3]; nDti++; } } dcmList[indx].CSA.numDti = nDti; } //save each series for (int s = 1; s <= series; s++) { for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { if (dti4D->gradDynVol[i] == s) { //dti4D->gradDynVol[i] = s; //nVol ++; dcmList[indx].TE = dti4D->TE[i]; dcmList[indx].intenScale = dti4D->intenScale[i]; dcmList[indx].intenIntercept = dti4D->intenIntercept[i]; dcmList[indx].isHasPhase = dti4D->isPhase[i]; dcmList[indx].isHasReal = dti4D->isReal[i]; dcmList[indx].isHasImaginary = dti4D->isImaginary[i]; dcmList[indx].intenScalePhilips = dti4D->intenScalePhilips[i]; dcmList[indx].RWVScale = dti4D->RWVScale[i]; dcmList[indx].RWVIntercept = dti4D->RWVIntercept[i]; dcmList[indx].triggerDelayTime = dti4D->triggerDelayTime[i]; dcmList[indx].isHasMagnitude = false; dcmList[indx].echoNum = echoNum[i]; break; } } if (s > 1) dcmList[indx].CSA.numDti = 0; //only save bvec for first type (magnitude) int ret2 = saveDcm2NiiCore(nConvert, dcmSort, dcmList, nameList, opts, &dti4Ds, s); if (ret2 != EXIT_SUCCESS) ret = ret2; //return EXIT_SUCCESS only if ALL are successful } return ret; }// saveDcm2Nii() void fillTDCMsort(struct TDCMsort& tdcmref, const uint64_t indx, const struct TDICOMdata& dcmdata){ // Copy the relevant parts of dcmdata to tdcmref. tdcmref.indx = indx; //printf("series/image %d %d\n", dcmdata.seriesNum, dcmdata.imageNum); tdcmref.img = ((uint64_t)dcmdata.seriesNum << 32) + dcmdata.imageNum; for(int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) tdcmref.dimensionIndexValues[i] = dcmdata.dimensionIndexValues[i]; //lines below added to cope with extreme anonymization // https://github.com/rordenlab/dcm2niix/issues/211 if (tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS-1] != 0) return; //Since dimensionIndexValues are indexed from 1, 0 indicates unused // we leverage this as a hail mary attempt to distinguish images with identical series and instance numbers //See Correction Number CP-1242: // "Clarify in the description of dimension indices ... start from 1" // 0008,0032 stored as HHMMSS.FFFFFF, there are 86400000 ms per day // dimensionIndexValues stored as uint32, so encode acquisition time in ms uint32_t h = trunc(dcmdata.acquisitionTime / 10000.0); double tm = dcmdata.acquisitionTime - (h * 10000.0); uint32_t m = trunc(tm / 100.0); tm = tm - (m * 100.0); uint32_t ms = round(tm * 1000); ms += (h * 3600000) + (m * 60000); //printf("HHMMSS.FFFF %.5f -> %d ms\n", dcmdata.acquisitionTime, ms); tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS-1] = ms; } // fillTDCMsort() int compareTDCMsort(void const *item1, void const *item2) { //for quicksort http://blog.ablepear.com/2011/11/objective-c-tuesdays-sorting-arrays.html struct TDCMsort const *dcm1 = (const struct TDCMsort *)item1; struct TDCMsort const *dcm2 = (const struct TDCMsort *)item2; //to do: detect duplicates with SOPInstanceUID (0008,0018) - accurate but slow text comparison int retval = 0; // tie if (dcm1->img < dcm2->img) retval = -1; else if (dcm1->img > dcm2->img) retval = 1; if(retval != 0) return retval; //sorted images // Check the dimensionIndexValues (useful for enhanced DICOM 4D series). // ->img is basically behaving as a (seriesNum, imageNum) sort key // concatenated into a (large) integer for qsort. That is unwieldy when // dimensionIndexValues need to be compared, because the existence of // uint128_t, uint256_t, etc. is not guaranteed. This sorts by // (seriesNum, ImageNum, div[0], div[1], ...), or if you think of it as a // number, the dimensionIndexValues come after the decimal point. for(int i=0; i < MAX_NUMBER_OF_DIMENSIONS; ++i){ if(dcm1->dimensionIndexValues[i] < dcm2->dimensionIndexValues[i]) return -1; else if(dcm1->dimensionIndexValues[i] > dcm2->dimensionIndexValues[i]) return 1; } return retval; } //compareTDCMsort() /*int 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); } struct TWarnings { //generate a warning only once per set bool acqNumVaries, bitDepthVaries, dateTimeVaries, echoVaries, phaseVaries, coilVaries, nameVaries, nameEmpty, orientVaries; }; TWarnings setWarnings() { TWarnings r; r.acqNumVaries = false; r.bitDepthVaries = false; r.dateTimeVaries = false; r.phaseVaries = false; r.echoVaries = false; r.coilVaries = false; r.nameVaries = false; r.nameEmpty = false; r.orientVaries = false; return r; } bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opts, struct TWarnings* warnings, bool *isMultiEcho, bool *isNonParallelSlices, bool *isCoilVaries) { //returns true if d1 and d2 should be stacked together as a single output if (!d1.isValid) return false; if (!d2.isValid) return false; if (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 if (d1.manufacturer != d2.manufacturer) return false; //do not stack data from different vendors if ((d1.isXA10A) && (d2.isXA10A) && (d1.seriesNum > 1000) && (d2.seriesNum > 1000)) { //kludge XA10A (0020,0011) increments [16001, 16002, ...] https://github.com/rordenlab/dcm2niix/issues/236 //images from series 16001,16002 report different study times (0008,0030)! if ((d1.seriesNum / 1000) != (d2.seriesNum / 1000)) return false; } else if (d1.seriesNum != d2.seriesNum) return false; #ifdef mySegmentByAcq if (d1.acquNum != d2.acquNum) return false; #endif bool isSameStudyInstanceUID = false; if ((strlen(d1.studyInstanceUID)> 1) && (strlen(d2.studyInstanceUID)> 1)) { if (strcmp(d1.studyInstanceUID, d2.studyInstanceUID) == 0) isSameStudyInstanceUID = true; } bool isSameTime = isSameFloatDouble(d1.dateTime, d2.dateTime); if ((isSameStudyInstanceUID) && (d1.isXA10A) && (d2.isXA10A)) isSameTime = true; //kludge XA10A 0008,0030 incorrect https://github.com/rordenlab/dcm2niix/issues/236 if ((!isSameStudyInstanceUID) && (!isSameTime)) return false; if ((d1.bitsAllocated != d2.bitsAllocated) || (d1.xyzDim[1] != d2.xyzDim[1]) || (d1.xyzDim[2] != d2.xyzDim[2]) || (d1.xyzDim[3] != d2.xyzDim[3]) ) { if (!warnings->bitDepthVaries) printMessage("slices not stacked: dimensions or bit-depth varies\n"); warnings->bitDepthVaries = 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) { if ((d1.TE != d2.TE) || (d1.echoNum != d2.echoNum)) *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 maps, others are not. Use 'merge 2D slices' option to force stacking\n"); warnings->phaseVaries = true; return false; } if ((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.coilCrc != d2.coilCrc) { if (!warnings->coilVaries) printMessage("slices not stacked: coil varies\n"); 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 ((!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)) 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) printMessage("slices stacked despite varying acquisition numbers (if this is not desired recompile with 'mySegmentByAcq')\n"); warnings->acqNumVaries = true; } return true; }// isSameSet() int singleDICOM(struct TDCMopts* opts, char *fname) { char filename[768] =""; strcat(filename, fname); if (isDICOMfile(filename) == 0) { printError("Not a DICOM image : %s\n", filename); return 0; } struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc( sizeof(struct TDICOMdata)); struct TDTI4D dti4D; 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(filename)+1); strcpy(nameList.str[nameList.numItems],filename); nameList.numItems++; struct TDCMsort dcmSort[1]; 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); return ret; }// singleDICOM() size_t fileBytes(const char * fname) { FILE *fp = fopen(fname, "rb"); if (!fp) return 0; fseek(fp, 0, SEEK_END); size_t fileLen = ftell(fp); fclose(fp); return fileLen; } //fileBytes() int 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 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); } else { if (fileBytes(filename) > 2048) convert_foreign (filename, *opts); #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(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(opts.indir)+1); strcpy(nameList.str[0],opts.indir); TDTI4D dti4D; 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(dcmList);//if (nConvertTotal == 0) if (nameList.numItems < 1) printMessage("No valid PAR/REC files were found\n"); if (nameList.numItems > 0) for (int i = 0; i < (int)nameList.numItems; i++) free(nameList.str[i]); free(nameList.str); return ret; }// convert_parRec() void freeNameList(struct TSearchList nameList) { if (nameList.numItems > 0) { unsigned long n = nameList.numItems; if (n > nameList.maxItems) n = nameList.maxItems; //assigned if (nameList->numItems < nameList->maxItems) for (unsigned long i = 0; i < n; i++) free(nameList.str[i]); } free(nameList.str); } int 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_FAILURE; } if (is_fileexists(dst_path)) { 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; } int nii_loadDir(struct TDCMopts* opts) { //Identifies all the DICOM files in a folder and its subfolders if (strlen(opts->indir) < 1) { printMessage("No input\n"); return EXIT_FAILURE; } char indir[512]; strcpy(indir,opts->indir); bool isFile = is_fileNotDir(opts->indir); //bool isParRec = (isFile && ( (isExt(indir, ".par")) || (isExt(indir, ".rec"))) ); if (isFile) //if user passes ~/dicom/mr1.dcm we will look at all files in ~/dicom dropFilenameFromPath(opts->indir);//getParentFolder(opts.indir, opts.indir); dropTrailingFileSep(opts->indir); if (strlen(opts->outdir) < 1) { strcpy(opts->outdir,opts->indir); } else dropTrailingFileSep(opts->outdir); if (!is_dir(opts->outdir,true)) { #ifdef myUseInDirIfOutDirUnavailable printWarning("Output folder invalid %s will try %s\n",opts->outdir,opts->indir); strcpy(opts->outdir,opts->indir); #else printError("Output folder invalid: %s\n",opts->outdir); return EXIT_FAILURE; #endif } /*if (isFile && ((isExt(indir, ".gz")) || (isExt(indir, ".tgz"))) ) { #ifndef myDisableTarGz #ifndef myDisableZLib untargz( indir, opts->outdir); #endif #endif }*/ getFileName(opts->indirParent, opts->indir); if (isFile && ( (isExt(indir, ".v"))) ) return convert_foreign (indir, *opts); if (isFile && ( (isExt(indir, ".par")) || (isExt(indir, ".rec"))) ) { char pname[512], rname[512]; strcpy(pname,indir); strcpy(rname,indir); changeExt (pname, "PAR"); changeExt (rname, "REC"); #ifndef _MSC_VER //Linux is case sensitive, #include if( access( rname, F_OK ) != 0 ) changeExt (rname, "rec"); if( access( pname, F_OK ) != 0 ) changeExt (pname, "par"); #endif if (is_fileNotDir(rname) && is_fileNotDir(pname) ) { strcpy(opts->indir, pname); //set to original file name, not path return convert_parRec(*opts); }; } if ((isFile) && (opts->isOnlySingleFile)) return singleDICOM(opts, indir); struct TSearchList nameList; nameList.maxItems = 24000; // larger requires more memory, smaller more passes //1: find filenames of dicom files: up to two passes if we found more files than we allocated memory for (int i = 0; i < 2; i++ ) { nameList.str = (char **) malloc((nameList.maxItems+1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file nameList.numItems = 0; searchDirForDICOM(opts->indir, &nameList, opts->dirSearchDepth, 0, opts); if (nameList.numItems <= nameList.maxItems) break; freeNameList(nameList); nameList.maxItems = nameList.numItems+1; //printMessage("Second pass required, found %ld images\n", nameList.numItems); } if (nameList.numItems < 1) { if (opts->dirSearchDepth > 0) printError("Unable to find any DICOM images in %s (or subfolders %d deep)\n", opts->indir, opts->dirSearchDepth); else //keep silent for dirSearchDepth = 0 - presumably searching multiple folders ; //printError("Unable to find any DICOM images in %s%s\n", opts->indir, str); free(nameList.str); //ignore compile warning - memory only freed on first of 2 passes return kEXIT_NO_VALID_FILES_FOUND; } size_t nDcm = nameList.numItems; printMessage( "Found %lu DICOM file(s)\n", nameList.numItems); //includes images and other non-image DICOMs // struct TDICOMdata dcmList [nameList.numItems]; //<- this exhausts the stack for large arrays struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(nameList.numItems * sizeof(struct TDICOMdata)); struct TDTI4D dti4D; int nConvertTotal = 0; bool compressionWarning = false; bool convertError = false; bool isDcmExt = isExt(opts->filename, ".dcm"); // "%r.dcm" with multi-echo should generate "1.dcm", "1e2.dcm" if (isDcmExt) opts->filename[strlen(opts->filename) - 4] = 0; // "%s_%r.dcm" -> "%s_%r" for (int i = 0; i < (int)nDcm; i++ ) { if ((isExt(nameList.str[i], ".par")) && (isDICOMfile(nameList.str[i]) < 1)) { strcpy(opts->indir, nameList.str[i]); //set to original file name, not path dcmList[i].converted2NII = 1; int ret = convert_parRec(*opts); if (ret == EXIT_SUCCESS) nConvertTotal++; else convertError = true; continue; } dcmList[i] = readDICOMv(nameList.str[i], opts->isVerbose, opts->compressFlag, &dti4D); //ignore compile warning - memory only freed on first of 2 passes //~ if ((dcmList[i].isValid) &&((dcmList[i].totalSlicesIn4DOrder != NULL) ||(dcmList[i].patientPositionNumPhilips > 1) || (dcmList[i].CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately if ((dcmList[i].imageNum > 0) && (opts->isRenameNotConvert > 0)) { //use imageNum instead of isValid to convert non-images (kWaveformSq will have instance number but is not a valid image) char outname[PATH_MAX] = {""}; if (dcmList[i].echoNum > 1) dcmList[i].isMultiEcho = true; //last resort: Siemens gives different echoes the same image number: avoid overwriting, e.g "-f %r.dcm" should generate "1.dcm", "1_e2.dcm" for multi-echo volumes nii_createFilename(dcmList[i], outname, *opts); if (isDcmExt) strcat (outname,".dcm"); int ret = copyFile (nameList.str[i], outname); if (ret != EXIT_SUCCESS) { printError("Unable to rename all DICOM images.\n"); return ret; } if (opts->isVerbose > 0) printMessage("Renaming %s -> %s\n", nameList.str[i], outname); dcmList[i].isValid = false; } if ((dcmList[i].isValid) && ((dti4D.sliceOrder[0] >= 0) || (dcmList[i].CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately struct TDCMsort dcmSort[1]; fillTDCMsort(dcmSort[0], i, dcmList[i]); dcmList[i].converted2NII = 1; int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, &dti4D); if (ret == EXIT_SUCCESS) nConvertTotal++; else convertError = true; } if ((dcmList[i].compressionScheme != kCompressNone) && (!compressionWarning) && (opts->compressFlag != kCompressNone)) { compressionWarning = true; //generate once per conversion rather than once per image printMessage("Image Decompression is new: please validate conversions\n"); } } if (opts->isRenameNotConvert > 0) { return EXIT_SUCCESS; } #ifdef USING_R if (opts->isScanOnly) { TWarnings warnings = setWarnings(); // Create the first series from the first DICOM file TDicomSeries firstSeries; firstSeries.representativeData = dcmList[0]; firstSeries.files.push_back(nameList.str[0]); opts->series.push_back(firstSeries); // Iterate over the remaining files for (size_t i = 1; i < nDcm; i++) { bool matched = false; // If the file matches an existing series, add it to the corresponding file list for (int j = 0; j < opts->series.size(); j++) { bool isMultiEchoUnused, isNonParallelSlices, isCoilVaries; if (isSameSet(opts->series[j].representativeData, dcmList[i], opts, &warnings, &isMultiEchoUnused, &isNonParallelSlices, &isCoilVaries)) { opts->series[j].files.push_back(nameList.str[i]); matched = true; break; } } // If not, create a new series object if (!matched) { TDicomSeries nextSeries; nextSeries.representativeData = dcmList[i]; nextSeries.files.push_back(nameList.str[i]); opts->series.push_back(nextSeries); } } // To avoid a spurious warning below nConvertTotal = nDcm; } else { #endif //3: stack DICOMs with the same Series struct TWarnings warnings = setWarnings(); for (int i = 0; i < (int)nDcm; i++ ) { if ((dcmList[i].converted2NII == 0) && (dcmList[i].isValid)) { int nConvert = 0; bool isMultiEcho = false; bool isNonParallelSlices = false; bool isCoilVaries = false; for (int j = i; j < (int)nDcm; j++) if (isSameSet(dcmList[i], dcmList[j], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries) ) nConvert++; if (nConvert < 1) nConvert = 1; //prevents compiler warning for next line: never executed since j=i always causes nConvert ++ TDCMsort * dcmSort = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); nConvert = 0; for (int j = i; j < (int)nDcm; j++) { isMultiEcho = false; isNonParallelSlices = false; isCoilVaries = false; if (isSameSet(dcmList[i], dcmList[j], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) { dcmList[j].converted2NII = 1; //do not reprocess repeats fillTDCMsort(dcmSort[nConvert], j, dcmList[j]); nConvert++; } else { if (isNonParallelSlices) { dcmList[i].isNonParallelSlices = true; dcmList[j].isNonParallelSlices = true; } if (isMultiEcho) { dcmList[i].isMultiEcho = true; dcmList[j].isMultiEcho = true; } if (isCoilVaries) { dcmList[i].isCoilVaries = true; dcmList[j].isCoilVaries = true; } } //unable to stack images: mark files that may need file name dis-ambiguation } qsort(dcmSort, nConvert, sizeof(struct TDCMsort), compareTDCMsort); //sort based on series and image numbers.... //dcmList[dcmSort[0].indx].isMultiEcho = isMultiEcho; if (opts->isVerbose) nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); else //nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); nConvert = removeDuplicates(nConvert, dcmSort); int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, &dti4D); if (ret == EXIT_SUCCESS) nConvertTotal += nConvert; else convertError = true; free(dcmSort); }//convert all images of this series } #ifdef USING_R } #endif free(dcmList); freeNameList(nameList); if (convertError) return EXIT_FAILURE; //at least one image failed to convert if (nConvertTotal == 0) { printMessage("No valid DICOM images were found\n"); //we may have found valid DICOM files but they are not DICOM images return kEXIT_NO_VALID_FILES_FOUND; } return EXIT_SUCCESS; }// nii_loadDir() /* cleaner than findPigz - perhaps validate someday void findExe(char name[512], const char * argv[]) { if (is_exe(name)) return; //name exists as provided char basename[PATH_MAX]; strcpy(basename, name); //basename = source //check executable folder strcpy(name,argv[0]); dropFilenameFromPath(name); char appendChar[2] = {"a"}; appendChar[0] = kPathSeparator; if (name[strlen(name)-1] != kPathSeparator) strcat (name,appendChar); strcat(name,basename); if (is_exe(name)) return; //name exists as provided //check /opt strcpy (name,"/opt/local/bin/" ); strcat (name, basename); if (is_exe(name)) return; //name exists as provided //check /usr strcpy (name,"/usr/local/bin/" ); strcat (name, basename); if (is_exe(name)) return; strcpy(name,""); //not found! }*/ #if defined(_WIN64) || defined(_WIN32) || defined(USING_R) #else //UNIX, not R int findpathof(char *pth, const char *exe) { //Find executable by searching the PATH environment variable. // http://www.linuxquestions.org/questions/programming-9/get-full-path-of-a-command-in-c-117965/ char *searchpath; char *beg, *end; int stop, found; size_t len; if (strchr(exe, '/') != NULL) { if (realpath(exe, pth) == NULL) return 0; return is_exe(pth); } searchpath = getenv("PATH"); if (searchpath == NULL) return 0; if (strlen(searchpath) <= 0) return 0; beg = searchpath; stop = 0; found = 0; do { end = strchr(beg, ':'); if (end == NULL) { len = strlen(beg); if (len == 0) return 0; //gcc 8.1 warning: specified bound depends on the length of the source argument //https://developers.redhat.com/blog/2018/05/24/detecting-string-truncation-with-gcc-8/ //strncpy(pth, beg, len); strcpy(pth,beg); stop = 1; } else { strncpy(pth, beg, end - beg); pth[end - beg] = '\0'; len = end - beg; } //gcc8.1 warning: specified bound depends on the length of the source argument //if (pth[len - 1] != '/') strncat(pth, "/", 1); if (pth[len - 1] != '/') strcat(pth, "/"); strncat(pth, exe, PATH_MAX - len); found = is_exe(pth); if (!stop) beg = end + 1; } while (!stop && !found); if (!found) strcpy(pth,""); return found; } #endif #ifndef USING_R void readFindPigz (struct TDCMopts *opts, const char * argv[]) { #if defined(_WIN64) || defined(_WIN32) strcpy(opts->pigzname,"pigz.exe"); if (!is_exe(opts->pigzname)) { #if defined(__APPLE__) #ifdef myDisableZLib printMessage("Compression requires %s in the same folder as the executable http://macappstore.org/pigz/\n",opts->pigzname); #else //myUseZLib printMessage("Compression will be faster with %s in the same folder as the executable http://macappstore.org/pigz/\n",opts->pigzname); #endif strcpy(opts->pigzname,""); #else #ifdef myDisableZLib printMessage("Compression requires %s in the same folder as the executable\n",opts->pigzname); #else //myUseZLib printMessage("Compression will be faster with %s in the same folder as the executable\n",opts->pigzname); #endif strcpy(opts->pigzname,""); #endif } else strcpy(opts->pigzname,".\\pigz"); //drop #else char str[PATH_MAX]; //possible pigz names const char * nams[] = { "pigz", "pigz_mricron", "pigz_afni", }; #define n_nam (sizeof (nams) / sizeof (const char *)) for (int n = 0; n < (int)n_nam; n++) { if (findpathof(str, nams[n])) { strcpy(opts->pigzname,str); //printMessage("Found pigz: %s\n", str); return; } } //possible pigz paths const char * pths[] = { "/usr/local/bin/", "/usr/bin/", }; #define n_pth (sizeof (pths) / sizeof (const char *)) char exepth[PATH_MAX]; strcpy(exepth,argv[0]); dropFilenameFromPath(exepth);//, opts.pigzname); char appendChar[2] = {"a"}; appendChar[0] = kPathSeparator; if (exepth[strlen(exepth)-1] != kPathSeparator) strcat (exepth,appendChar); //see if pigz in any path for (int n = 0; n < (int)n_nam; n++) { //printf ("%d: %s\n", i, nams[n]); for (int p = 0; p < (int)n_pth; p++) { strcpy(str, pths[p]); strcat(str, nams[n]); if (is_exe(str)) goto pigzFound; } //p //check exepth strcpy(str, exepth); strcat(str, nams[n]); if (is_exe(str)) goto pigzFound; } //n //Failure: #if defined(__APPLE__) #ifdef myDisableZLib printMessage("Compression requires 'pigz' to be installed http://macappstore.org/pigz/\n"); #else //myUseZLib printMessage("Compression will be faster with 'pigz' installed http://macappstore.org/pigz/\n"); #endif #else //if APPLE else ... #ifdef myDisableZLib printMessage("Compression requires 'pigz' to be installed\n"); #else //myUseZLib printMessage("Compression will be faster with 'pigz' installed\n"); #endif #endif return; pigzFound: //Success strcpy(opts->pigzname,str); //printMessage("Found pigz: %s\n", str); #endif } //readFindPigz() #endif void setDefaultOpts (struct TDCMopts *opts, const char * argv[]) { //either "setDefaultOpts(opts,NULL)" or "setDefaultOpts(opts,argv)" where argv[0] is path to search strcpy(opts->pigzname,""); readFindPigz(opts, argv); #ifdef myEnableJasper opts->compressFlag = kCompressYes; //JASPER for JPEG2000 #else #ifdef myDisableOpenJPEG opts->compressFlag = kCompressNone; //no decompressor #else opts->compressFlag = kCompressYes; //OPENJPEG for JPEG2000 #endif #endif //printMessage("%d %s\n",opts->compressFlag, opts->compressname); strcpy(opts->indir,""); strcpy(opts->outdir,""); strcpy(opts->imageComments,""); opts->isOnlySingleFile = false; //convert all files in a directory, not just a single file opts->isRenameNotConvert = false; opts->isForceStackSameSeries = false; opts->isIgnoreDerivedAnd2D = false; opts->isPhilipsFloatNotDisplayScaling = true; opts->isCrop = false; opts->isGz = false; opts->isSave3D = false; opts->dirSearchDepth = 5; #ifdef myDisableZLib opts->gzLevel = 6; #else opts->gzLevel = MZ_DEFAULT_LEVEL; //-1; #endif opts->isMaximize16BitRange = false; //e.g. if INT16 image has range 0..500 scale to be 0..50000 with hdr.scl_slope = hdr.scl_slope * 0.01 opts->isFlipY = true; //false: images in raw DICOM orientation, true: image rows flipped to cartesian coordinates opts->isRGBplanar = false; //false for NIfTI (RGBRGB...), true for Analyze (RRR..RGGG..GBBB..B) opts->isCreateBIDS = true; opts->isOnlyBIDS = false; opts->isSortDTIbyBVal = false; #ifdef myNoAnonymizeBIDS opts->isAnonymizeBIDS = false; #else opts->isAnonymizeBIDS = true; #endif opts->isCreateText = false; #ifdef myDebug opts->isVerbose = true; #else opts->isVerbose = false; #endif opts->isTiltCorrect = true; opts->numSeries = 0; memset(opts->seriesNumber, 0, sizeof(opts->seriesNumber)); strcpy(opts->filename,"%f_%p_%t_%s"); } // setDefaultOpts() #if defined(_WIN64) || defined(_WIN32) //windows has unusual file permissions for many users - lets save preferences to the registry void saveIniFile (struct TDCMopts opts) { HKEY hKey; if(RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\dcm2nii",0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS) { RegCloseKey(hKey); return; } printMessage("Saving defaults to registry\n"); DWORD dwValue = opts.isGz; RegSetValueExA(hKey, "isGZ", 0, REG_DWORD, reinterpret_cast(&dwValue), sizeof(dwValue)); dwValue = opts.isMaximize16BitRange; RegSetValueExA(hKey, "isMaximize16BitRange", 0, REG_DWORD, reinterpret_cast(&dwValue), sizeof(dwValue)); RegSetValueExA(hKey,"filename",0, REG_SZ,(LPBYTE)opts.filename, strlen(opts.filename)+1); RegCloseKey(hKey); } //saveIniFile() void readIniFile (struct TDCMopts *opts, const char * argv[]) { setDefaultOpts(opts, argv); HKEY hKey; DWORD vSize = 0; DWORD dwDataType = 0; DWORD dwValue = 0; //RegOpenKeyEx(RegOpenKeyEx, key, 0, accessRights, keyHandle); //if(RegOpenKeyEx(HKEY_CURRENT_USER,(WCHAR)"Software\\dcm2nii", 0, KEY_QUERY_VALUE,&hKey) != ERROR_SUCCESS) { if(RegOpenKeyExA(HKEY_CURRENT_USER,"Software\\dcm2nii", 0, KEY_QUERY_VALUE,&hKey) != ERROR_SUCCESS) { RegCloseKey(hKey); return; } vSize = sizeof(dwValue); //if(RegQueryValueExA(hKey,"isGZ", 0, (LPDWORD )&dwDataType, (&dwValue), &vSize) == ERROR_SUCCESS) if(RegQueryValueExA(hKey,"isGZ", 0, (LPDWORD )&dwDataType, reinterpret_cast(&dwValue), &vSize) == ERROR_SUCCESS) opts->isGz = dwValue; if(RegQueryValueExA(hKey,"isMaximize16BitRange", 0, (LPDWORD )&dwDataType, reinterpret_cast(&dwValue), &vSize) == ERROR_SUCCESS) opts->isMaximize16BitRange = dwValue; vSize = 512; char buffer[512]; if(RegQueryValueExA(hKey,"filename", 0,NULL,(LPBYTE)buffer,&vSize ) == ERROR_SUCCESS ) strcpy(opts->filename,buffer); RegCloseKey(hKey); } //readIniFile() #else //for Unix we will save preferences in a hidden text file in the home directory #define STATUSFILENAME "/.dcm2nii.ini" void readIniFile (struct TDCMopts *opts, const char * argv[]) { setDefaultOpts(opts, argv); sprintf(opts->optsname, "%s%s", getenv("HOME"), STATUSFILENAME); FILE *fp = fopen(opts->optsname, "r"); if (fp == NULL) return; char Setting[20],Value[255]; //while ( fscanf(fp, "%[^=]=%s\n", Setting, Value) == 2 ) { //while ( fscanf(fp, "%[^=]=%s\n", Setting, Value) == 2 ) { while ( fscanf(fp, "%[^=]=%[^\n]\n", Setting, Value) == 2 ) { //printMessage(">%s<->'%s'\n",Setting,Value); if ( strcmp(Setting,"isGZ") == 0 ) opts->isGz = atoi(Value); if ( strcmp(Setting,"isMaximize16BitRange") == 0 ) opts->isMaximize16BitRange = atoi(Value); else if ( strcmp(Setting,"isBIDS") == 0 ) opts->isCreateBIDS = atoi(Value); else if ( strcmp(Setting,"filename") == 0 ) strcpy(opts->filename,Value); } fclose(fp); }// readIniFile() void saveIniFile (struct TDCMopts opts) { FILE *fp = fopen(opts.optsname, "w"); //printMessage("%s\n",localfilename); if (fp == NULL) return; printMessage("Saving defaults file %s\n", opts.optsname); fprintf(fp, "isGZ=%d\n", opts.isGz); fprintf(fp, "isMaximize16BitRange=%d\n", opts.isMaximize16BitRange); fprintf(fp, "isBIDS=%d\n", opts.isCreateBIDS); fprintf(fp, "filename=%s\n", opts.filename); fclose(fp); } //saveIniFile() #endif dcm2niix-1.0.20181125/console/nii_dicom_batch.h000066400000000000000000000041601337661136700207150ustar00rootroot00000000000000// // #ifndef MRIpro_nii_batch_h #define MRIpro_nii_batch_h #ifdef __cplusplus extern "C" { #endif #include //requires VS 2015 or later #include #ifndef USING_R #include "nifti1.h" #endif #include "nii_dicom.h" #ifdef USING_R struct TDicomSeries { TDICOMdata representativeData; std::vector files; }; #endif #define MAX_NUM_SERIES 16 struct TDCMopts { bool isRenameNotConvert, isMaximize16BitRange, isSave3D,isGz, isFlipY, isCreateBIDS, isSortDTIbyBVal, isAnonymizeBIDS, isOnlyBIDS, isCreateText, isIgnoreDerivedAnd2D, isPhilipsFloatNotDisplayScaling, isTiltCorrect, isRGBplanar, isOnlySingleFile, isForceStackSameSeries, isCrop; int isVerbose, compressFlag, dirSearchDepth, gzLevel; //support for compressed data 0=none, char filename[512], outdir[512], indir[512], pigzname[512], optsname[512], indirParent[512], imageComments[24]; float seriesNumber[MAX_NUM_SERIES]; long numSeries; #ifdef USING_R bool isScanOnly; void *imageList; std::vector series; #endif }; void saveIniFile (struct TDCMopts opts); void setDefaultOpts (struct TDCMopts *opts, const char * argv[]); //either "setDefaultOpts(opts,NULL)" or "setDefaultOpts(opts,argv)" where argv[0] is path to search void readIniFile (struct TDCMopts *opts, const char * argv[]); int nii_saveNII(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts); //void readIniFile (struct TDCMopts *opts); int nii_loadDir(struct TDCMopts *opts); //void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct TDTI4D *dti4D, struct nifti_1_header *h, const char * filename); void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename); int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopts opts); void nii_createDummyFilename(char * niiFilename, struct TDCMopts opts); //void findExe(char name[512], const char * argv[]); #ifdef __cplusplus } #endif #endif dcm2niix-1.0.20181125/console/nii_foreign.cpp000066400000000000000000000425451337661136700204560ustar00rootroot00000000000000#include "nii_foreign.h" #include "nii_dicom.h" #include "nifti1_io_core.h" #include "nii_dicom_batch.h" #include "nifti1.h" //#include "nifti1_io_core.h" #include #include #include #include #include //requires VS 2015 or later #include #include "print.h" #ifdef _MSC_VER #include #define getcwd _getcwd #define chdir _chrdir #include "io.h" #include //#define snprintMessage _snprintMessage //#define vsnprintMessage _vsnprintMessage #define strcasecmp _stricmp #define strncasecmp _strnicmp #else #include #endif #ifndef Float32 #define Float32 float #endif #ifndef uint32 #define uint32 uint32_t #endif #ifdef __GNUC__ #define PACK(...) __VA_ARGS__ __attribute__((__packed__)) #else #define PACK(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop)) #endif /*nii_readEcat7 2017 by Chris Rorden, BSD license http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7_8h_source.html#l00060 http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7r_8c_source.html#l00717 http://xmedcon.sourcearchive.com/documentation/0.10.7-1build1/ecat7_8h_source.html https://github.com/BIC-MNI/minc-tools/tree/master/conversion/ecattominc https://github.com/nipy/nibabel/blob/ec4567fb09b4472c5a4bb9a13dbcc9eb0a63d875/nibabel/ecat.py */ void strClean(char * cString) { int len = (int)strlen(cString); if (len < 1) return; for (int i = 0; i < len; i++) { char c = cString[i]; if ( (c < char(32)) || (c == char(127)) || (c == char(255)) ) cString[i] = 0; if ( (c==' ') || (c==',') || (c=='^') || (c=='/') || (c=='\\') || (c=='%') || (c=='*')) cString[i] = '_'; } } unsigned char * readEcat7(const char *fname, struct TDICOMdata *dcm, struct nifti_1_header *hdr, struct TDCMopts opts, bool isWarnIfNotEcat) { //data type #define ECAT7_BYTE 1 #define ECAT7_VAXI2 2 #define ECAT7_VAXI4 3 #define ECAT7_VAXR4 4 #define ECAT7_IEEER4 5 #define ECAT7_SUNI2 6 #define ECAT7_SUNI4 7 //file types //#define ECAT7_UNKNOWN 0 #define ECAT7_2DSCAN 1 #define ECAT7_IMAGE16 2 #define ECAT7_ATTEN 3 #define ECAT7_2DNORM 4 #define ECAT7_POLARMAP 5 #define ECAT7_VOLUME8 6 #define ECAT7_VOLUME16 7 #define ECAT7_PROJ 8 #define ECAT7_PROJ16 9 #define ECAT7_IMAGE8 10 #define ECAT7_3DSCAN 11 #define ECAT7_3DSCAN8 12 #define ECAT7_3DNORM 13 #define ECAT7_3DSCANFIT 14 PACK( typedef struct { char magic[14],original_filename[32]; uint16_t sw_version, system_type, file_type; char serial_number[10]; uint32 scan_start_time; char isotope_name[8]; Float32 isotope_halflife; char radiopharmaceutical[32]; Float32 gantry_tilt, gantry_rotation, bed_elevation, intrinsic_tilt; int16_t wobble_speed, transm_source_type; Float32 distance_scanned, transaxial_fov; uint16_t angular_compression, coin_samp_mode, axial_samp_mode; Float32 ecat_calibration_factor; uint16_t calibration_unitS, calibration_units_type, compression_code; char study_type[12], patient_id[16], patient_name[32], patient_sex, patient_dexterity; Float32 patient_age, patient_height, patient_weight; uint32 patient_birth_date; char physician_name[32], operator_name[32], study_description[32]; uint16_t acquisition_type, patient_orientation; char facility_name[20]; uint16_t num_planes, num_frames, num_gates, num_bed_pos; Float32 init_bed_position; Float32 bed_position[15]; Float32 plane_separation; uint16_t lwr_sctr_thres, lwr_true_thres, upr_true_thres; char user_process_code[10]; uint16_t acquisition_mode; Float32 bin_size, branching_fraction; uint32 dose_start_time; Float32 dosage, well_counter_corr_factor; char data_units[32]; uint16_t septa_state; char fill[12]; }) ecat_main_hdr; PACK( typedef struct { int16_t data_type, num_dimensions, x_dimension, y_dimension, z_dimension; Float32 x_offset, y_offset, z_offset, recon_zoom, scale_factor; int16_t image_min, image_max; Float32 x_pixel_size, y_pixel_size, z_pixel_size; int32_t frame_duration, frame_start_time; int16_t filter_code; Float32 x_resolution, y_resolution, z_resolution, num_r_elements, num_angles, z_rotation_angle, decay_corr_fctr; int32_t processing_code, gate_duration, r_wave_offset, num_accepted_beats; Float32 filter_cutoff_frequenc, filter_resolution, filter_ramp_slope; int16_t filter_order; Float32 filter_scatter_fraction, filter_scatter_slope; char annotation[40]; Float32 mtx[9], rfilter_cutoff, rfilter_resolution; int16_t rfilter_code, rfilter_order; Float32 zfilter_cutoff, zfilter_resolution; int16_t zfilter_code, zfilter_order; Float32 mtx_1_4, mtx_2_4, mtx_3_4; int16_t scatter_type, recon_type, recon_views, fill_cti[87], fill_user[49]; }) ecat_img_hdr; PACK( typedef struct { int32_t hdr[4], r[31][4]; }) ecat_list_hdr; bool swapEndian = false; size_t n; FILE *f; ecat_main_hdr mhdr; f = fopen(fname, "rb"); if (f) n = fread(&mhdr, sizeof(mhdr), 1, f); if(!f || n!=1) { printMessage("Problem reading ECAT7 file!\n"); fclose(f); return NULL; } if ((mhdr.magic[0] != 'M') || (mhdr.magic[1] != 'A') || (mhdr.magic[2] != 'T') || (mhdr.magic[3] != 'R') || (mhdr.magic[4] != 'I') || (mhdr.magic[5] != 'X') ) { if (isWarnIfNotEcat) printMessage("Signature not 'MATRIX' (ECAT7): '%s'\n", fname); fclose(f); return NULL; } swapEndian = mhdr.file_type > 255; if (swapEndian) { nifti_swap_2bytes(2, &mhdr.sw_version); nifti_swap_2bytes(1, &mhdr.file_type); //nifti_swap_2bytes(1, &mhdr.num_frames); nifti_swap_4bytes(1, &mhdr.ecat_calibration_factor); nifti_swap_4bytes(1, &mhdr.isotope_halflife); nifti_swap_4bytes(2, &mhdr.dosage); } if ((mhdr.file_type < ECAT7_2DSCAN) || (mhdr.file_type > ECAT7_3DSCANFIT)) { printMessage("Unknown ECAT file type %d\n", mhdr.file_type); fclose(f); return NULL; } //read list matrix ecat_list_hdr lhdr; fseek(f, 512, SEEK_SET); size_t nRead = fread(&lhdr, sizeof(lhdr), 1, f); if (nRead != 1) { printMessage("Error reading ECAT file (list header)\n"); fclose(f); return NULL; } if (swapEndian) nifti_swap_4bytes(128, &lhdr.hdr[0]); //offset to first image int img_StartBytes = lhdr.r[0][1] * 512; //load image header for first image fseek(f, img_StartBytes - 512, SEEK_SET); //image header is block immediately before image ecat_img_hdr ihdr; nRead = fread(&ihdr, sizeof(ihdr), 1, f); if (nRead != 1) { printMessage("Error reading ECAT file (image header)\n"); fclose(f); return NULL; } if (swapEndian) { nifti_swap_2bytes(5, &ihdr.data_type); nifti_swap_4bytes(5, &ihdr.x_offset); nifti_swap_2bytes(2, &ihdr.image_min); nifti_swap_4bytes(5, &ihdr.x_pixel_size); nifti_swap_2bytes(1, &ihdr.filter_code); nifti_swap_4bytes(14, &ihdr.x_resolution); nifti_swap_2bytes(1, &ihdr.filter_order); nifti_swap_4bytes(2, &ihdr.filter_scatter_fraction); nifti_swap_4bytes(11, &ihdr.mtx); nifti_swap_2bytes(2, &ihdr.rfilter_code); nifti_swap_4bytes(2, &ihdr.zfilter_cutoff); nifti_swap_2bytes(2, &ihdr.zfilter_code); nifti_swap_4bytes(3, &ihdr.mtx_1_4); nifti_swap_2bytes(3, &ihdr.scatter_type); } if ((ihdr.data_type != ECAT7_BYTE) && (ihdr.data_type != ECAT7_SUNI2) && (ihdr.data_type != ECAT7_SUNI4)) { printMessage("Unknown or unsupported ECAT data type %d\n", ihdr.data_type); fclose(f); return NULL; } int bytesPerVoxel = 2; if (ihdr.data_type == ECAT7_BYTE) bytesPerVoxel = 1; if (ihdr.data_type == ECAT7_SUNI4) bytesPerVoxel = 4; //next: read offsets for each volume: data not saved sequentially (each volume preceded by its own ecat_img_hdr) int num_vol = 0; bool isAbort = false; bool isScaleFactorVaries = false; #define kMaxVols 16000 size_t * imgOffsets = (size_t *)malloc(sizeof(size_t) * (kMaxVols)); float * imgSlopes = (float *)malloc(sizeof(float) * (kMaxVols)); ecat_img_hdr ihdrN; while ((lhdr.hdr[0]+lhdr.hdr[3]) == 31) { //while valid list if (num_vol > 0) { //read the next list fseek(f, 512 * (lhdr.hdr[1] -1), SEEK_SET); nRead =fread(&lhdr, 512, 1, f); if (nRead != 1) { printMessage("Error reading ECAT file (yet another list header)\n"); fclose(f); return NULL; } if (swapEndian) nifti_swap_4bytes(128, &lhdr.hdr[0]); } if ((lhdr.hdr[0]+lhdr.hdr[3]) != 31) break; //if valid list if (lhdr.hdr[3] < 1) break; for (int k = 0; k < lhdr.hdr[3]; k++) { //check images' ecat_img_hdr matches first fseek(f, (lhdr.r[k][1]-1) * 512, SEEK_SET); //image header is block immediately before image nRead = fread(&ihdrN, sizeof(ihdrN), 1, f); if (nRead != 1) { printMessage("Error reading ECAT file (yet another image header)\n"); fclose(f); return NULL; } if (swapEndian) { nifti_swap_2bytes(5, &ihdrN.data_type); nifti_swap_4bytes(5, &ihdrN.x_offset); nifti_swap_2bytes(2, &ihdrN.image_min); nifti_swap_4bytes(5, &ihdrN.x_pixel_size); nifti_swap_2bytes(1, &ihdrN.filter_code); nifti_swap_4bytes(14, &ihdrN.x_resolution); nifti_swap_2bytes(1, &ihdrN.filter_order); nifti_swap_4bytes(2, &ihdrN.filter_scatter_fraction); nifti_swap_4bytes(11, &ihdrN.mtx); nifti_swap_2bytes(2, &ihdrN.rfilter_code); nifti_swap_4bytes(2, &ihdrN.zfilter_cutoff); nifti_swap_2bytes(2, &ihdrN.zfilter_code); nifti_swap_4bytes(3, &ihdrN.mtx_1_4); nifti_swap_2bytes(3, &ihdrN.scatter_type); } if (ihdr.scale_factor != ihdrN.scale_factor) isScaleFactorVaries = true; if ((ihdr.data_type != ihdrN.data_type) || (ihdr.x_dimension != ihdrN.x_dimension) || (ihdr.y_dimension != ihdrN.y_dimension) || (ihdr.z_dimension != ihdrN.z_dimension)) { printError("Error: ECAT volumes have varying image dimensions\n"); isAbort = true; } if (num_vol < kMaxVols) { imgOffsets[num_vol] = lhdr.r[k][1]; imgSlopes[num_vol] = ihdrN.scale_factor; } num_vol ++; } if ((lhdr.hdr[0] > 0) || (isAbort)) break; //this list contains empty volumes: all lists have been read } //read all image offsets //report error reading image offsets if ((num_vol < 1) || (isAbort) || (num_vol >= kMaxVols)) { printMessage("Failure to extract ECAT7 images\n"); if (num_vol >= kMaxVols) printMessage("Increase kMaxVols"); fclose(f); free (imgOffsets); free(imgSlopes); return NULL; } if ((isScaleFactorVaries) && (bytesPerVoxel != 2)) { printError("ECAT scale factor varies between volumes (check for updates) '%s'\n", fname); fclose(f); free (imgOffsets); free(imgSlopes); return NULL; } //load image data unsigned char * img = NULL; if ((isScaleFactorVaries) && (bytesPerVoxel == 2)) { //we need to convert volumes from 16-bit to 32-bit to preserve scaling factors int num_vox = ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension; size_t bytesPerVolumeIn = num_vox * bytesPerVoxel; //bytesPerVoxel == 2 unsigned char * imgIn = (unsigned char*)malloc(bytesPerVolumeIn); int16_t * img16i = (int16_t*) imgIn; bytesPerVoxel = 4; size_t bytesPerVolume = num_vox * bytesPerVoxel; img = (unsigned char*)malloc(bytesPerVolume * num_vol); float * img32 = (float*) img; for (int v = 0; v < num_vol; v++) { fseek(f, imgOffsets[v] * 512, SEEK_SET); nRead = fread( &imgIn[0], 1, bytesPerVolumeIn, f); if (nRead != bytesPerVolumeIn) { printMessage("%zu Error reading ECAT file (offset %zu bytes %zu)\n", nRead, imgOffsets[v] * 512, bytesPerVolumeIn); fclose(f); return NULL; } if (swapEndian) nifti_swap_2bytes(num_vox, imgIn); int volOffset = v * num_vox; float scale = imgSlopes[v] * mhdr.ecat_calibration_factor; for (int i = 0; i < num_vox; i++) img32[i+volOffset] = (img16i[i] * scale); } //we have applied the scale factors to the data, so eliminate them ihdr.scale_factor = 1.0; mhdr.ecat_calibration_factor = 1.0; } else { //if isScaleFactorVaries else simple conversion size_t bytesPerVolume = ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * bytesPerVoxel; img = (unsigned char*)malloc(bytesPerVolume * num_vol); for (int v = 0; v < num_vol; v++) { fseek(f, imgOffsets[v] * 512, SEEK_SET); size_t sz = fread( &img[v * bytesPerVolume], 1, bytesPerVolume, f); if (sz != bytesPerVolume) { free(img); return NULL; } } if ((swapEndian) && (bytesPerVoxel == 2)) nifti_swap_2bytes(ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * num_vol, img); if ((swapEndian) && (bytesPerVoxel == 4)) nifti_swap_4bytes(ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * num_vol, img); } printWarning("ECAT support VERY experimental (Spatial transforms unknown)\n"); free (imgOffsets); free(imgSlopes); fclose(f); //fill DICOM header float timeBetweenVolumes = ihdr.frame_duration; if (num_vol > 1) timeBetweenVolumes = (float)(ihdrN.frame_start_time- ihdr.frame_start_time)/(float)(num_vol-1); //copy and clean strings (ECAT can use 0x0D as a string terminator) strncpy(dcm->patientName, mhdr.patient_name, 32); strncpy(dcm->patientID, mhdr.patient_id, 16); strncpy(dcm->seriesDescription, mhdr.study_description, 32); strncpy(dcm->protocolName, mhdr.study_type, 12); strncpy(dcm->imageComments, mhdr.isotope_name, 8); strncpy(dcm->procedureStepDescription, mhdr.radiopharmaceutical, 32); strClean(dcm->patientName); strClean(dcm->patientID); strClean(dcm->seriesDescription); strClean(dcm->protocolName); strClean(dcm->imageComments); strClean(dcm->procedureStepDescription); dcm->ecat_dosage = mhdr.dosage; dcm->ecat_isotope_halflife = mhdr.isotope_halflife; if (opts.isVerbose) { printMessage("ECAT7 details for '%s'\n", fname); printMessage(" Software version %d\n", mhdr.sw_version); printMessage(" System Type %d\n", mhdr.system_type); printMessage(" Frame duration %dms\n", ihdr.frame_duration); printMessage(" Time between volumes %gms\n", timeBetweenVolumes ); printMessage(" Patient name '%s'\n", dcm->patientName); printMessage(" Patient ID '%s'\n", dcm->patientID); printMessage(" Study description '%s'\n", dcm->seriesDescription); printMessage(" Study type '%s'\n", dcm->protocolName); printMessage(" Isotope name '%s'\n", dcm->imageComments); printMessage(" Isotope halflife %gs\n", mhdr.isotope_halflife); printMessage(" Radiopharmaceutical '%s'\n", dcm->procedureStepDescription); printMessage(" Dosage %gbequerels/cc\n", mhdr.dosage); if (!isScaleFactorVaries) { printMessage(" Scale factor %12.12g\n", ihdr.scale_factor); printMessage(" ECAT calibration factor %8.12g\n", mhdr.ecat_calibration_factor); } printMessage(" NIfTI scale slope %12.12g\n",ihdr.scale_factor * mhdr.ecat_calibration_factor); } dcm->manufacturer = kMANUFACTURER_SIEMENS; //dcm->manufacturersModelName = itoa(mhdr.system_type); sprintf(dcm->manufacturersModelName, "%d", mhdr.system_type); dcm->bitsAllocated = bytesPerVoxel * 8; if (isScaleFactorVaries) dcm->isFloat = true; dcm->bitsStored = 15; //ensures 16-bit images saved as INT16 not UINT16 dcm->samplesPerPixel = 1; dcm->xyzMM[1] = ihdr.x_pixel_size * 10.0; //cm -> mm dcm->xyzMM[2] = ihdr.y_pixel_size * 10.0; //cm -> mm dcm->xyzMM[3] = ihdr.z_pixel_size * 10.0; //cm -> mm dcm->TR = timeBetweenVolumes; dcm->xyzDim[1] = ihdr.x_dimension; dcm->xyzDim[2] = ihdr.y_dimension; dcm->xyzDim[3] = ihdr.z_dimension; dcm->xyzDim[4] = num_vol; //create a NIfTI header headerDcm2Nii(*dcm, hdr, false); //here we mimic SPM's spatial starting estimate SForm mat44 m44; LOAD_MAT44(m44, -hdr->pixdim[1], 0.0f, 0.0f, ((float)dcm->xyzDim[1]-2.0)/2.0*dcm->xyzMM[1], 0.0f, -hdr->pixdim[2], 0.0f, ((float)dcm->xyzDim[2]-2.0)/2.0*dcm->xyzMM[2], 0.0f, 0.0f, -hdr->pixdim[3], ((float)dcm->xyzDim[3]-2.0)/2.0*dcm->xyzMM[3]); setQSForm(hdr, m44, false); //make sure image does not include a spatial matrix bool isMatrix = false; for (int i = 0; i < 9; i++) if (ihdr.mtx[i] != 0.0) isMatrix = true; if (isMatrix) printWarning("ECAT volume appears to store spatial transformation matrix (please check for updates)\n"); hdr->scl_slope = ihdr.scale_factor * mhdr.ecat_calibration_factor; if (mhdr.gantry_tilt != 0.0) printMessage("Warning: ECAT gantry tilt not supported %g\n", mhdr.gantry_tilt); return img; } int convert_foreign (const char *fn, struct TDCMopts opts){ struct nifti_1_header hdr; struct TDICOMdata dcm = clear_dicom_data(); unsigned char * img = NULL; img = readEcat7(fn, &dcm, &hdr, opts, false); //false: silent, do not report if file is not ECAT format if (!img) return EXIT_FAILURE; char niiFilename[1024]; int ret = nii_createFilename(dcm, niiFilename, opts); printMessage("Saving ECAT as '%s'\n", niiFilename); if (ret != EXIT_SUCCESS) return ret; //struct TDTI4D dti4D; //nii_SaveBIDS(niiFilename, dcm, opts, &dti4D, &hdr, fn); nii_SaveBIDS(niiFilename, dcm, opts, &hdr, fn); ret = nii_saveNII(niiFilename, hdr, img, opts); free(img); return ret; }// convert_foreign() dcm2niix-1.0.20181125/console/nii_foreign.h000066400000000000000000000004371337661136700201150ustar00rootroot00000000000000//Attempt to open non-DICOM image #ifndef _NII_FOREIGN_ #define _NII_FOREIGN_ #ifdef __cplusplus extern "C" { #endif #include "nii_dicom_batch.h" //int open_foreign (const char *fn); int convert_foreign (const char *fn, struct TDCMopts opts); #ifdef __cplusplus } #endif #endifdcm2niix-1.0.20181125/console/nii_ortho.cpp000066400000000000000000000313711337661136700201530ustar00rootroot00000000000000#ifndef USING_R #include "nifti1.h" #endif #include "nifti1_io_core.h" #include "nii_ortho.h" #include #include #include #include #include //requires VS 2015 or later #include #include #include #include //#include #include #ifndef _MSC_VER #include #endif //#define MY_DEBUG //verbose text reporting #include "print.h" typedef struct { int v[3]; } vec3i; mat33 matDotMul33 (mat33 a, mat33 b) // in Matlab: ret = a'.*b { mat33 ret; for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { ret.m[i][j] = a.m[i][j]*b.m[j][i]; } } return ret; } mat33 matMul33 (mat33 a, mat33 b) // mult = a * b { mat33 mult; for(int i=0;i<3;i++) { for(int j=0;j<3;j++) { mult.m[j][i]=0; for(int k=0;k<3;k++) mult.m[j][i]+=a.m[j][k]*b.m[k][i]; } } return mult; } float getOrthoResidual (mat33 orig, mat33 transform) { mat33 mat = matDotMul33(orig, transform); float ret = 0; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { ret = ret + (mat.m[i][j]); } } return ret; } mat33 getBestOrient(mat44 R, vec3i flipVec) //flipVec reports flip: [1 1 1]=no flips, [-1 1 1] flip X dimension { mat33 ret, newmat, orig; LOAD_MAT33(orig,R.m[0][0],R.m[0][1],R.m[0][2], R.m[1][0],R.m[1][1],R.m[1][2], R.m[2][0],R.m[2][1],R.m[2][2]); float best = 0;//FLT_MAX; float newval; for (int rot = 0; rot < 6; rot++) { //6 rotations switch (rot) { case 0: LOAD_MAT33(newmat,flipVec.v[0],0,0, 0,flipVec.v[1],0, 0,0,flipVec.v[2]); break; case 1: LOAD_MAT33(newmat,flipVec.v[0],0,0, 0,0,flipVec.v[1], 0,flipVec.v[2],0); break; case 2: LOAD_MAT33(newmat,0,flipVec.v[0],0, flipVec.v[1],0,0, 0,0,flipVec.v[2]); break; case 3: LOAD_MAT33(newmat,0,flipVec.v[0],0, 0,0,flipVec.v[1], flipVec.v[2],0,0); break; case 4: LOAD_MAT33(newmat,0,0,flipVec.v[0], flipVec.v[1],0,0, 0,flipVec.v[2],0); break; case 5: LOAD_MAT33(newmat,0,0,flipVec.v[0], 0,flipVec.v[1],0, flipVec.v[2],0,0); break; } newval = getOrthoResidual(orig, newmat); if (newval > best) { best = newval; ret = newmat; } } return ret; } bool isMat44Canonical(mat44 R) //returns true if diagonals >0 and all others =0 // no rotation is necessary - already in perfect orthogonal alignment { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if ((i == j) && (R.m[i][j] <= 0) ) return false; if ((i != j) && (R.m[i][j] != 0) ) return false; }//j }//i return true; } vec3i setOrientVec(mat33 m) // Assumes isOrthoMat NOT computed on INVERSE, hence return INVERSE of solution... //e.g. [-1,2,3] means reflect x axis, [2,1,3] means swap x and y dimensions { vec3i ret = {{0, 0, 0}}; //mat33 m = {-1,0,0, 0,1,0, 0,0,1}; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (m.m[i][j] > 0) ret.v[j] = i+1; if (m.m[i][j] < 0) ret.v[j] = -(i+1); }//j }//i return ret; } mat44 setMat44Vec(mat33 m33, vec3 Translations) //convert a 3x3 rotation matrix to a 4x4 matrix where the last column stores translations and the last row is 0 0 0 1 { mat44 m44; for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { m44.m[i][j] = m33.m[i][j]; } } m44.m[0][3] = Translations.v[0]; m44.m[1][3] = Translations.v[1]; m44.m[2][3] = Translations.v[2]; m44.m[3][0] = 0; m44.m[3][1] = 0; m44.m[3][2] = 0; m44.m[3][3] = 1; return m44; } mat44 sFormMat(struct nifti_1_header *h) { mat44 s; s.m[0][0]=h->srow_x[0]; s.m[0][1]=h->srow_x[1]; s.m[0][2]=h->srow_x[2]; s.m[0][3]=h->srow_x[3]; s.m[1][0]=h->srow_y[0]; s.m[1][1]=h->srow_y[1]; s.m[1][2]=h->srow_y[2]; s.m[1][3]=h->srow_y[3]; s.m[2][0]=h->srow_z[0]; s.m[2][1]=h->srow_z[1]; s.m[2][2]=h->srow_z[2]; s.m[2][3]=h->srow_z[3]; s.m[3][0] = 0 ; s.m[3][1] = 0 ; s.m[3][2] = 0 ; s.m[3][3] = 1 ; return s; } void mat2sForm (struct nifti_1_header *h, mat44 s) { h->srow_x[0] = s.m[0][0]; h->srow_x[1] = s.m[0][1]; h->srow_x[2] = s.m[0][2]; h->srow_x[3] = s.m[0][3]; h->srow_y[0] = s.m[1][0]; h->srow_y[1] = s.m[1][1]; h->srow_y[2] = s.m[1][2]; h->srow_y[3] = s.m[1][3]; h->srow_z[0] = s.m[2][0]; h->srow_z[1] = s.m[2][1]; h->srow_z[2] = s.m[2][2]; h->srow_z[3] = s.m[2][3]; } size_t* orthoOffsetArray(int dim, int stepBytesPerVox) { //return lookup table of length dim with values incremented by stepBytesPerVox // e.g. if Dim=10 and stepBytes=2: 0,2,4..18, is stepBytes=-2 18,16,14...0 size_t *lut= (size_t *)malloc(dim*sizeof(size_t)); if (stepBytesPerVox > 0) lut[0] = 0; else lut[0] = -stepBytesPerVox *(dim-1); if (dim > 1) for (int i=1; i < dim; i++) lut[i] = lut[i-1] + (size_t)stepBytesPerVox; return lut; } //orthoOffsetArray() //void reOrientImg( unsigned char * restrict img, vec3i outDim, vec3i outInc, int bytePerVox, int nvol) { void reOrientImg( unsigned char * img, vec3i outDim, vec3i outInc, int bytePerVox, int nvol) { //reslice data to new orientation //generate look up tables size_t* xLUT =orthoOffsetArray(outDim.v[0], bytePerVox*outInc.v[0]); size_t* yLUT =orthoOffsetArray(outDim.v[1], bytePerVox*outInc.v[1]); size_t* zLUT =orthoOffsetArray(outDim.v[2], bytePerVox*outInc.v[2]); //convert data size_t bytePerVol = bytePerVox*outDim.v[0]*outDim.v[1]*outDim.v[2]; //number of voxels in spatial dimensions [1,2,3] size_t o = 0; //output address uint8_t *inbuf = (uint8_t *) malloc(bytePerVol); //we convert 1 volume at a time uint8_t *outbuf = (uint8_t *) img; //source image for (int vol= 0; vol < nvol; vol++) { memcpy(&inbuf[0], &outbuf[vol*bytePerVol], bytePerVol); //copy source volume for (int z = 0; z < outDim.v[2]; z++) for (int y = 0; y < outDim.v[1]; y++) for (int x = 0; x < outDim.v[0]; x++) { memcpy(&outbuf[o], &inbuf[xLUT[x]+yLUT[y]+zLUT[z]], bytePerVox); o = o+ bytePerVox; } //for each x } //for each volume //free arrays free(inbuf); free(xLUT); free(yLUT); free(zLUT); } //reOrientImg unsigned char * reOrient(unsigned char* img, struct nifti_1_header *h, vec3i orientVec, mat33 orient, vec3 minMM) //e.g. [-1,2,3] means reflect x axis, [2,1,3] means swap x and y dimensions { size_t nvox = h->dim[1] * h->dim[2] * h->dim[3]; if (nvox < 1) return img; vec3i outDim= {{0,0,0}}; vec3i outInc= {{0,0,0}}; for (int i = 0; i < 3; i++) { //set dimension, pixdim and outDim.v[i] = h->dim[abs(orientVec.v[i])]; if (abs(orientVec.v[i]) == 1) outInc.v[i] = 1; if (abs(orientVec.v[i]) == 2) outInc.v[i] = h->dim[1]; if (abs(orientVec.v[i]) == 3) outInc.v[i] = h->dim[1]*h->dim[2]; if (orientVec.v[i] < 0) outInc.v[i] = -outInc.v[i]; //flip } //for each dimension int nvol = 1; //convert all non-spatial volumes from source to destination for (int vol = 4; vol < 8; vol++) { if (h->dim[vol] > 1) nvol = nvol * h->dim[vol]; } reOrientImg(img, outDim, outInc, h->bitpix / 8, nvol); //now change the header.... vec3 outPix= {{h->pixdim[abs(orientVec.v[0])],h->pixdim[abs(orientVec.v[1])],h->pixdim[abs(orientVec.v[2])]}}; for (int i = 0; i < 3; i++) { h->dim[i+1] = outDim.v[i]; h->pixdim[i+1] = outPix.v[i]; } mat44 s = sFormMat(h); mat33 mat; //computer transform LOAD_MAT33(mat, s.m[0][0],s.m[0][1],s.m[0][2], s.m[1][0],s.m[1][1],s.m[1][2], s.m[2][0],s.m[2][1],s.m[2][2]); mat = matMul33( mat, orient); s = setMat44Vec(mat, minMM); //add offset mat2sForm(h,s); h->qform_code = h->sform_code; //apply to the quaternion as well float dumdx, dumdy, dumdz; nifti_mat44_to_quatern( s , &h->quatern_b, &h->quatern_c, &h->quatern_d,&h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz,&h->pixdim[0]) ; return img; } //reOrient() float getDistance (vec3 v, vec3 min) //scalar distance between two 3D points - Pythagorean theorem { return sqrt(pow((v.v[0]-min.v[0]),2)+pow((v.v[1]-min.v[1]),2)+pow((v.v[2]-min.v[2]),2) ); } vec3 xyz2mm (mat44 R, vec3 v) { vec3 ret; for (int i = 0; i < 3; i++) { ret.v[i] = ( (R.m[i][0]*v.v[0])+(R.m[i][1]*v.v[1])+ (R.m[i][2]*v.v[2])+R.m[i][3] ); } return ret; } vec3 minCornerFlip (struct nifti_1_header *h, vec3i* flipVec) //orthogonal rotations and reflections applied as 3x3 matrices will cause the origin to shift // a simple solution is to first compute the most left, posterior, inferior voxel in the source image // this voxel will be at location i,j,k = 0,0,0, so we can simply use this as the offset for the final 4x4 matrix... { int i,j, minIndex; vec3i flipVecs[8]; vec3 corner[8], min; mat44 s = sFormMat(h); for (int i = 0; i < 8; i++) { if (i & 1) flipVecs[i].v[0] = -1; else flipVecs[i].v[0] = 1; if (i & 2) flipVecs[i].v[1] = -1; else flipVecs[i].v[1] = 1; if (i & 4) flipVecs[i].v[2] = -1; else flipVecs[i].v[2] = 1; corner[i] = setVec3(0,0,0); //assume no reflections if ((flipVecs[i].v[0]) < 1) corner[i].v[0] = h->dim[1]-1; //reflect X if ((flipVecs[i].v[1]) < 1) corner[i].v[1] = h->dim[2]-1; //reflect Y if ((flipVecs[i].v[2]) < 1) corner[i].v[2] = h->dim[3]-1; //reflect Z corner[i] = xyz2mm(s,corner[i]); } //find extreme edge from ALL corners.... min = corner[0]; for (i = 1; i < 8; i++) { for (j = 0; j < 3; j++) { if (corner[i].v[j]< min.v[j]) min.v[j] = corner[i].v[j]; } } float dx; //observed distance from corner float min_dx = getDistance (corner[0], min); minIndex = 0; //index of corner closest to min //see if any corner is closer to absmin than the first one... for (i = 1; i < 8; i++) { dx = getDistance (corner[i], min); if (dx < min_dx) { min_dx = dx; minIndex = i; } } min = corner[minIndex]; //this is the single corner closest to min from all *flipVec= flipVecs[minIndex]; return min; } #ifdef MY_DEBUG void reportMat44o(char *str, mat44 A) { printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n",str, A.m[0][0],A.m[0][1],A.m[0][2],A.m[0][3], A.m[1][0],A.m[1][1],A.m[1][2],A.m[1][3], A.m[2][0],A.m[2][1],A.m[2][2],A.m[2][3]); } #endif unsigned char * nii_setOrtho(unsigned char* img, struct nifti_1_header *h) { if ((h->dim[1] < 1) || (h->dim[2] < 1) || (h->dim[3] < 1)) return img; if ((h->sform_code == NIFTI_XFORM_UNKNOWN) && (h->qform_code != NIFTI_XFORM_UNKNOWN)) { //only q-form provided mat44 q = nifti_quatern_to_mat44(h->quatern_b, h->quatern_c, h->quatern_d, h->qoffset_x, h->qoffset_y, h->qoffset_z, h->pixdim[1], h->pixdim[2], h->pixdim[3],h->pixdim[0]); mat2sForm(h,q); //convert q-form to s-form h->sform_code = h->qform_code; } if (h->sform_code == NIFTI_XFORM_UNKNOWN) { #ifdef MY_DEBUG printMessage("No Q or S spatial transforms - assuming canonical orientation"); #endif return img; } mat44 s = sFormMat(h); if (isMat44Canonical( s)) { #ifdef MY_DEBUG printMessage("Image in perfect alignment: no need to reorient"); #endif return img; } vec3i flipV; vec3 minMM = minCornerFlip(h, &flipV); mat33 orient = getBestOrient(s, flipV); vec3i orientVec = setOrientVec(orient); if ((orientVec.v[0]==1) && (orientVec.v[1]==2) && (orientVec.v[2]==3) ) { #ifdef MY_DEBUG printMessage("Image already near best orthogonal alignment: no need to reorient\n"); #endif return img; } bool is24 = false; if (h->bitpix == 24 ) { //RGB stored as planar data. treat as 3 8-bit slices return img; /*is24 = true; h->bitpix = 8; h->dim[3] = h->dim[3] * 3;*/ } img = reOrient(img, h,orientVec, orient, minMM); if (is24 ) { h->bitpix = 24; h->dim[3] = h->dim[3] / 3; } #ifdef MY_DEBUG printMessage("NewRotation= %d %d %d\n", orientVec.v[0],orientVec.v[1],orientVec.v[2]); printMessage("MinCorner= %.2f %.2f %.2f\n", minMM.v[0],minMM.v[1],minMM.v[2]); reportMat44o((char*)"input",s); s = sFormMat(h); reportMat44o((char*)"output",s); #endif return img; } dcm2niix-1.0.20181125/console/nii_ortho.h000066400000000000000000000005431337661136700176150ustar00rootroot00000000000000#ifndef _NIFTI_ORTHO_CORE_ #define _NIFTI_ORTHO_CORE_ #ifdef __cplusplus extern "C" { #endif #ifndef USING_R #include "nifti1.h" #endif void mat2sForm (struct nifti_1_header *h, mat44 s); bool isMat44Canonical(mat44 R); unsigned char * nii_setOrtho(unsigned char* img, struct nifti_1_header *h); #ifdef __cplusplus } #endif #endif dcm2niix-1.0.20181125/console/print.h000066400000000000000000000045701337661136700167630ustar00rootroot00000000000000//This unit allows us to re-direct text messages // For standard C programs send text messages to the console via "printf" // The XCode project shows how you can re-direct these messages to a NSTextView // For QT programs, we can sent text messages to the cout buffer // The QT project shows how you can re-direct these to a Qtextedit // For R programs, we can intercept these messages. #ifndef _R_PRINT_H_ #define _R_PRINT_H_ #include #ifdef USING_R #define R_USE_C99_IN_CXX #include #define printMessage(...) { Rprintf("[dcm2niix info] "); Rprintf(__VA_ARGS__); } #define printWarning(...) { Rprintf("[dcm2niix WARNING] "); Rprintf(__VA_ARGS__); } #define printError(...) { Rprintf("[dcm2niix ERROR] "); Rprintf(__VA_ARGS__); } #else #ifdef myUseCOut //for piping output to Qtextedit // printf and cout buffers are not the same // #define printMessage(...) ({fprintf(stdout,__VA_ARGS__);}) #include template< typename... Args > void printMessage( const char* format, Args... args ) { //std::printf( format, args... ); //fprintf(stdout,"Short read on %s: Expected 512, got %zd\n",path, bytes_read); int length = std::snprintf( nullptr, 0, format, args... ); if ( length <= 0 ) return; char* buf = new char[length + 1]; std::snprintf( buf, length + 1, format, args... ); std::cout << buf; delete[] buf; } #define printError(...) do { printMessage("Error: "); printMessage(__VA_ARGS__);} while(0) #else #include #define printMessage printf //#define printMessageError(...) fprintf (stderr, __VA_ARGS__) #ifdef myErrorStdOut //for XCode MRIcro project, pipe errors to stdout not stderr #define printError(...) do { printMessage("Error: "); printMessage(__VA_ARGS__);} while(0) #else #define printError(...) do { fprintf (stderr,"Error: "); fprintf (stderr, __VA_ARGS__);} while(0) #endif #endif //myUseCOut //n.b. use ({}) for multi-line macros http://www.geeksforgeeks.org/multiline-macros-in-c/ //these next lines work on GCC but not _MSC_VER // #define printWarning(...) ({printMessage("Warning: "); printMessage(__VA_ARGS__);}) // #define printError(...) ({ printMessage("Error: "); printMessage(__VA_ARGS__);}) #define printWarning(...) do {printMessage("Warning: "); printMessage(__VA_ARGS__);} while(0) #endif //USING_R #endif //_R_PRINT_H_ dcm2niix-1.0.20181125/console/tinydir.h000066400000000000000000000205411337661136700173050ustar00rootroot00000000000000/* Copyright (c) 2013-2014, Cong Xu All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef TINYDIR_H #define TINYDIR_H #include #include #include #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN #include #pragma warning (disable : 4996) #else #include #include #endif /* types */ #define _TINYDIR_PATH_MAX 4096 #ifdef _MSC_VER /* extra chars for the "\\*" mask */ #define _TINYDIR_PATH_EXTRA 2 #else #define _TINYDIR_PATH_EXTRA 0 #endif #define _TINYDIR_FILENAME_MAX 256 #ifdef _MSC_VER #define _TINYDIR_FUNC static __inline #else #define _TINYDIR_FUNC static __inline__ #endif typedef struct { char path[_TINYDIR_PATH_MAX]; char name[_TINYDIR_FILENAME_MAX]; int is_dir; int is_reg; #ifdef _MSC_VER #else struct stat _s; #endif } tinydir_file; typedef struct { char path[_TINYDIR_PATH_MAX]; int has_next; size_t n_files; tinydir_file *_files; #ifdef _MSC_VER HANDLE _h; WIN32_FIND_DATA _f; #else DIR *_d; struct dirent *_e; #endif } tinydir_dir; /* declarations */ _TINYDIR_FUNC int tinydir_open(tinydir_dir *dir, const char *path); _TINYDIR_FUNC int tinydir_open_sorted(tinydir_dir *dir, const char *path); _TINYDIR_FUNC void tinydir_close(tinydir_dir *dir); _TINYDIR_FUNC int tinydir_next(tinydir_dir *dir); _TINYDIR_FUNC int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); _TINYDIR_FUNC int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); _TINYDIR_FUNC int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); _TINYDIR_FUNC int _tinydir_file_cmp(const void *a, const void *b); /* definitions*/ _TINYDIR_FUNC int tinydir_open(tinydir_dir *dir, const char *path) { if (dir == NULL || path == NULL || strlen(path) == 0) { errno = EINVAL; return -1; } if (strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) { errno = ENAMETOOLONG; return -1; } /* initialise dir */ dir->_files = NULL; #ifdef _MSC_VER dir->_h = INVALID_HANDLE_VALUE; #else dir->_d = NULL; #endif tinydir_close(dir); strcpy(dir->path, path); #ifdef _MSC_VER strcat(dir->path, "\\*"); dir->_h = FindFirstFile(dir->path, &dir->_f); dir->path[strlen(dir->path) - 2] = '\0'; if (dir->_h == INVALID_HANDLE_VALUE) #else dir->_d = opendir(path); if (dir->_d == NULL) #endif { errno = ENOENT; goto bail; } /* read first file */ dir->has_next = 1; #ifndef _MSC_VER dir->_e = readdir(dir->_d); if (dir->_e == NULL) { dir->has_next = 0; } #endif return 0; bail: tinydir_close(dir); return -1; } _TINYDIR_FUNC int tinydir_open_sorted(tinydir_dir *dir, const char *path) { /* Count the number of files first, to pre-allocate the files array */ size_t n_files = 0; if (tinydir_open(dir, path) == -1) { return -1; } while (dir->has_next) { n_files++; if (tinydir_next(dir) == -1) { goto bail; } } tinydir_close(dir); if (tinydir_open(dir, path) == -1) { return -1; } dir->n_files = 0; dir->_files = (tinydir_file *)malloc(sizeof *dir->_files * n_files); if (dir->_files == NULL) { errno = ENOMEM; goto bail; } while (dir->has_next) { tinydir_file *p_file; dir->n_files++; p_file = &dir->_files[dir->n_files - 1]; if (tinydir_readfile(dir, p_file) == -1) { goto bail; } if (tinydir_next(dir) == -1) { goto bail; } /* Just in case the number of files has changed between the first and second reads, terminate without writing into unallocated memory */ if (dir->n_files == n_files) { break; } } qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); return 0; bail: tinydir_close(dir); return -1; } _TINYDIR_FUNC void tinydir_close(tinydir_dir *dir) { if (dir == NULL) { return; } memset(dir->path, 0, sizeof(dir->path)); dir->has_next = 0; dir->n_files = 0; if (dir->_files != NULL) { free(dir->_files); } dir->_files = NULL; #ifdef _MSC_VER if (dir->_h != INVALID_HANDLE_VALUE) { FindClose(dir->_h); } dir->_h = INVALID_HANDLE_VALUE; #else if (dir->_d) { closedir(dir->_d); } dir->_d = NULL; dir->_e = NULL; #endif } _TINYDIR_FUNC int tinydir_next(tinydir_dir *dir) { if (dir == NULL) { errno = EINVAL; return -1; } if (!dir->has_next) { errno = ENOENT; return -1; } #ifdef _MSC_VER if (FindNextFile(dir->_h, &dir->_f) == 0) #else dir->_e = readdir(dir->_d); if (dir->_e == NULL) #endif { dir->has_next = 0; #ifdef _MSC_VER if (GetLastError() != ERROR_SUCCESS && GetLastError() != ERROR_NO_MORE_FILES) { tinydir_close(dir); errno = EIO; return -1; } #endif } return 0; } _TINYDIR_FUNC int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) { if (dir == NULL || file == NULL) { errno = EINVAL; return -1; } #ifdef _MSC_VER if (dir->_h == INVALID_HANDLE_VALUE) #else if (dir->_e == NULL) #endif { errno = ENOENT; return -1; } if (strlen(dir->path) + strlen( #ifdef _MSC_VER dir->_f.cFileName #else dir->_e->d_name #endif ) + 1 + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) { /* the path for the file will be too long */ errno = ENAMETOOLONG; return -1; } if (strlen( #ifdef _MSC_VER dir->_f.cFileName #else dir->_e->d_name #endif ) >= _TINYDIR_FILENAME_MAX) { errno = ENAMETOOLONG; return -1; } strcpy(file->path, dir->path); strcat(file->path, "/"); strcpy(file->name, #ifdef _MSC_VER dir->_f.cFileName #else dir->_e->d_name #endif ); strcat(file->path, file->name); #ifndef _MSC_VER if (stat(file->path, &file->_s) == -1) { return -1; } #endif file->is_dir = #ifdef _MSC_VER !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); #else S_ISDIR(file->_s.st_mode); #endif file->is_reg = #ifdef _MSC_VER !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || ( !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && #ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && #endif #ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && #endif !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); #else S_ISREG(file->_s.st_mode); #endif return 0; } _TINYDIR_FUNC int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) { if (dir == NULL || file == NULL) { errno = EINVAL; return -1; } if (i >= dir->n_files) { errno = ENOENT; return -1; } memcpy(file, &dir->_files[i], sizeof(tinydir_file)); return 0; } _TINYDIR_FUNC int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) { char path[_TINYDIR_PATH_MAX]; if (dir == NULL) { errno = EINVAL; return -1; } if (i >= dir->n_files || !dir->_files[i].is_dir) { errno = ENOENT; return -1; } strcpy(path, dir->_files[i].path); tinydir_close(dir); if (tinydir_open_sorted(dir, path) == -1) { return -1; } return 0; } _TINYDIR_FUNC int _tinydir_file_cmp(const void *a, const void *b) { const tinydir_file *fa = (const tinydir_file *)a; const tinydir_file *fb = (const tinydir_file *)b; if (fa->is_dir != fb->is_dir) { return -(fa->is_dir - fb->is_dir); } return strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); } #endif dcm2niix-1.0.20181125/console/ucm.cmake000066400000000000000000000600361337661136700172430ustar00rootroot00000000000000# # ucm.cmake - useful cmake macros # # Copyright (c) 2016 Viktor Kirilov # # Distributed under the MIT Software License # See accompanying file LICENSE.txt or copy at # https://opensource.org/licenses/MIT # # The documentation can be found at the library's page: # https://github.com/onqtam/ucm cmake_minimum_required(VERSION 2.8.12) include(CMakeParseArguments) # optionally include cotire - the git submodule might not be inited (or the user might have already included it) if(NOT COMMAND cotire) include(${CMAKE_CURRENT_LIST_DIR}/../cotire/CMake/cotire.cmake OPTIONAL) endif() if(COMMAND cotire AND "1.7.9" VERSION_LESS "${COTIRE_CMAKE_MODULE_VERSION}") set(ucm_with_cotire 1) else() set(ucm_with_cotire 0) endif() # option(UCM_UNITY_BUILD "Enable unity build for targets registered with the ucm_add_target() macro" OFF) # option(UCM_NO_COTIRE_FOLDER "Do not use a cotire folder in the solution explorer for all unity and cotire related targets" ON) # ucm_add_flags # Adds compiler flags to CMAKE__FLAGS or to a specific config macro(ucm_add_flags) cmake_parse_arguments(ARG "C;CXX;CLEAR_OLD" "" "CONFIG" ${ARGN}) if(NOT ARG_CONFIG) set(ARG_CONFIG " ") endif() foreach(CONFIG ${ARG_CONFIG}) # determine to which flags to add if(NOT ${CONFIG} STREQUAL " ") string(TOUPPER ${CONFIG} CONFIG) set(CXX_FLAGS CMAKE_CXX_FLAGS_${CONFIG}) set(C_FLAGS CMAKE_C_FLAGS_${CONFIG}) else() set(CXX_FLAGS CMAKE_CXX_FLAGS) set(C_FLAGS CMAKE_C_FLAGS) endif() # clear the old flags if(${ARG_CLEAR_OLD}) if("${ARG_CXX}" OR NOT "${ARG_C}") set(${CXX_FLAGS} "") endif() if("${ARG_C}" OR NOT "${ARG_CXX}") set(${C_FLAGS} "") endif() endif() # add all the passed flags foreach(flag ${ARG_UNPARSED_ARGUMENTS}) if("${ARG_CXX}" OR NOT "${ARG_C}") set(${CXX_FLAGS} "${${CXX_FLAGS}} ${flag}") endif() if("${ARG_C}" OR NOT "${ARG_CXX}") set(${C_FLAGS} "${${C_FLAGS}} ${flag}") endif() endforeach() endforeach() endmacro() # ucm_set_flags # Sets the CMAKE__FLAGS compiler flags or for a specific config macro(ucm_set_flags) ucm_add_flags(CLEAR_OLD ${ARGN}) endmacro() # ucm_add_linker_flags # Adds linker flags to CMAKE__LINKER_FLAGS or to a specific config macro(ucm_add_linker_flags) cmake_parse_arguments(ARG "CLEAR_OLD;EXE;MODULE;SHARED;STATIC" "" "CONFIG" ${ARGN}) if(NOT ARG_CONFIG) set(ARG_CONFIG " ") endif() foreach(CONFIG ${ARG_CONFIG}) string(TOUPPER "${CONFIG}" CONFIG) if(NOT ${ARG_EXE} AND NOT ${ARG_MODULE} AND NOT ${ARG_SHARED} AND NOT ${ARG_STATIC}) set(ARG_EXE 1) set(ARG_MODULE 1) set(ARG_SHARED 1) set(ARG_STATIC 1) endif() set(flags_configs "") if(${ARG_EXE}) if(NOT "${CONFIG}" STREQUAL " ") list(APPEND flags_configs CMAKE_EXE_LINKER_FLAGS_${CONFIG}) else() list(APPEND flags_configs CMAKE_EXE_LINKER_FLAGS) endif() endif() if(${ARG_MODULE}) if(NOT "${CONFIG}" STREQUAL " ") list(APPEND flags_configs CMAKE_MODULE_LINKER_FLAGS_${CONFIG}) else() list(APPEND flags_configs CMAKE_MODULE_LINKER_FLAGS) endif() endif() if(${ARG_SHARED}) if(NOT "${CONFIG}" STREQUAL " ") list(APPEND flags_configs CMAKE_SHARED_LINKER_FLAGS_${CONFIG}) else() list(APPEND flags_configs CMAKE_SHARED_LINKER_FLAGS) endif() endif() if(${ARG_STATIC}) if(NOT "${CONFIG}" STREQUAL " ") list(APPEND flags_configs CMAKE_STATIC_LINKER_FLAGS_${CONFIG}) else() list(APPEND flags_configs CMAKE_STATIC_LINKER_FLAGS) endif() endif() # clear the old flags if(${ARG_CLEAR_OLD}) foreach(flags ${flags_configs}) set(${flags} "") endforeach() endif() # add all the passed flags foreach(flag ${ARG_UNPARSED_ARGUMENTS}) foreach(flags ${flags_configs}) set(${flags} "${${flags}} ${flag}") endforeach() endforeach() endforeach() endmacro() # ucm_set_linker_flags # Sets the CMAKE__LINKER_FLAGS linker flags or for a specific config macro(ucm_set_linker_flags) ucm_add_linker_flags(CLEAR_OLD ${ARGN}) endmacro() # ucm_gather_flags # Gathers all lists of flags for printing or manipulation macro(ucm_gather_flags with_linker result) set(${result} "") # add the main flags without a config list(APPEND ${result} CMAKE_C_FLAGS) list(APPEND ${result} CMAKE_CXX_FLAGS) if(${with_linker}) list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS) list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS) list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS) list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS) endif() if("${CMAKE_CONFIGURATION_TYPES}" STREQUAL "" AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "") # handle single config generators - like makefiles/ninja - when CMAKE_BUILD_TYPE is set string(TOUPPER ${CMAKE_BUILD_TYPE} config) list(APPEND ${result} CMAKE_C_FLAGS_${config}) list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) if(${with_linker}) list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) endif() else() # handle multi config generators (like msvc, xcode) foreach(config ${CMAKE_CONFIGURATION_TYPES}) string(TOUPPER ${config} config) list(APPEND ${result} CMAKE_C_FLAGS_${config}) list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) if(${with_linker}) list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) endif() endforeach() endif() endmacro() # ucm_set_runtime # Sets the runtime (static/dynamic) for msvc/gcc macro(ucm_set_runtime) cmake_parse_arguments(ARG "STATIC;DYNAMIC" "" "" ${ARGN}) if(ARG_UNPARSED_ARGUMENTS) message(FATAL_ERROR "unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" STREQUAL "") message(AUTHOR_WARNING "ucm_set_runtime() does not support clang yet!") endif() ucm_gather_flags(0 flags_configs) # add/replace the flags # note that if the user has messed with the flags directly this function might fail # - for example if with MSVC and the user has removed the flags - here we just switch/replace them if("${ARG_STATIC}") foreach(flags ${flags_configs}) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.4.7) # option "-static-libstdc++" available since GCC 4.5 if(NOT ${flags} MATCHES "-static-libstdc\\+\\+") set(${flags} "${${flags}} -static-libstdc++") endif() endif() if(NOT ${flags} MATCHES "-static-libgcc") set(${flags} "${${flags}} -static-libgcc") endif() elseif(MSVC) if(${flags} MATCHES "/MD") string(REGEX REPLACE "/MD" "/MT" ${flags} "${${flags}}") endif() endif() endforeach() elseif("${ARG_DYNAMIC}") foreach(flags ${flags_configs}) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") if(${flags} MATCHES "-static-libstdc\\+\\+") string(REGEX REPLACE "-static-libstdc\\+\\+" "" ${flags} "${${flags}}") endif() if(${flags} MATCHES "-static-libgcc") string(REGEX REPLACE "-static-libgcc" "" ${flags} "${${flags}}") endif() elseif(MSVC) if(${flags} MATCHES "/MT") string(REGEX REPLACE "/MT" "/MD" ${flags} "${${flags}}") endif() endif() endforeach() endif() endmacro() # ucm_print_flags # Prints all compiler flags for all configurations macro(ucm_print_flags) ucm_gather_flags(1 flags_configs) message("") foreach(flags ${flags_configs}) message("${flags}: ${${flags}}") endforeach() message("") endmacro() # ucm_count_sources # Counts the number of source files macro(ucm_count_sources) cmake_parse_arguments(ARG "" "RESULT" "" ${ARGN}) if(${ARG_RESULT} STREQUAL "") message(FATAL_ERROR "Need to pass RESULT and a variable name to ucm_count_sources()") endif() set(result 0) foreach(SOURCE_FILE ${ARG_UNPARSED_ARGUMENTS}) if("${SOURCE_FILE}" MATCHES \\.\(c|C|cc|cp|cpp|CPP|c\\+\\+|cxx|i|ii\)$) math(EXPR result "${result} + 1") endif() endforeach() set(${ARG_RESULT} ${result}) endmacro() # ucm_include_file_in_sources # Includes the file to the source with compiler flags macro(ucm_include_file_in_sources) cmake_parse_arguments(ARG "" "HEADER" "" ${ARGN}) if(${ARG_HEADER} STREQUAL "") message(FATAL_ERROR "Need to pass HEADER and a header file to ucm_include_file_in_sources()") endif() foreach(src ${ARG_UNPARSED_ARGUMENTS}) if(${src} MATCHES \\.\(c|C|cc|cp|cpp|CPP|c\\+\\+|cxx\)$) # get old flags get_source_file_property(old_compile_flags ${src} COMPILE_FLAGS) if(old_compile_flags STREQUAL "NOTFOUND") set(old_compile_flags "") endif() # update flags if(MSVC) set_source_files_properties(${src} PROPERTIES COMPILE_FLAGS "${old_compile_flags} /FI\"${CMAKE_CURRENT_SOURCE_DIR}/${ARG_HEADER}\"") else() set_source_files_properties(${src} PROPERTIES COMPILE_FLAGS "${old_compile_flags} -include \"${CMAKE_CURRENT_SOURCE_DIR}/${ARG_HEADER}\"") endif() endif() endforeach() endmacro() # ucm_dir_list # Returns a list of subdirectories for a given directory macro(ucm_dir_list thedir result) file(GLOB sub-dir "${thedir}/*") set(list_of_dirs "") foreach(dir ${sub-dir}) if(IS_DIRECTORY ${dir}) get_filename_component(DIRNAME ${dir} NAME) LIST(APPEND list_of_dirs ${DIRNAME}) endif() endforeach() set(${result} ${list_of_dirs}) endmacro() # ucm_trim_front_words # Trims X times the front word from a string separated with "/" and removes # the front "/" characters after that (used for filters for visual studio) macro(ucm_trim_front_words source out num_filter_trims) set(result "${source}") set(counter 0) while(${counter} LESS ${num_filter_trims}) MATH(EXPR counter "${counter} + 1") # removes everything at the front up to a "/" character string(REGEX REPLACE "^([^/]+)" "" result "${result}") # removes all consecutive "/" characters from the front string(REGEX REPLACE "^(/+)" "" result "${result}") endwhile() set(${out} ${result}) endmacro() # ucm_remove_files # Removes source files from a list of sources (path is the relative path for it to be found) macro(ucm_remove_files) cmake_parse_arguments(ARG "" "FROM" "" ${ARGN}) if("${ARG_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "Need to pass some relative files to ucm_remove_files()") endif() if(${ARG_FROM} STREQUAL "") message(FATAL_ERROR "Need to pass FROM and a variable name to ucm_remove_files()") endif() foreach(cur_file ${ARG_UNPARSED_ARGUMENTS}) list(REMOVE_ITEM ${ARG_FROM} ${cur_file}) endforeach() endmacro() # ucm_remove_directories # Removes all source files from the given directories from the sources list macro(ucm_remove_directories) cmake_parse_arguments(ARG "" "FROM" "MATCHES" ${ARGN}) if("${ARG_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "Need to pass some relative directories to ucm_remove_directories()") endif() if(${ARG_FROM} STREQUAL "") message(FATAL_ERROR "Need to pass FROM and a variable name to ucm_remove_directories()") endif() foreach(cur_dir ${ARG_UNPARSED_ARGUMENTS}) foreach(cur_file ${${ARG_FROM}}) string(REGEX MATCH ${cur_dir} res ${cur_file}) if(NOT "${res}" STREQUAL "") if("${ARG_MATCHES}" STREQUAL "") list(REMOVE_ITEM ${ARG_FROM} ${cur_file}) else() foreach(curr_ptrn ${ARG_MATCHES}) string(REGEX MATCH ${curr_ptrn} res ${cur_file}) if(NOT "${res}" STREQUAL "") list(REMOVE_ITEM ${ARG_FROM} ${cur_file}) break() endif() endforeach() endif() endif() endforeach() endforeach() endmacro() # ucm_add_files_impl macro(ucm_add_files_impl result trim files) foreach(cur_file ${files}) SET(${result} ${${result}} ${cur_file}) get_filename_component(FILEPATH ${cur_file} PATH) ucm_trim_front_words("${FILEPATH}" FILEPATH "${trim}") # replacing forward slashes with back slashes so filters can be generated (back slash used in parsing...) STRING(REPLACE "/" "\\" FILTERS "${FILEPATH}") SOURCE_GROUP("${FILTERS}" FILES ${cur_file}) endforeach() endmacro() # ucm_add_files # Adds files to a list of sources macro(ucm_add_files) cmake_parse_arguments(ARG "" "TO;FILTER_POP" "" ${ARGN}) if("${ARG_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "Need to pass some relative files to ucm_add_files()") endif() if(${ARG_TO} STREQUAL "") message(FATAL_ERROR "Need to pass TO and a variable name to ucm_add_files()") endif() if("${ARG_FILTER_POP}" STREQUAL "") set(ARG_FILTER_POP 0) endif() ucm_add_files_impl(${ARG_TO} ${ARG_FILTER_POP} "${ARG_UNPARSED_ARGUMENTS}") endmacro() # ucm_add_dir_impl macro(ucm_add_dir_impl result rec trim dirs_in additional_ext) set(dirs "${dirs_in}") # handle the "" and "." cases if("${dirs}" STREQUAL "" OR "${dirs}" STREQUAL ".") set(dirs "./") endif() foreach(cur_dir ${dirs}) # to circumvent some linux/cmake/path issues - barely made it work... if(cur_dir STREQUAL "./") set(cur_dir "") else() set(cur_dir "${cur_dir}/") endif() # since unix is case sensitive - add these valid extensions too # we don't use "UNIX" but instead "CMAKE_HOST_UNIX" because we might be cross # compiling (for example emscripten) under windows and UNIX may be set to 1 # Also OSX is case insensitive like windows... set(additional_file_extensions "") if(CMAKE_HOST_UNIX AND NOT APPLE) set(additional_file_extensions "${cur_dir}*.CPP" "${cur_dir}*.C" "${cur_dir}*.H" "${cur_dir}*.HPP" ) endif() foreach(ext ${additional_ext}) list(APPEND additional_file_extensions "${cur_dir}*.${ext}") endforeach() # find all sources and set them as result FILE(GLOB found_sources RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" # https://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/Overall-Options.html#index-file-name-suffix-71 # sources "${cur_dir}*.cpp" "${cur_dir}*.cxx" "${cur_dir}*.c++" "${cur_dir}*.cc" "${cur_dir}*.cp" "${cur_dir}*.c" "${cur_dir}*.i" "${cur_dir}*.ii" # headers "${cur_dir}*.h" "${cur_dir}*.h++" "${cur_dir}*.hpp" "${cur_dir}*.hxx" "${cur_dir}*.hh" "${cur_dir}*.inl" "${cur_dir}*.inc" "${cur_dir}*.ipp" "${cur_dir}*.ixx" "${cur_dir}*.txx" "${cur_dir}*.tpp" "${cur_dir}*.tcc" "${cur_dir}*.tpl" ${additional_file_extensions}) SET(${result} ${${result}} ${found_sources}) # set the proper filters ucm_trim_front_words("${cur_dir}" cur_dir "${trim}") # replacing forward slashes with back slashes so filters can be generated (back slash used in parsing...) STRING(REPLACE "/" "\\" FILTERS "${cur_dir}") SOURCE_GROUP("${FILTERS}" FILES ${found_sources}) endforeach() if(${rec}) foreach(cur_dir ${dirs}) ucm_dir_list("${cur_dir}" subdirs) foreach(subdir ${subdirs}) ucm_add_dir_impl(${result} ${rec} ${trim} "${cur_dir}/${subdir}" "${additional_ext}") endforeach() endforeach() endif() endmacro() # ucm_add_dirs # Adds all files from directories traversing them recursively to a list of sources # and generates filters according to their location (accepts relative paths only). # Also this macro trims X times the front word from the filter string for visual studio filters. macro(ucm_add_dirs) cmake_parse_arguments(ARG "RECURSIVE" "TO;FILTER_POP" "ADDITIONAL_EXT" ${ARGN}) if(${ARG_TO} STREQUAL "") message(FATAL_ERROR "Need to pass TO and a variable name to ucm_add_dirs()") endif() if("${ARG_FILTER_POP}" STREQUAL "") set(ARG_FILTER_POP 0) endif() ucm_add_dir_impl(${ARG_TO} ${ARG_RECURSIVE} ${ARG_FILTER_POP} "${ARG_UNPARSED_ARGUMENTS}" "${ARG_ADDITIONAL_EXT}") endmacro() # ucm_add_target # Adds a target eligible for cotiring - unity build and/or precompiled header macro(ucm_add_target) cmake_parse_arguments(ARG "UNITY" "NAME;TYPE;PCH_FILE;CPP_PER_UNITY" "UNITY_EXCLUDED;SOURCES" ${ARGN}) if(NOT "${ARG_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "Unrecognized options passed to ucm_add_target()") endif() if("${ARG_NAME}" STREQUAL "") message(FATAL_ERROR "Need to pass NAME and a name for the target to ucm_add_target()") endif() set(valid_types EXECUTABLE STATIC SHARED MODULE) list(FIND valid_types "${ARG_TYPE}" is_type_valid) if(${is_type_valid} STREQUAL "-1") message(FATAL_ERROR "Need to pass TYPE and the type for the target [EXECUTABLE/STATIC/SHARED/MODULE] to ucm_add_target()") endif() if("${ARG_SOURCES}" STREQUAL "") message(FATAL_ERROR "Need to pass SOURCES and a list of source files to ucm_add_target()") endif() # init with the global unity flag set(do_unity ${UCM_UNITY_BUILD}) # check the UNITY argument if(NOT ARG_UNITY) set(do_unity FALSE) endif() # if target is excluded through the exclusion list list(FIND UCM_UNITY_BUILD_EXCLUDE_TARGETS ${ARG_NAME} is_target_excluded) if(NOT ${is_target_excluded} STREQUAL "-1") set(do_unity FALSE) endif() # unity build only for targets with > 1 source file (otherwise there will be an additional unnecessary target) if(do_unity) # optimization ucm_count_sources(${ARG_SOURCES} RESULT num_sources) if(${num_sources} LESS 2) set(do_unity FALSE) endif() endif() set(wanted_cotire ${do_unity}) # if cotire cannot be used if(do_unity AND NOT ucm_with_cotire) set(do_unity FALSE) endif() # inform the developer that the current target might benefit from a unity build if(NOT ARG_UNITY AND ${UCM_UNITY_BUILD}) ucm_count_sources(${ARG_SOURCES} RESULT num_sources) if(${num_sources} GREATER 1) message(AUTHOR_WARNING "Target '${ARG_NAME}' may benefit from a unity build.\nIt has ${num_sources} sources - enable with UNITY flag") endif() endif() # prepare for the unity build set(orig_target ${ARG_NAME}) if(do_unity) # the original target will be added with a different name than the requested set(orig_target ${ARG_NAME}_ORIGINAL) # exclude requested files from unity build of the current target foreach(excluded_file "${ARG_UNITY_EXCLUDED}") set_source_files_properties(${excluded_file} PROPERTIES COTIRE_EXCLUDED TRUE) endforeach() endif() # add the original target if(${ARG_TYPE} STREQUAL "EXECUTABLE") add_executable(${orig_target} ${ARG_SOURCES}) else() add_library(${orig_target} ${ARG_TYPE} ${ARG_SOURCES}) endif() if(do_unity) # set the number of unity cpp files to be used for the unity target if(NOT "${ARG_CPP_PER_UNITY}" STREQUAL "") set_property(TARGET ${orig_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "${ARG_CPP_PER_UNITY}") else() set_property(TARGET ${orig_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "100") endif() if(NOT "${ARG_PCH_FILE}" STREQUAL "") set_target_properties(${orig_target} PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${ARG_PCH_FILE}") else() set_target_properties(${orig_target} PROPERTIES COTIRE_ENABLE_PRECOMPILED_HEADER FALSE) endif() # add a unity target for the original one with the name intended for the original set_target_properties(${orig_target} PROPERTIES COTIRE_UNITY_TARGET_NAME ${ARG_NAME}) # this is the library call that does the magic cotire(${orig_target}) set_target_properties(clean_cotire PROPERTIES FOLDER "CMakePredefinedTargets") # disable the original target and enable the unity one get_target_property(unity_target_name ${orig_target} COTIRE_UNITY_TARGET_NAME) set_target_properties(${orig_target} PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) set_target_properties(${unity_target_name} PROPERTIES EXCLUDE_FROM_ALL 0 EXCLUDE_FROM_DEFAULT_BUILD 0) # also set the name of the target output as the original one set_target_properties(${unity_target_name} PROPERTIES OUTPUT_NAME ${ARG_NAME}) if(UCM_NO_COTIRE_FOLDER) # reset the folder property so all unity targets dont end up in a single folder in the solution explorer of VS set_target_properties(${unity_target_name} PROPERTIES FOLDER "") endif() set_target_properties(all_unity PROPERTIES FOLDER "CMakePredefinedTargets") elseif(NOT "${ARG_PCH_FILE}" STREQUAL "") set(wanted_cotire TRUE) if(ucm_with_cotire) set_target_properties(${orig_target} PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) set_target_properties(${orig_target} PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${ARG_PCH_FILE}") cotire(${orig_target}) set_target_properties(clean_cotire PROPERTIES FOLDER "CMakePredefinedTargets") endif() endif() # print a message if the target was requested to be cotired but it couldn't if(wanted_cotire AND NOT ucm_with_cotire) if(NOT COMMAND cotire) message(AUTHOR_WARNING "Target \"${ARG_NAME}\" not cotired because cotire isn't loaded") else() message(AUTHOR_WARNING "Target \"${ARG_NAME}\" not cotired because cotire is older than the required version") endif() endif() endmacro() dcm2niix-1.0.20181125/console/ujpeg.cpp000066400000000000000000000761731337661136700173040ustar00rootroot00000000000000// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder // version 1.3.5 (2016-11-14) // Copyright (c) 2009-2016 Martin J. Fiedler // published under the terms of the MIT license // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////////////////////////// // DOCUMENTATION SECTION // // read this if you want to know what this is all about // /////////////////////////////////////////////////////////////////////////////// // INTRODUCTION // ============ // // This is a minimal decoder for baseline JPEG images. It accepts memory dumps // of JPEG files as input and generates either 8-bit grayscale or packed 24-bit // RGB images as output. It does not parse JFIF or Exif headers; all JPEG files // are assumed to be either grayscale or YCbCr. CMYK or other color spaces are // not supported. All YCbCr subsampling schemes with power-of-two ratios are // supported, as are restart intervals. Progressive or lossless JPEG is not // supported. // Summed up, NanoJPEG should be able to decode all images from digital cameras // and most common forms of other non-progressive JPEG images. // The decoder is not optimized for speed, it's optimized for simplicity and // small code. Image quality should be at a reasonable level. A bicubic chroma // upsampling filter ensures that subsampled YCbCr images are rendered in // decent quality. The decoder is not meant to deal with broken JPEG files in // a graceful manner; if anything is wrong with the bitstream, decoding will // simply fail. // The code should work with every modern C compiler without problems and // should not emit any warnings. It uses only (at least) 32-bit integer // arithmetic and is supposed to be endianness independent and 64-bit clean. // However, it is not thread-safe. // COMPILE-TIME CONFIGURATION // ========================== // // The following aspects of NanoJPEG can be controlled with preprocessor // defines: // // _NJ_EXAMPLE_PROGRAM = Compile a main() function with an example // program. // _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header // file for NanoJPEG. Example: // #define _NJ_INCLUDE_HEADER_ONLY // #include "nanojpeg.c" // int main(void) { // njInit(); // // your code here // njDone(); // } // NJ_USE_LIBC=1 = Use the malloc(), free(), memset() and memcpy() // functions from the standard C library (default). // NJ_USE_LIBC=0 = Don't use the standard C library. In this mode, // external functions njAlloc(), njFreeMem(), // njFillMem() and njCopyMem() need to be defined // and implemented somewhere. // NJ_USE_WIN32=0 = Normal mode (default). // NJ_USE_WIN32=1 = If compiling with MSVC for Win32 and // NJ_USE_LIBC=0, NanoJPEG will use its own // implementations of the required C library // functions (default if compiling with MSVC and // NJ_USE_LIBC=0). // NJ_CHROMA_FILTER=1 = Use the bicubic chroma upsampling filter // (default). // NJ_CHROMA_FILTER=0 = Use simple pixel repetition for chroma upsampling // (bad quality, but faster and less code). // API // === // // For API documentation, read the "header section" below. // EXAMPLE // ======= // // A few pages below, you can find an example program that uses NanoJPEG to // convert JPEG files into PGM or PPM. To compile it, use something like // gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c // You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want :) // The only thing you might need is -Wno-shift-negative-value, because this // code relies on the target machine using two's complement arithmetic, but // the C standard does not, even though *any* practically useful machine // nowadays uses two's complement. /////////////////////////////////////////////////////////////////////////////// // HEADER SECTION // // copy and pase this into nanojpeg.h if you want // /////////////////////////////////////////////////////////////////////////////// #ifndef _NANOJPEG_H #define _NANOJPEG_H // nj_result_t: Result codes for njDecode(). typedef enum _nj_result { NJ_OK = 0, // no error, decoding successful NJ_NO_JPEG, // not a JPEG file NJ_UNSUPPORTED, // unsupported format NJ_OUT_OF_MEM, // out of memory NJ_INTERNAL_ERR, // internal error NJ_SYNTAX_ERROR, // syntax error __NJ_FINISHED, // used internally, will never be reported } nj_result_t; // njInit: Initialize NanoJPEG. // For safety reasons, this should be called at least one time before using // using any of the other NanoJPEG functions. void njInit(void); // njDecode: Decode a JPEG image. // Decodes a memory dump of a JPEG file into internal buffers. // Parameters: // jpeg = The pointer to the memory dump. // size = The size of the JPEG file. // Return value: The error code in case of failure, or NJ_OK (zero) on success. nj_result_t njDecode(const void* jpeg, const int size); // njGetWidth: Return the width (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetWidth() is undefined. int njGetWidth(void); // njGetHeight: Return the height (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetHeight() is undefined. int njGetHeight(void); // njIsColor: Return 1 if the most recently decoded image is a color image // (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result // of njGetWidth() is undefined. int njIsColor(void); // njGetImage: Returns the decoded image data. // Returns a pointer to the most recently image. The memory layout it byte- // oriented, top-down, without any padding between lines. Pixels of color // images will be stored as three consecutive bytes for the red, green and // blue channels. This data format is thus compatible with the PGM or PPM // file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8. // If njDecode() failed, the result of njGetImage() is undefined. unsigned char* njGetImage(void); // njGetImageSize: Returns the size (in bytes) of the image data returned // by njGetImage(). If njDecode() failed, the result of njGetImageSize() is // undefined. int njGetImageSize(void); // njDone: Uninitialize NanoJPEG. // Resets NanoJPEG's internal state and frees all memory that has been // allocated at run-time by NanoJPEG. It is still possible to decode another // image after a njDone() call. void njDone(void); #endif//_NANOJPEG_H /////////////////////////////////////////////////////////////////////////////// // CONFIGURATION SECTION // // adjust the default settings for the NJ_ defines here // /////////////////////////////////////////////////////////////////////////////// #ifndef NJ_USE_LIBC #define NJ_USE_LIBC 1 #endif #ifndef NJ_USE_WIN32 #ifdef _MSC_VER #define NJ_USE_WIN32 (!NJ_USE_LIBC) #else #define NJ_USE_WIN32 0 #endif #endif #ifndef NJ_CHROMA_FILTER #define NJ_CHROMA_FILTER 1 #endif /////////////////////////////////////////////////////////////////////////////// // EXAMPLE PROGRAM // // just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC) // /////////////////////////////////////////////////////////////////////////////// #ifdef _NJ_EXAMPLE_PROGRAM #include #include #include int main(int argc, char* argv[]) { int size; char *buf; FILE *f; if (argc < 2) { printf("Usage: %s []\n", argv[0]); return 2; } f = fopen(argv[1], "rb"); if (!f) { printf("Error opening the input file.\n"); return 1; } fseek(f, 0, SEEK_END); size = (int) ftell(f); buf = (char*) malloc(size); fseek(f, 0, SEEK_SET); size = (int) fread(buf, 1, size, f); fclose(f); njInit(); if (njDecode(buf, size)) { free((void*)buf); printf("Error decoding the input file.\n"); return 1; } free((void*)buf); f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb"); if (!f) { printf("Error opening the output file.\n"); return 1; } fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight()); fwrite(njGetImage(), 1, njGetImageSize(), f); fclose(f); njDone(); return 0; } #endif /////////////////////////////////////////////////////////////////////////////// // IMPLEMENTATION SECTION // // you may stop reading here // /////////////////////////////////////////////////////////////////////////////// #ifndef _NJ_INCLUDE_HEADER_ONLY #ifdef _MSC_VER #define NJ_INLINE static __inline #define NJ_FORCE_INLINE static __forceinline #else #define NJ_INLINE static inline #define NJ_FORCE_INLINE static inline #endif #if NJ_USE_LIBC #include #include #define njAllocMem malloc #define njFreeMem free #define njFillMem memset #define njCopyMem memcpy #elif NJ_USE_WIN32 #include #define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size))) #define njFreeMem(block) ((void) LocalFree((HLOCAL) block)) NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm { mov edi, block mov al, value mov ecx, count rep stosb } } NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm { mov edi, dest mov esi, src mov ecx, count rep movsb } } #else extern void* njAllocMem(int size); extern void njFreeMem(void* block); extern void njFillMem(void* block, unsigned char byte, int size); extern void njCopyMem(void* dest, const void* src, int size); #endif typedef struct _nj_code { unsigned char bits, code; } nj_vlc_code_t; typedef struct _nj_cmp { int cid; int ssx, ssy; int width, height; int stride; int qtsel; int actabsel, dctabsel; int dcpred; unsigned char *pixels; } nj_component_t; typedef struct _nj_ctx { nj_result_t error; const unsigned char *pos; int size; int length; int width, height; int mbwidth, mbheight; int mbsizex, mbsizey; int ncomp; nj_component_t comp[3]; int qtused, qtavail; unsigned char qtab[4][64]; nj_vlc_code_t vlctab[4][65536]; int buf, bufbits; int block[64]; int rstinterval; unsigned char *rgb; } nj_context_t; static nj_context_t nj; static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; NJ_FORCE_INLINE unsigned char njClip(const int x) { return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x); } #define W1 2841 #define W2 2676 #define W3 2408 #define W5 1609 #define W6 1108 #define W7 565 #ifdef USING_R NJ_INLINE int shiftLeft(int i, int p) { unsigned u = *(unsigned *)(&i); u <<= p; return int(u); } #else #define shiftLeft(i,p) (i) << (p) #endif NJ_INLINE void njRowIDCT(int* blk) { int x0, x1, x2, x3, x4, x5, x6, x7, x8; if (!((x1 = shiftLeft(blk[4], 11)) | (x2 = blk[6]) | (x3 = blk[2]) | (x4 = blk[1]) | (x5 = blk[7]) | (x6 = blk[5]) | (x7 = blk[3]))) { blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = shiftLeft(blk[0], 3); return; } x0 = shiftLeft(blk[0], 11); x0 += 128; x8 = W7 * (x4 + x5); x4 = x8 + (W1 - W7) * x4; x5 = x8 - (W1 + W7) * x5; x8 = W3 * (x6 + x7); x6 = x8 - (W3 - W5) * x6; x7 = x8 - (W3 + W5) * x7; x8 = x0 + x1; x0 -= x1; x1 = W6 * (x3 + x2); x2 = x1 - (W2 + W6) * x2; x3 = x1 + (W2 - W6) * x3; x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; x5 -= x7; x7 = x8 + x3; x8 -= x3; x3 = x0 + x2; x0 -= x2; x2 = (181 * (x4 + x5) + 128) >> 8; x4 = (181 * (x4 - x5) + 128) >> 8; blk[0] = (x7 + x1) >> 8; blk[1] = (x3 + x2) >> 8; blk[2] = (x0 + x4) >> 8; blk[3] = (x8 + x6) >> 8; blk[4] = (x8 - x6) >> 8; blk[5] = (x0 - x4) >> 8; blk[6] = (x3 - x2) >> 8; blk[7] = (x7 - x1) >> 8; } NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) { int x0, x1, x2, x3, x4, x5, x6, x7, x8; if (!((x1 = shiftLeft(blk[8*4], 8)) | (x2 = blk[8*6]) | (x3 = blk[8*2]) | (x4 = blk[8*1]) | (x5 = blk[8*7]) | (x6 = blk[8*5]) | (x7 = blk[8*3]))) { x1 = njClip(((blk[0] + 32) >> 6) + 128); for (x0 = 8; x0; --x0) { *out = (unsigned char) x1; out += stride; } return; } x0 = shiftLeft(blk[0], 8); x0 += 8192; x8 = W7 * (x4 + x5) + 4; x4 = (x8 + (W1 - W7) * x4) >> 3; x5 = (x8 - (W1 + W7) * x5) >> 3; x8 = W3 * (x6 + x7) + 4; x6 = (x8 - (W3 - W5) * x6) >> 3; x7 = (x8 - (W3 + W5) * x7) >> 3; x8 = x0 + x1; x0 -= x1; x1 = W6 * (x3 + x2) + 4; x2 = (x1 - (W2 + W6) * x2) >> 3; x3 = (x1 + (W2 - W6) * x3) >> 3; x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; x5 -= x7; x7 = x8 + x3; x8 -= x3; x3 = x0 + x2; x0 -= x2; x2 = (181 * (x4 + x5) + 128) >> 8; x4 = (181 * (x4 - x5) + 128) >> 8; *out = njClip(((x7 + x1) >> 14) + 128); out += stride; *out = njClip(((x3 + x2) >> 14) + 128); out += stride; *out = njClip(((x0 + x4) >> 14) + 128); out += stride; *out = njClip(((x8 + x6) >> 14) + 128); out += stride; *out = njClip(((x8 - x6) >> 14) + 128); out += stride; *out = njClip(((x0 - x4) >> 14) + 128); out += stride; *out = njClip(((x3 - x2) >> 14) + 128); out += stride; *out = njClip(((x7 - x1) >> 14) + 128); } #define njThrow(e) do { nj.error = e; return; } while (0) #define njCheckError() do { if (nj.error) return; } while (0) static int njShowBits(int bits) { unsigned char newbyte; if (!bits) return 0; while (nj.bufbits < bits) { if (nj.size <= 0) { nj.buf = shiftLeft(nj.buf, 8) | 0xFF; nj.bufbits += 8; continue; } newbyte = *nj.pos++; nj.size--; nj.bufbits += 8; nj.buf = shiftLeft(nj.buf, 8) | newbyte; if (newbyte == 0xFF) { if (nj.size) { unsigned char marker = *nj.pos++; nj.size--; switch (marker) { case 0x00: case 0xFF: break; case 0xD9: nj.size = 0; break; default: if ((marker & 0xF8) != 0xD0) nj.error = NJ_SYNTAX_ERROR; else { nj.buf = shiftLeft(nj.buf, 8) | marker; nj.bufbits += 8; } } } else nj.error = NJ_SYNTAX_ERROR; } } return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1); } NJ_INLINE void njSkipBits(int bits) { if (nj.bufbits < bits) (void) njShowBits(bits); nj.bufbits -= bits; } NJ_INLINE int njGetBits(int bits) { int res = njShowBits(bits); njSkipBits(bits); return res; } NJ_INLINE void njByteAlign(void) { nj.bufbits &= 0xF8; } static void njSkip(int count) { nj.pos += count; nj.size -= count; nj.length -= count; if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR; } NJ_INLINE unsigned short njDecode16(const unsigned char *pos) { return (pos[0] << 8) | pos[1]; } static void njDecodeLength(void) { if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR); nj.length = njDecode16(nj.pos); if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR); njSkip(2); } NJ_INLINE void njSkipMarker(void) { njDecodeLength(); njSkip(nj.length); } NJ_INLINE void njDecodeSOF(void) { int i, ssxmax = 0, ssymax = 0; nj_component_t* c; njDecodeLength(); njCheckError(); if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR); if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED); nj.height = njDecode16(nj.pos+1); nj.width = njDecode16(nj.pos+3); if (!nj.width || !nj.height) njThrow(NJ_SYNTAX_ERROR); nj.ncomp = nj.pos[5]; njSkip(6); switch (nj.ncomp) { case 1: case 3: break; default: njThrow(NJ_UNSUPPORTED); } if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR); for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { c->cid = nj.pos[0]; if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR); if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR); if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR); njSkip(3); nj.qtused |= 1 << c->qtsel; if (c->ssx > ssxmax) ssxmax = c->ssx; if (c->ssy > ssymax) ssymax = c->ssy; } if (nj.ncomp == 1) { c = nj.comp; c->ssx = c->ssy = ssxmax = ssymax = 1; } nj.mbsizex = shiftLeft(ssxmax, 3); nj.mbsizey = shiftLeft(ssymax, 3); nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex; nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey; for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax; c->height = (nj.height * c->ssy + ssymax - 1) / ssymax; c->stride = nj.mbwidth * shiftLeft(c->ssx, 3); if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED); if (!(c->pixels = (unsigned char*) njAllocMem(c->stride * nj.mbheight * shiftLeft(c->ssy, 3)))) njThrow(NJ_OUT_OF_MEM); } if (nj.ncomp == 3) { nj.rgb = (unsigned char*) njAllocMem(nj.width * nj.height * nj.ncomp); if (!nj.rgb) njThrow(NJ_OUT_OF_MEM); } njSkip(nj.length); } NJ_INLINE void njDecodeDHT(void) { int codelen, currcnt, remain, spread, i, j; nj_vlc_code_t *vlc; static unsigned char counts[16]; njDecodeLength(); njCheckError(); while (nj.length >= 17) { i = nj.pos[0]; if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR); if (i & 0x02) njThrow(NJ_UNSUPPORTED); i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value for (codelen = 1; codelen <= 16; ++codelen) counts[codelen - 1] = nj.pos[codelen]; njSkip(17); vlc = &nj.vlctab[i][0]; remain = spread = 65536; for (codelen = 1; codelen <= 16; ++codelen) { spread >>= 1; currcnt = counts[codelen - 1]; if (!currcnt) continue; if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR); remain -= shiftLeft(currcnt, 16 - codelen); if (remain < 0) njThrow(NJ_SYNTAX_ERROR); for (i = 0; i < currcnt; ++i) { unsigned char code = nj.pos[i]; for (j = spread; j; --j) { vlc->bits = (unsigned char) codelen; vlc->code = code; ++vlc; } } njSkip(currcnt); } while (remain--) { vlc->bits = 0; ++vlc; } } if (nj.length) njThrow(NJ_SYNTAX_ERROR); } NJ_INLINE void njDecodeDQT(void) { int i; unsigned char *t; njDecodeLength(); njCheckError(); while (nj.length >= 65) { i = nj.pos[0]; if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR); nj.qtavail |= 1 << i; t = &nj.qtab[i][0]; for (i = 0; i < 64; ++i) t[i] = nj.pos[i + 1]; njSkip(65); } if (nj.length) njThrow(NJ_SYNTAX_ERROR); } NJ_INLINE void njDecodeDRI(void) { njDecodeLength(); njCheckError(); if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR); nj.rstinterval = njDecode16(nj.pos); njSkip(nj.length); } static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) { int value = njShowBits(16); int bits = vlc[value].bits; if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; } njSkipBits(bits); value = vlc[value].code; if (code) *code = (unsigned char) value; bits = value & 15; if (!bits) return 0; value = njGetBits(bits); if (value < (1 << (bits - 1))) value += (shiftLeft(-1, bits)) + 1; return value; } NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) { unsigned char code = 0; int value, coef = 0; njFillMem(nj.block, 0, sizeof(nj.block)); c->dcpred += njGetVLC(&nj.vlctab[c->dctabsel][0], NULL); nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0]; do { value = njGetVLC(&nj.vlctab[c->actabsel][0], &code); if (!code) break; // EOB if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR); coef += (code >> 4) + 1; if (coef > 63) njThrow(NJ_SYNTAX_ERROR); nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef]; } while (coef < 63); for (coef = 0; coef < 64; coef += 8) njRowIDCT(&nj.block[coef]); for (coef = 0; coef < 8; ++coef) njColIDCT(&nj.block[coef], &out[coef], c->stride); } NJ_INLINE void njDecodeScan(void) { int i, mbx, mby, sbx, sby; int rstcount = nj.rstinterval, nextrst = 0; nj_component_t* c; njDecodeLength(); njCheckError(); if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR); if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED); njSkip(1); for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR); if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR); c->dctabsel = nj.pos[1] >> 4; c->actabsel = (nj.pos[1] & 1) | 2; njSkip(2); } if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED); njSkip(nj.length); for (mbx = mby = 0;;) { for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) for (sby = 0; sby < c->ssy; ++sby) for (sbx = 0; sbx < c->ssx; ++sbx) { njDecodeBlock(c, &c->pixels[shiftLeft((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx, 3)]); njCheckError(); } if (++mbx >= nj.mbwidth) { mbx = 0; if (++mby >= nj.mbheight) break; } if (nj.rstinterval && !(--rstcount)) { njByteAlign(); i = njGetBits(16); if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR); nextrst = (nextrst + 1) & 7; rstcount = nj.rstinterval; for (i = 0; i < 3; ++i) nj.comp[i].dcpred = 0; } } nj.error = __NJ_FINISHED; } #if NJ_CHROMA_FILTER #define CF4A (-9) #define CF4B (111) #define CF4C (29) #define CF4D (-3) #define CF3A (28) #define CF3B (109) #define CF3C (-9) #define CF3X (104) #define CF3Y (27) #define CF3Z (-3) #define CF2A (139) #define CF2B (-11) #define CF(x) njClip(((x) + 64) >> 7) NJ_INLINE void njUpsampleH(nj_component_t* c) { const int xmax = c->width - 3; unsigned char *out, *lin, *lout; int x, y; out = (unsigned char*) njAllocMem((c->width * c->height) << 1); if (!out) njThrow(NJ_OUT_OF_MEM); lin = c->pixels; lout = out; for (y = c->height; y; --y) { lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]); lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]); lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]); for (x = 0; x < xmax; ++x) { lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]); lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]); } lin += c->stride; lout += c->width << 1; lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]); lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]); lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]); } c->width <<= 1; c->stride = c->width; njFreeMem((void*)c->pixels); c->pixels = out; } NJ_INLINE void njUpsampleV(nj_component_t* c) { const int w = c->width, s1 = c->stride, s2 = s1 + s1; unsigned char *out, *cin, *cout; int x, y; out = (unsigned char*) njAllocMem((c->width * c->height) << 1); if (!out) njThrow(NJ_OUT_OF_MEM); for (x = 0; x < w; ++x) { cin = &c->pixels[x]; cout = &out[x]; *cout = CF(CF2A * cin[0] + CF2B * cin[s1]); cout += w; *cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]); cout += w; *cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]); cout += w; cin += s1; for (y = c->height - 3; y; --y) { *cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]); cout += w; *cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]); cout += w; cin += s1; } cin += s1; *cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]); cout += w; *cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]); cout += w; *cout = CF(CF2A * cin[0] + CF2B * cin[-s1]); } c->height <<= 1; c->stride = c->width; njFreeMem((void*) c->pixels); c->pixels = out; } #else NJ_INLINE void njUpsample(nj_component_t* c) { int x, y, xshift = 0, yshift = 0; unsigned char *out, *lin, *lout; while (c->width < nj.width) { c->width <<= 1; ++xshift; } while (c->height < nj.height) { c->height <<= 1; ++yshift; } out = (unsigned char*) njAllocMem(c->width * c->height); if (!out) njThrow(NJ_OUT_OF_MEM); lin = c->pixels; lout = out; for (y = 0; y < c->height; ++y) { lin = &c->pixels[(y >> yshift) * c->stride]; for (x = 0; x < c->width; ++x) lout[x] = lin[x >> xshift]; lout += c->width; } c->stride = c->width; njFreeMem((void*) c->pixels); c->pixels = out; } #endif NJ_INLINE void njConvert(void) { int i; nj_component_t* c; for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { #if NJ_CHROMA_FILTER while ((c->width < nj.width) || (c->height < nj.height)) { if (c->width < nj.width) njUpsampleH(c); njCheckError(); if (c->height < nj.height) njUpsampleV(c); njCheckError(); } #else if ((c->width < nj.width) || (c->height < nj.height)) njUpsample(c); #endif if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR); } if (nj.ncomp == 3) { // convert to RGB int x, yy; unsigned char *prgb = nj.rgb; const unsigned char *py = nj.comp[0].pixels; const unsigned char *pcb = nj.comp[1].pixels; const unsigned char *pcr = nj.comp[2].pixels; for (yy = nj.height; yy; --yy) { for (x = 0; x < nj.width; ++x) { int y = py[x] << 8; int cb = pcb[x] - 128; int cr = pcr[x] - 128; *prgb++ = njClip((y + 359 * cr + 128) >> 8); *prgb++ = njClip((y - 88 * cb - 183 * cr + 128) >> 8); *prgb++ = njClip((y + 454 * cb + 128) >> 8); } py += nj.comp[0].stride; pcb += nj.comp[1].stride; pcr += nj.comp[2].stride; } } else if (nj.comp[0].width != nj.comp[0].stride) { // grayscale -> only remove stride unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride]; unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width]; int y; for (y = nj.comp[0].height - 1; y; --y) { njCopyMem(pout, pin, nj.comp[0].width); pin += nj.comp[0].stride; pout += nj.comp[0].width; } nj.comp[0].stride = nj.comp[0].width; } } void njInit(void) { njFillMem(&nj, 0, sizeof(nj_context_t)); } void njDone(void) { int i; for (i = 0; i < 3; ++i) if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels); if (nj.rgb) njFreeMem((void*) nj.rgb); njInit(); } nj_result_t njDecode(const void* jpeg, const int size) { njDone(); nj.pos = (const unsigned char*) jpeg; nj.size = size & 0x7FFFFFFF; if (nj.size < 2) return NJ_NO_JPEG; if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG; njSkip(2); while (!nj.error) { if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR; njSkip(2); switch (nj.pos[-1]) { case 0xC0: njDecodeSOF(); break; case 0xC4: njDecodeDHT(); break; case 0xDB: njDecodeDQT(); break; case 0xDD: njDecodeDRI(); break; case 0xDA: njDecodeScan(); break; case 0xFE: njSkipMarker(); break; default: if ((nj.pos[-1] & 0xF0) == 0xE0) njSkipMarker(); else return NJ_UNSUPPORTED; } } if (nj.error != __NJ_FINISHED) return nj.error; nj.error = NJ_OK; njConvert(); return nj.error; } int njGetWidth(void) { return nj.width; } int njGetHeight(void) { return nj.height; } int njIsColor(void) { return (nj.ncomp != 1); } unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; } int njGetImageSize(void) { return nj.width * nj.height * nj.ncomp; } #endif // _NJ_INCLUDE_HEADER_ONLYdcm2niix-1.0.20181125/console/ujpeg.h000066400000000000000000000046431337661136700167420ustar00rootroot00000000000000#ifndef _NANOJPEG_H #define _NANOJPEG_H // nj_result_t: Result codes for njDecode(). typedef enum _nj_result { NJ_OK = 0, // no error, decoding successful NJ_NO_JPEG, // not a JPEG file NJ_UNSUPPORTED, // unsupported format NJ_OUT_OF_MEM, // out of memory NJ_INTERNAL_ERR, // internal error NJ_SYNTAX_ERROR, // syntax error __NJ_FINISHED, // used internally, will never be reported } nj_result_t; // njInit: Initialize NanoJPEG. // For safety reasons, this should be called at least one time before using // using any of the other NanoJPEG functions. void njInit(void); // njDecode: Decode a JPEG image. // Decodes a memory dump of a JPEG file into internal buffers. // Parameters: // jpeg = The pointer to the memory dump. // size = The size of the JPEG file. // Return value: The error code in case of failure, or NJ_OK (zero) on success. nj_result_t njDecode(const void* jpeg, const int size); // njGetWidth: Return the width (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetWidth() is undefined. int njGetWidth(void); // njGetHeight: Return the height (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetHeight() is undefined. int njGetHeight(void); // njIsColor: Return 1 if the most recently decoded image is a color image // (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result // of njGetWidth() is undefined. int njIsColor(void); // njGetImage: Returns the decoded image data. // Returns a pointer to the most recently image. The memory layout it byte- // oriented, top-down, without any padding between lines. Pixels of color // images will be stored as three consecutive bytes for the red, green and // blue channels. This data format is thus compatible with the PGM or PPM // file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8. // If njDecode() failed, the result of njGetImage() is undefined. unsigned char* njGetImage(void); // njGetImageSize: Returns the size (in bytes) of the image data returned // by njGetImage(). If njDecode() failed, the result of njGetImageSize() is // undefined. int njGetImageSize(void); // njDone: Uninitialize NanoJPEG. // Resets NanoJPEG's internal state and frees all memory that has been // allocated at run-time by NanoJPEG. It is still possible to decode another // image after a njDone() call. void njDone(void); #endif//_NANOJPEG_H dcm2niix-1.0.20181125/cpfiles.command000077500000000000000000000005231337661136700167760ustar00rootroot00000000000000#!/bin/sh cd /Users/rorden/Documents/cocoa/dcm2niix/console #cp ni*.c ../qtGui #cp ni*.cpp ../qtGui #cp ni*.h ../qtGui #cp tinydir.h ../qtGui #cp ni*.c ../wxWidgets #cp ni*.cpp ../wxWidgets #cp ni*.h ../wxWidgets #cp tinydir.h ../wxWidgets cp *.c ../xcode/dcm2/core cp *.cpp ../xcode/dcm2/core cp *.h ../xcode/dcm2/core #myDisableMiniZdcm2niix-1.0.20181125/dcm_qa/000077500000000000000000000000001337661136700152325ustar00rootroot00000000000000dcm2niix-1.0.20181125/dcm_qa_nih/000077500000000000000000000000001337661136700160705ustar00rootroot00000000000000dcm2niix-1.0.20181125/dcm_qa_uih/000077500000000000000000000000001337661136700160775ustar00rootroot00000000000000dcm2niix-1.0.20181125/docs/000077500000000000000000000000001337661136700147365ustar00rootroot00000000000000dcm2niix-1.0.20181125/docs/CMakeLists.txt000066400000000000000000000023361337661136700175020ustar00rootroot00000000000000# Copyright 2016, The dcm2niix contributors # # This program is free software: you can redistribute it and/or modify it under # the terms of the new BSD license. See the license.txt file or visit # https://opensource.org/licenses/BSD-3-Clause for details. find_program(SPHINX_EXECUTABLE NAMES sphinx-build HINTS $ENV{SPHINX_DIR} PATH_SUFFIXES bin DOC "Sphinx documentation generator") if (NOT SPHINX_EXECUTABLE) message(FATAL_ERROR "sphinx-build executable not found") endif () set(SPHINX_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/source") set(SPHINX_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/build") set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/doctrees") set(SPHINX_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/man") configure_file("${SPHINX_SOURCE_DIR}/conf.py" "${SPHINX_BUILD_DIR}/conf.py" COPYONLY) add_custom_target(build-man ALL COMMAND ${SPHINX_EXECUTABLE} -b man -c "${SPHINX_BUILD_DIR}" -d "${SPHINX_CACHE_DIR}" "${SPHINX_SOURCE_DIR}" "${SPHINX_OUTPUT_DIR}" COMMENT "Generating man pages with Sphinx") install(DIRECTORY "${SPHINX_OUTPUT_DIR}/" DESTINATION "share/man/man1") dcm2niix-1.0.20181125/docs/source/000077500000000000000000000000001337661136700162365ustar00rootroot00000000000000dcm2niix-1.0.20181125/docs/source/conf.py000066400000000000000000000234351337661136700175440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # dcm2niix documentation build configuration file, created by # sphinx-quickstart on Tue Dec 20 16:22:24 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'dcm2niix' copyright = u'2016 The dcm2niix contributors' author = u'The dcm2niix contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = u'1.0' # The full version, including alpha/beta/rc tags. release = u'1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = u'dcm2niix v1.0' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'dcm2niixdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'dcm2niix.tex', u'dcm2niix Documentation', author, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('dcm2niix', 'dcm2niix', u'DICOM to NifTI converter', ['This manual was developed by Ghislain Antony Vaillant and is maintained by the community'], 1), ('dcm2niibatch', 'dcm2niibatch', u'DICOM to NifTI batch converter', ['This manual was developed and is maintained by Benjamin Irving '], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'dcm2niix', u'dcm2niix Documentation', author, 'dcm2niix', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False dcm2niix-1.0.20181125/docs/source/dcm2niibatch.rst000066400000000000000000000045411337661136700213230ustar00rootroot00000000000000:orphan: dcm2niibatch manual =================== Synopsis -------- **dcm2niixbatch** <*configuration-file*> Description ----------- Most medical imaging devices save images in some variation of the popular DICOM format. However, most scientific tools expect medical images to be stored in the comparatively simpler NIfTI format. **dcm2niix** is designed to perform such conversion from DICOM to NIfTI with a simple command-line interface. **dcm2niibatch** acts as a wrapper around **dcm2niix** and allows the processing of multiple DICOM sequences by specifying the list of files and settings in a yaml text file. The makes processing a dataset of DICOM scans simpler and more easily repeatable. In addition, yaml files are designed to be both human and machine readable and a script can easily be written in many programming languages to automatically create a yaml file based on an existing folder structure. Please be advised that **dcm2niix** and **dcm2niibatch** have been developed for research purposes only and should not be considered a clinical tool. Configuration file format ------------------------- Perform a batch conversion of multiple dicoms using **dcm2niibatch**, which is run by passing a configuration file e.g *dcm2niibatch batch_config.yml* The configuration file should be in yaml format as shown in example *batch_config.yaml* .. code-block:: yaml Options: isGz: false isFlipY: false isVerbose: false isCreateBIDS: false isOnlySingleFile: false Files: - in_dir: /path/to/first/folder out_dir: /path/to/output/folder filename: dcemri - in_dir: /path/to/second/folder out_dir: /path/to/output/folder filename: fa3 You can add as many files as you want to convert as long as this structure stays consistent. Note that a dash must separate each file. in_dir Path to the dicom files out_dir Path to save the nifti file filename File name of nifti file to save See also -------- :manpage:`dcm2niix(1)` Licensing --------- Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty. dcm2niix-1.0.20181125/docs/source/dcm2niix.rst000066400000000000000000000064701337661136700205140ustar00rootroot00000000000000:orphan: dcm2niix manual =============== Synopsis -------- **dcm2niix** [*options*] <*sourcedir*> Description ----------- Most medical imaging devices save images in some variation of the popular DICOM format. However, most scientific tools expect medical images to be stored in the comparatively simpler NIfTI format. **dcm2niix** is designed to perform such conversion from DICOM to NIfTI with a simple command-line interface. Please be advised that **dcm2niix** has been developed for research purposes only and should not be considered a clinical tool. Options ------- -1..-9 gz compression level (1=fastest..9=smallest, default 6) -b Save additional BIDS metadata to a side-car .json file. The "i"nput-only option reads DICOMs but saves neither BIDS nor NIfTI. -ba anonymize BIDS -f Format string for the output filename(s). The following specifiers are supported: - %a, antenna (coil) name - %b, basename (filename of 1st DICOM file) - %c, comments - %d, description - %e, echo number - %f, folder name - %i, patient ID - %j, seriesInstanceUID - %k, studyInstanceUID - %m, manufacturer - %n, patient name - %p, protocol - %r, instance number (of 1st DICOM file) - %s, series number - %t, time - %u, acquisition number - %v, vendor - %x, study ID - %z, sequence name The default format string is "%p_%e_%4s". -i Ignore derived, localizer and 2D images. -l Losslessly scale 16-bit integers to use maximal dynamic range. -m Merge slices from the same series regardless of study time, echo, coil, orientation, etc... -n Only convert this series number. Provide a negative number for listing of series numbers in input folder. -o Output directory where the converted files should be saved. If unspecified, the files are saved within the specified source directory. -p Use Philips precise float (rather than display) scaling. -r Rename instead of convert DICOMs. Useful for organizing images. -s Convert a single file only. -t Save patient details as text notes. -u Update check: attempts to see if newer version is available. -v Enable verbose output. "n" for succinct, "y" for verbose, "h" for high verbosity -x Crop images. This will attempt to remove excess neck from 3D acquisitions. -z Desired compression method. The "y"es option uses the external program pigz if available. The "i" option compresses the image using the slower built-in compression routines. Licensing --------- Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty. The dcm2niix project is distributed under the BSD 2-Clause License.dcm2niix-1.0.20181125/docs/source/index.rst000066400000000000000000000006551337661136700201050ustar00rootroot00000000000000.. dcm2niix documentation master file, created by sphinx-quickstart on Tue Dec 20 16:22:24 2016. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to dcm2niix's documentation! ==================================== Contents: .. toctree:: :maxdepth: 2 Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` dcm2niix-1.0.20181125/license.txt000066400000000000000000000037101337661136700161720ustar00rootroot00000000000000The following files are from different authors and have their own licenses nifti.h, nifti1_io.h/nifti1_io_core.cpp is public domain http://niftilib.sourceforge.net http://sourceforge.net/projects/niftilib/files/latest/download ujpeg.h/ujpeg.cpp uses the MIT license (see file for license text) http://keyj.emphy.de/nanojpeg/ miniz.c is public domain (http://unlicense.org) https://code.google.com/p/miniz/ --- The Software has been developed for research purposes only and is not a clinical tool Copyright (c) 2014-2016 Chris Rorden. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright owner nor the name of this project (dcm2niix) may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT OWNER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.dcm2niix-1.0.20181125/rmfiles.command000077500000000000000000000004761337661136700170210ustar00rootroot00000000000000#!/bin/sh #remove duplicates cd /Users/rorden/Documents/cocoa/dcm2niix rm ./qtGui/ni*.cpp rm ./qtGui/ni*.h rm ./qtGui/tinydir.h rm ./wxWidgets/ni*.cpp rm ./wxWidgets/ni*.h rm ./wxWidgets/tinydir.h rm ./xcode/dcm2/core/*.c rm ./xcode/dcm2/core/*.cpp rm ./xcode/dcm2/core/*.h rm ./xcode/dcm2/core/tinydir.h