diff --git a/include/DICOMRead.h b/include/DICOMRead.h index c13fda0f70e..3d6c8cfaac1 100644 --- a/include/DICOMRead.h +++ b/include/DICOMRead.h @@ -27,6 +27,9 @@ #include "dicom_objects.h" #include "condition.h" +#include "nii_dicom.h" +#include "nii_dicom_batch.h" + #define NUMBEROFTAGS 24 #define SHORTSIZE 16 #ifndef INTSIZE @@ -59,6 +62,7 @@ typedef unsigned short int BOOL; char *SDCMStatusFile = 0; char *SDCMListFile = 0; int UseDICOMRead2 = 1; // use new dicom reader by default +int UseDICOMRead3 = 0; /* These variables allow the user to change the first tag checked to get the slice thickness. This is needed with siemens mag res angiogram (MRAs) */ @@ -69,6 +73,7 @@ int AutoSliceResElTag = 0; // automatically determine which tag to use based on extern char *SDCMStatusFile; extern char *SDCMListFile; extern int UseDICOMRead2; +extern int UseDICOMRead3; extern long SliceResElTag1; extern long SliceResElTag2; extern int AutoSliceResElTag; @@ -286,6 +291,7 @@ int CompareDCMFileInfo(const void *a, const void *b); int DCMCountFrames(DICOMInfo **dcmfi_list, int nlist); int DCMSliceDir(DICOMInfo **dcmfi_list, int nlist); MRI *DICOMRead2(const char *dcmfile, int LoadVolume); +MRIFSSTRUCT *DICOMRead3(const char *dcmfile, int LoadVolume); DCM_ELEMENT *GetElementFromFile(const char *dicomfile, long grpid, long elid); int AllocElementData(DCM_ELEMENT *e); diff --git a/mri_convert/CMakeLists.txt b/mri_convert/CMakeLists.txt index 9844755fca3..e9dbb73d1d3 100644 --- a/mri_convert/CMakeLists.txt +++ b/mri_convert/CMakeLists.txt @@ -3,7 +3,7 @@ project(mri_convert) # ATH: temporarily turning off this warning add_compile_options(-Wno-self-assign) -include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom) +include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom ${CMAKE_SOURCE_DIR}/packages/dcm2niix) add_executable(mri_convert mri_convert.cpp) add_help(mri_convert mri_convert.help.xml) diff --git a/mri_convert/mri_convert.cpp b/mri_convert/mri_convert.cpp index e320b122735..924896216b8 100644 --- a/mri_convert/mri_convert.cpp +++ b/mri_convert/mri_convert.cpp @@ -1141,6 +1141,10 @@ int main(int argc, char *argv[]) forced_in_type = string_to_type(in_type_string); force_in_type_flag = TRUE; } + else if (strcmp(argv[i], "-dicomread3") == 0) + { + UseDICOMRead3 = 1; + } else if(strcmp(argv[i], "-dicomread2") == 0) { UseDICOMRead2 = 1; diff --git a/mri_otl/CMakeLists.txt b/mri_otl/CMakeLists.txt index f9ddbdef4fe..49490ba389b 100644 --- a/mri_otl/CMakeLists.txt +++ b/mri_otl/CMakeLists.txt @@ -1,6 +1,6 @@ project(mri_otl) -include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom) +include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom ${CMAKE_SOURCE_DIR}/packages/dcm2niix) add_executable(list_otl_labels list_otl_labels.cpp) target_link_libraries(list_otl_labels utils) diff --git a/mri_parse_sdcmdir/CMakeLists.txt b/mri_parse_sdcmdir/CMakeLists.txt index 89b659a093a..6e985dc21bb 100644 --- a/mri_parse_sdcmdir/CMakeLists.txt +++ b/mri_parse_sdcmdir/CMakeLists.txt @@ -1,6 +1,6 @@ project(mri_parse_sdcmdir) -include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom) +include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom ${CMAKE_SOURCE_DIR}/packages/dcm2niix) add_executable(mri_parse_sdcmdir mri_parse_sdcmdir.cpp) target_link_libraries(mri_parse_sdcmdir utils) diff --git a/mri_probedicom/CMakeLists.txt b/mri_probedicom/CMakeLists.txt index 6994bc91f78..8896927a420 100644 --- a/mri_probedicom/CMakeLists.txt +++ b/mri_probedicom/CMakeLists.txt @@ -6,6 +6,7 @@ if(OPENGL_FOUND) ${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom ${CMAKE_SOURCE_DIR}/packages/glut + ${CMAKE_SOURCE_DIR}/packages/dcm2niix ${X11_INCLUDE_DIR} ) diff --git a/mri_watershed/CMakeLists.txt b/mri_watershed/CMakeLists.txt index 445e923ab78..6e9001a19d2 100644 --- a/mri_watershed/CMakeLists.txt +++ b/mri_watershed/CMakeLists.txt @@ -1,6 +1,6 @@ project(mri_watershed) -include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom) +include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom ${CMAKE_SOURCE_DIR}/packages/dcm2niix) add_executable(mri_watershed mri_watershed.cpp) add_help(mri_watershed mri_watershed.help.xml) diff --git a/packages/CMakeLists.txt b/packages/CMakeLists.txt index 8fd9e75cf05..0b912f0bbf6 100644 --- a/packages/CMakeLists.txt +++ b/packages/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectories( xml2 minc dicom + dcm2niix cephes netcdf tetgen diff --git a/packages/dcm2niix/CMakeLists.txt b/packages/dcm2niix/CMakeLists.txt new file mode 100644 index 00000000000..df054a10fd1 --- /dev/null +++ b/packages/dcm2niix/CMakeLists.txt @@ -0,0 +1,233 @@ +cmake_minimum_required(VERSION 2.8.11) + +project(dcm2niix) + +# Option Choose whether to use static runtime +include(ucm.cmake) +option(USE_STATIC_RUNTIME "Use static runtime" ON) +if(USE_STATIC_RUNTIME) + ucm_set_runtime(STATIC) +else() + ucm_set_runtime(DYNAMIC) +endif() + +# Basic CMake build settings +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING + "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel") +endif() + +if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + # using Clang + add_definitions(-fno-caret-diagnostics) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip") +elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") + # using GCC + if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 7.1.0)) + add_definitions(-Wno-format-overflow) # available since GCC 7.1.0 + endif() + if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.5.0)) + add_definitions(-Wno-unused-result) # available since GCC 4.5.0 + endif() + if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.8.0)) + add_definitions(-fno-diagnostics-show-caret) # available since GCC 4.8.0 + endif() +elseif(MSVC) + # using Visual Studio C++ + add_definitions(-D_CRT_SECURE_NO_DEPRECATE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4018") # '<': signed/unsigned mismatch + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4068") # unknown pragma + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4101") # unreferenced local variable + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244") # 'initializing': conversion from 'double' to 'int', possible loss of data + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") # 'initializing': conversion from 'size_t' to 'int', possible loss of data + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4305") # 'argument': truncation from 'double' to 'float' + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4308") # negative integral constant converted to unsigned type + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4334") # '<<': result of 32-bit shift implicitly converted to 64 bits + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 'uint32_t' : forcing value to bool 'true' or 'false' + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819") # The file contains a character that cannot be represented in the current code page + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4996") # 'access': The POSIX name for this item is deprecated + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:8388608") # set "Stack Reserve Size" to 8MB (default value is 1MB) +endif() + +# Compiler dependent flags +include (CheckCXXCompilerFlag) +if(UNIX) + check_cxx_compiler_flag(-march=armv8-a+crc ARM_CRC) + if(ARM_CRC) + # wrong answer for Apple Silicon: check_cxx_compiler_flag(-msse2 HAS_SSE2) + else() + check_cxx_compiler_flag(-msse2 HAS_SSE2) + if(HAS_SSE2) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -mfpmath=sse") + endif() + endif() +endif() + +set(PROGRAMS dcm2niix) + +set(DCM2NIIX_SRCS + main_console.cpp + nii_dicom.cpp + jpg_0XC3.cpp + ujpeg.cpp + nifti1_io_core.cpp + nii_foreign.cpp + nii_ortho.cpp + nii_dicom_batch.cpp) + +option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) +if(USE_JPEGLS) + add_definitions(-DmyEnableJPEGLS) + if(MSVC) + add_definitions(-DCHARLS_STATIC) + endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") + + set(CHARLS_SRCS + charls/jpegls.cpp + charls/jpegmarkersegment.cpp + charls/interface.cpp + charls/jpegstreamwriter.cpp + charls/jpegstreamreader.cpp) + add_executable(dcm2niix ${DCM2NIIX_SRCS} ${CHARLS_SRCS}) +else() + add_executable(dcm2niix ${DCM2NIIX_SRCS}) +endif() + +set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") +set_property(CACHE ZLIB_IMPLEMENTATION PROPERTY STRINGS "Miniz;System;Custom") +if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "Miniz") + if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "System") + set(ZLIB_ROOT ${ZLIB_ROOT} CACHE PATH "Specify custom zlib root directory.") + if(NOT ZLIB_ROOT) + message(FATAL_ERROR "ZLIB_ROOT needs to be set to locate custom zlib!") + endif() + endif() + find_package(ZLIB REQUIRED) + add_definitions(-DmyDisableMiniZ) + target_include_directories(dcm2niix PRIVATE ${ZLIB_INCLUDE_DIRS}) + target_link_libraries(dcm2niix ${ZLIB_LIBRARIES}) +endif() + +option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) +if(USE_TURBOJPEG) + find_package(PkgConfig REQUIRED) + pkg_check_modules(TURBOJPEG REQUIRED libturbojpeg) + add_definitions(-DmyTurboJPEG) + target_include_directories(dcm2niix PRIVATE ${TURBOJPEG_INCLUDEDIR}) + target_link_libraries(dcm2niix ${TURBOJPEG_LIBRARIES}) +endif() + +option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) +if(USE_JASPER) + find_package(Jasper REQUIRED) + add_definitions(-DmyEnableJasper) + target_include_directories(dcm2niix PRIVATE ${JASPER_INCLUDE_DIR}) + target_link_libraries(dcm2niix ${JASPER_LIBRARIES}) +endif() + +option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) +if(USE_OPENJPEG) + set(OpenJPEG_DIR "${OpenJPEG_DIR}" CACHE PATH "Path to OpenJPEG configuration file" FORCE) + + find_package(OpenJPEG REQUIRED) + + if(WIN32) + if(BUILD_SHARED_LIBS) + add_definitions(-DOPJ_EXPORTS) + else() + add_definitions(-DOPJ_STATIC) + endif() + endif() + + target_include_directories(dcm2niix PRIVATE ${OPENJPEG_INCLUDE_DIRS}) + target_link_libraries(dcm2niix ${OPENJPEG_LIBRARIES}) +else () + add_definitions(-DmyDisableOpenJPEG) +endif() + +option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) +if(BATCH_VERSION) + set(DCM2NIIBATCH_SRCS + main_console_batch.cpp + nii_dicom.cpp + jpg_0XC3.cpp + ujpeg.cpp + nifti1_io_core.cpp + nii_foreign.cpp + nii_ortho.cpp + nii_dicom_batch.cpp) + + if(USE_JPEGLS) + add_executable(dcm2niibatch ${DCM2NIIBATCH_SRCS} ${CHARLS_SRCS}) + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + add_executable(dcm2niibatch ${DCM2NIIBATCH_SRCS}) + endif() + + set(YAML-CPP_DIR ${YAML-CPP_DIR} CACHE PATH "Path to yaml-cpp configuration file" FORCE) + + find_package(YAML-CPP REQUIRED) + target_include_directories(dcm2niibatch PRIVATE ${YAML_CPP_INCLUDE_DIR}) + target_link_libraries(dcm2niibatch ${YAML_CPP_LIBRARIES}) + + if(ZLIB_FOUND) + target_include_directories(dcm2niibatch PRIVATE ${ZLIB_INCLUDE_DIRS}) + target_link_libraries(dcm2niibatch ${ZLIB_LIBRARIES}) + endif() + + if(TURBOJPEG_FOUND) + target_include_directories(dcm2niibatch PRIVATE ${TURBOJPEG_INCLUDEDIR}) + target_link_libraries(dcm2niibatch ${TURBOJPEG_LIBRARIES}) + endif() + + if(JASPER_FOUND) + target_include_directories(dcm2niibatch PRIVATE ${JASPER_INCLUDE_DIR}) + target_link_libraries(dcm2niibatch ${JASPER_LIBRARIES}) + endif() + + if(OPENJPEG_FOUND) + target_include_directories(dcm2niibatch PRIVATE ${OPENJPEG_INCLUDE_DIRS}) + target_link_libraries(dcm2niibatch ${OPENJPEG_LIBRARIES}) + endif() + + list(APPEND PROGRAMS dcm2niibatch) +endif() + + +if(APPLE) + message("-- Adding Apple plist") + set_target_properties(dcm2niix PROPERTIES LINK_FLAGS "-Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_SOURCE_DIR}/Info.plist") + #Apple notarization requires a Info.plist + # For .app bundles, the Info.plist is a separate file, for executables it is appended as a section + #you can check that the Info.plist section has been inserted with either of these commands + # otool -l ./dcm2niix | grep info_plist -B1 -A10 + # launchctl plist ./dcm2niix +endif() + +### start of addition for FREESURFER +# we should not need this if we use target_compile_definitions() +#target_link_libraries(dcm2niix nifti ${ZLIB_LIBRARIES}) + +set(LIBRARY dcm2niixfs) +add_library(dcm2niixfs STATIC + dcm2fsWrapper.cpp + nii_dicom.cpp + jpg_0XC3.cpp + ujpeg.cpp + nifti1_io_core.cpp + nii_foreign.cpp + nii_ortho.cpp + nii_dicom_batch.cpp) +target_compile_definitions(dcm2niixfs PUBLIC -DMGH_FREESURFER -DUSING_MGH_NIFTI_IO) + +set(DCM2NIIXFSEXE dcm2niixfsexe) +add_executable(${DCM2NIIXFSEXE} dcm2fsmain.cpp) +target_link_libraries(${DCM2NIIXFSEXE} dcm2niixfs nifti ${ZLIB_LIBRARIES}) +### end of addition for FREESURFER + +install(TARGETS ${PROGRAMS} DESTINATION bin) +### additions for FREESURFER +install(TARGETS ${LIBRARY} DESTINATION bin) +install(TARGETS ${DCM2NIIXFSEXE} DESTINATION bin) diff --git a/packages/dcm2niix/Info.plist b/packages/dcm2niix/Info.plist new file mode 100644 index 00000000000..d470b8ed58d --- /dev/null +++ b/packages/dcm2niix/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleExecutable + dcm2niix + CFBundleIdentifier + com.mricro.dcm2niix + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + dcm2niix + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + CFBundleSupportedPlatforms + + MacOSX + + CFBundlePackageType + APPL + + diff --git a/packages/dcm2niix/charls/License.txt b/packages/dcm2niix/charls/License.txt new file mode 100644 index 00000000000..cb5c2b29920 --- /dev/null +++ b/packages/dcm2niix/charls/License.txt @@ -0,0 +1,29 @@ +https://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. diff --git a/packages/dcm2niix/charls/README.md b/packages/dcm2niix/charls/README.md new file mode 100644 index 00000000000..82f45adc778 --- /dev/null +++ b/packages/dcm2niix/charls/README.md @@ -0,0 +1,82 @@ +[![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. diff --git a/packages/dcm2niix/charls/charls.h b/packages/dcm2niix/charls/charls.h new file mode 100644 index 00000000000..031536779ca --- /dev/null +++ b/packages/dcm2niix/charls/charls.h @@ -0,0 +1,91 @@ +/* + (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 diff --git a/packages/dcm2niix/charls/colortransform.h b/packages/dcm2niix/charls/colortransform.h new file mode 100644 index 00000000000..e5ec0c6547b --- /dev/null +++ b/packages/dcm2niix/charls/colortransform.h @@ -0,0 +1,196 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/constants.h b/packages/dcm2niix/charls/constants.h new file mode 100644 index 00000000000..10f9337f28d --- /dev/null +++ b/packages/dcm2niix/charls/constants.h @@ -0,0 +1,17 @@ +// +// 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 diff --git a/packages/dcm2niix/charls/context.h b/packages/dcm2niix/charls/context.h new file mode 100644 index 00000000000..3c6d8bdf038 --- /dev/null +++ b/packages/dcm2niix/charls/context.h @@ -0,0 +1,117 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/contextrunmode.h b/packages/dcm2niix/charls/contextrunmode.h new file mode 100644 index 00000000000..b3f32cfe899 --- /dev/null +++ b/packages/dcm2niix/charls/contextrunmode.h @@ -0,0 +1,109 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/decoderstrategy.h b/packages/dcm2niix/charls/decoderstrategy.h new file mode 100644 index 00000000000..b80d6744b81 --- /dev/null +++ b/packages/dcm2niix/charls/decoderstrategy.h @@ -0,0 +1,311 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/defaulttraits.h b/packages/dcm2niix/charls/defaulttraits.h new file mode 100644 index 00000000000..433c839828d --- /dev/null +++ b/packages/dcm2niix/charls/defaulttraits.h @@ -0,0 +1,147 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/encoderstrategy.h b/packages/dcm2niix/charls/encoderstrategy.h new file mode 100644 index 00000000000..fa7c24dd419 --- /dev/null +++ b/packages/dcm2niix/charls/encoderstrategy.h @@ -0,0 +1,200 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/interface.cpp b/packages/dcm2niix/charls/interface.cpp new file mode 100644 index 00000000000..c833706943f --- /dev/null +++ b/packages/dcm2niix/charls/interface.cpp @@ -0,0 +1,245 @@ +// +// (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); + } + } +} diff --git a/packages/dcm2niix/charls/jlscodecfactory.h b/packages/dcm2niix/charls/jlscodecfactory.h new file mode 100644 index 00000000000..53384de311c --- /dev/null +++ b/packages/dcm2niix/charls/jlscodecfactory.h @@ -0,0 +1,23 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/jpegimagedatasegment.h b/packages/dcm2niix/charls/jpegimagedatasegment.h new file mode 100644 index 00000000000..980efc0a5a1 --- /dev/null +++ b/packages/dcm2niix/charls/jpegimagedatasegment.h @@ -0,0 +1,29 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/jpegls.cpp b/packages/dcm2niix/charls/jpegls.cpp new file mode 100644 index 00000000000..72deb7dfb65 --- /dev/null +++ b/packages/dcm2niix/charls/jpegls.cpp @@ -0,0 +1,185 @@ +// +// (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; diff --git a/packages/dcm2niix/charls/jpegmarkercode.h b/packages/dcm2niix/charls/jpegmarkercode.h new file mode 100644 index 00000000000..69aa85efbd3 --- /dev/null +++ b/packages/dcm2niix/charls/jpegmarkercode.h @@ -0,0 +1,40 @@ +// +// (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. +}; diff --git a/packages/dcm2niix/charls/jpegmarkersegment.cpp b/packages/dcm2niix/charls/jpegmarkersegment.cpp new file mode 100644 index 00000000000..35551aed921 --- /dev/null +++ b/packages/dcm2niix/charls/jpegmarkersegment.cpp @@ -0,0 +1,116 @@ +// +// (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)); +} diff --git a/packages/dcm2niix/charls/jpegmarkersegment.h b/packages/dcm2niix/charls/jpegmarkersegment.h new file mode 100644 index 00000000000..568cf9a4328 --- /dev/null +++ b/packages/dcm2niix/charls/jpegmarkersegment.h @@ -0,0 +1,76 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/jpegsegment.h b/packages/dcm2niix/charls/jpegsegment.h new file mode 100644 index 00000000000..4e52c59c567 --- /dev/null +++ b/packages/dcm2niix/charls/jpegsegment.h @@ -0,0 +1,28 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/jpegstreamreader.cpp b/packages/dcm2niix/charls/jpegstreamreader.cpp new file mode 100644 index 00000000000..052d3ec67f3 --- /dev/null +++ b/packages/dcm2niix/charls/jpegstreamreader.cpp @@ -0,0 +1,409 @@ +// +// (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); + } +} diff --git a/packages/dcm2niix/charls/jpegstreamreader.h b/packages/dcm2niix/charls/jpegstreamreader.h new file mode 100644 index 00000000000..6d14b268fe6 --- /dev/null +++ b/packages/dcm2niix/charls/jpegstreamreader.h @@ -0,0 +1,75 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/jpegstreamwriter.cpp b/packages/dcm2niix/charls/jpegstreamwriter.cpp new file mode 100644 index 00000000000..a0d0103044a --- /dev/null +++ b/packages/dcm2niix/charls/jpegstreamwriter.cpp @@ -0,0 +1,91 @@ +// +// (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)); +} diff --git a/packages/dcm2niix/charls/jpegstreamwriter.h b/packages/dcm2niix/charls/jpegstreamwriter.h new file mode 100644 index 00000000000..dd8ffdb66c4 --- /dev/null +++ b/packages/dcm2niix/charls/jpegstreamwriter.h @@ -0,0 +1,111 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/lookuptable.h b/packages/dcm2niix/charls/lookuptable.h new file mode 100644 index 00000000000..5ef59eeddab --- /dev/null +++ b/packages/dcm2niix/charls/lookuptable.h @@ -0,0 +1,75 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/losslesstraits.h b/packages/dcm2niix/charls/losslesstraits.h new file mode 100644 index 00000000000..db620af2382 --- /dev/null +++ b/packages/dcm2niix/charls/losslesstraits.h @@ -0,0 +1,135 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/processline.h b/packages/dcm2niix/charls/processline.h new file mode 100644 index 00000000000..464a8c37907 --- /dev/null +++ b/packages/dcm2niix/charls/processline.h @@ -0,0 +1,352 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/publictypes.h b/packages/dcm2niix/charls/publictypes.h new file mode 100644 index 00000000000..839c7f6dbb0 --- /dev/null +++ b/packages/dcm2niix/charls/publictypes.h @@ -0,0 +1,343 @@ +/* + (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 diff --git a/packages/dcm2niix/charls/scan.h b/packages/dcm2niix/charls/scan.h new file mode 100644 index 00000000000..8c3383e0bf9 --- /dev/null +++ b/packages/dcm2niix/charls/scan.h @@ -0,0 +1,854 @@ +// +// (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 diff --git a/packages/dcm2niix/charls/util.h b/packages/dcm2niix/charls/util.h new file mode 100644 index 00000000000..17914d28d30 --- /dev/null +++ b/packages/dcm2niix/charls/util.h @@ -0,0 +1,219 @@ +// +// (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 diff --git a/packages/dcm2niix/dcm2fsWrapper.cpp b/packages/dcm2niix/dcm2fsWrapper.cpp new file mode 100644 index 00000000000..98894ed47f7 --- /dev/null +++ b/packages/dcm2niix/dcm2fsWrapper.cpp @@ -0,0 +1,123 @@ +#include + +#include "nii_dicom.h" +#include "dcm2fsWrapper.h" + +struct TDCMopts dcm2fsWrapper::tdcmOpts; +//MRIFSSTRUCT dcm2fsWrapper::mrifsStruct; + +/* oct06 version +isIgnoreTriggerTimes = false +isTestx0021x105E = false +isAddNamePostFixes = true +isSaveNativeEndian = true +isOneDirAtATime = false +isRenameNotConvert = false +isSave3D = false +isGz = false +isPipedGz = false +isFlipY = true +isCreateBIDS = true +isSortDTIbyBVal = false +isAnonymizeBIDS = true +isOnlyBIDS = false +isCreateText = false +isForceOnsetTimes = true +isIgnoreDerivedAnd2D = false +isPhilipsFloatNotDisplayScaling = true +isTiltCorrect = true +isRGBplanar = false +isOnlySingleFile = false +isForceStackDCE = true +isIgnoreSeriesInstanceUID = false // if true d.seriesUidCrc = d.seriesNum; +isRotate3DAcq = true +isCrop = false +saveFormat = 0 +isMaximize16BitRange = 2 +isForceStackSameSeries = 2 +nameConflictBehavior = 2 +isVerbose = 0 +isProgress = 0 +compressFlag = 0 +dirSearchDepth = 5 +gzLevel = 6 +filename = "%s_%p\000%t_%s" +outdir = "/space/papancha/1/users/yh887/fs_test/dcm2niix/test2" +indir = "/autofs/space/sulc_001/users/xdicom/dicom/INTEROPERABILITY" +pigzname = '\000' +optsname = "/homes/7/yh887/.dcm2nii.ini" +indirParent = "INTEROPERABILITY", '\000' , +imageComments = "\000\325\377\377\377\177\000\000`\325\377\377\377\177\000\000&\260be\000\000\000", +seriesNumber = {0 }, +numSeries = 0 + */ +void dcm2fsWrapper::setOpts(const char* dcmindir, const char* niioutdir) +{ + memset(&tdcmOpts, 0, sizeof(tdcmOpts)); + setDefaultOpts(&tdcmOpts, NULL); + + if (dcmindir != NULL) + strcpy(tdcmOpts.indir, dcmindir); + if (niioutdir != NULL) + strcpy(tdcmOpts.outdir, niioutdir); + + tdcmOpts.isRotate3DAcq = false; + tdcmOpts.isFlipY = false; + tdcmOpts.isIgnoreSeriesInstanceUID = true; + tdcmOpts.isCreateBIDS = false; + tdcmOpts.isGz = false; + //tdcmOpts.isForceStackSameSeries = 1; // merge 2D slice '-m y' + tdcmOpts.isForceStackDCE = false; + //tdcmOpts.isForceOnsetTimes = false; +} + +bool dcm2fsWrapper::isDICOM(const char* file) +{ + return isDICOMfile(file); +} + +int dcm2fsWrapper::dcm2NiiOneSeries(const char* dcmfile) +{ + struct TDICOMdata tdicomData = readDICOM((char*)dcmfile); + double seriesNo = (double)tdicomData.seriesUidCrc; //seriesNum; + tdcmOpts.seriesNumber[0] = seriesNo; + tdcmOpts.numSeries = 1; + + //memset(&mrifsStruct, 0, sizeof(mrifsStruct)); + return nii_loadDirCore(tdcmOpts.indir, &tdcmOpts); +} + +int dcm2fsWrapper::saveNii(const char* nii) +{ + FILE *fp = fopen(nii, "wb"); + if (!fp) + return 1; + + MRIFSSTRUCT* mrifsStruct = getMrifsStruct(); + + //if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) + fwrite(&mrifsStruct->hdr0, sizeof(mrifsStruct->hdr0), 1, fp); + uint32_t pad = 0; + fwrite(&pad, sizeof( pad), 1, fp); + fwrite(mrifsStruct->imgM, mrifsStruct->imgsz, 1, fp); + fclose(fp); + + return 0; +} + +MRIFSSTRUCT* dcm2fsWrapper::getMrifsStruct(void) +{ + return nii_getMrifsStruct(); +} + +nifti_1_header* dcm2fsWrapper::getNiiHeader(void) +{ + MRIFSSTRUCT* mrifsStruct = getMrifsStruct(); + return &mrifsStruct->hdr0; +} + +const unsigned char* dcm2fsWrapper::getMRIimg(void) +{ + MRIFSSTRUCT* mrifsStruct = getMrifsStruct(); + return mrifsStruct->imgM; +} diff --git a/packages/dcm2niix/dcm2fsWrapper.h b/packages/dcm2niix/dcm2fsWrapper.h new file mode 100644 index 00000000000..48f3ad9951a --- /dev/null +++ b/packages/dcm2niix/dcm2fsWrapper.h @@ -0,0 +1,25 @@ +#ifndef DCM2FSWRAPPER_H +#define DCM2FSWRAPPER_H + +#include "nii_dicom_batch.h" +#include "nii_dicom.h" + +class dcm2fsWrapper +{ +public: + static void setOpts(const char* dcmindir, const char* niioutdir); + static bool isDICOM(const char* file); + static int dcm2NiiOneSeries(const char* dcmfile); + static int saveNii(const char* nii); + + static MRIFSSTRUCT* getMrifsStruct(void); + static nifti_1_header* getNiiHeader(void); + static const unsigned char* getMRIimg(void); + +private: + static struct TDCMopts tdcmOpts; + + //static MRIFSSTRUCT mrifsStruct; +}; + +#endif diff --git a/packages/dcm2niix/dcm2fsmain.cpp b/packages/dcm2niix/dcm2fsmain.cpp new file mode 100644 index 00000000000..2c0b61d175b --- /dev/null +++ b/packages/dcm2niix/dcm2fsmain.cpp @@ -0,0 +1,55 @@ +#include +#include + +#include "dcm2fsWrapper.h" + +int main(int argc, const char *argv[]) +{ + const char* dcmfile = argv[1]; + const char* niifile = argv[2]; + +#if 1 + if (!dcm2fsWrapper::isDICOM(dcmfile)) { + printf("ERROR: %s is not a dicom file or some other problem\n", dcmfile); + exit(1); + } + + dcm2fsWrapper::setOpts(NULL, NULL); + dcm2fsWrapper::dcm2NiiOneSeries(dcmfile); + dcm2fsWrapper::saveNii(niifile); + + return 0; +#else + struct TDICOMdata tdicomData = readDICOM((char*)dcmfile); + int seriesNo = tdicomData.seriesNum; + + // set the TDCMopts + struct TDCMopts tdcmOpts; + setDefaultOpts(&tdcmOpts, NULL); + tdcmOpts.seriesNumber[0] = seriesNo; + tdcmOpts.numSeries = 1; + tdcmOpts.isIgnoreSeriesInstanceUID = true; + tdcmOpts.isCreateBIDS = false; + strcpy(tdcmOpts.indir, "/space/papancha/1/users/yh887/testdata/tutorial_data_20190918_1558/practice_with_data/DICOM"); + strcpy(tdcmOpts.outdir, "/space/papancha/1/users/yh887/dcm2niix_out"); + //getFileNameX(tdcmOpts.indir, dcmfile, 512); + + + + NIFTISTRUCT niftiStruct; + nii_loadDirCore(tdcmOpts.indir, &tdcmOpts, &niftiStruct); + + FILE *fp = fopen("/space/papancha/1/users/yh887/dcm2niix_out/fs.nii", "wb"); + if (!fp) + return 1; + + //if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) + fwrite(&niftiStruct.hdr0, sizeof(niftiStruct.hdr0), 1, fp); + uint32_t pad = 0; + fwrite(&pad, sizeof( pad), 1, fp); + fwrite(niftiStruct.imgM, niftiStruct.imgsz, 1, fp); + fclose(fp); + + return 0; +#endif +} diff --git a/packages/dcm2niix/jpg_0XC3.cpp b/packages/dcm2niix/jpg_0XC3.cpp new file mode 100644 index 00000000000..54e5dc4bd88 --- /dev/null +++ b/packages/dcm2niix/jpg_0XC3.cpp @@ -0,0 +1,513 @@ +#include "jpg_0XC3.h" +#include "print.h" +#include //requires VS 2015 or later +#include +#include +#include +#include + +unsigned char readByte(unsigned char *lRawRA, long *lRawPos, long lRawSz) { + unsigned char ret = 0x00; + if (*lRawPos < lRawSz) + ret = lRawRA[*lRawPos]; + (*lRawPos)++; + return ret; +} // readByte() + +uint16_t readWord(unsigned char *lRawRA, long *lRawPos, long lRawSz) { + return ((readByte(lRawRA, lRawPos, lRawSz) << 8) + readByte(lRawRA, lRawPos, lRawSz)); +} // readWord() + +int readBit(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos) { //Read the next single bit + int result = (lRawRA[*lRawPos] >> (7 - *lCurrentBitPos)) & 1; + (*lCurrentBitPos)++; + if (*lCurrentBitPos == 8) { + (*lRawPos)++; + *lCurrentBitPos = 0; + } + return result; +} // readBit() + +int bitMask(int bits) { + return ((2 << (bits - 1)) - 1); +} // bitMask() + +int readBits(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, int lNum) { //lNum: bits to read, not to exceed 16 + int result = lRawRA[*lRawPos]; + result = (result << 8) + lRawRA[(*lRawPos) + 1]; + result = (result << 8) + lRawRA[(*lRawPos) + 2]; + result = (result >> (24 - *lCurrentBitPos - lNum)) & bitMask(lNum); //lCurrentBitPos is incremented from 1, so -1 + *lCurrentBitPos = *lCurrentBitPos + lNum; + if (*lCurrentBitPos > 7) { + *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); // div 8 + *lCurrentBitPos = *lCurrentBitPos & 7; //mod 8 + } + return result; +} // readBits() + +struct HufTables { + uint8_t SSSSszRA[18]; + uint8_t LookUpRA[256]; + int DHTliRA[32]; + int DHTstartRA[32]; + int HufSz[32]; + int HufCode[32]; + int HufVal[32]; + int MaxHufSi; + int MaxHufVal; +}; // HufTables() + +int decodePixelDifference(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, struct HufTables l) { + int lByte = (lRawRA[*lRawPos] << *lCurrentBitPos) + (lRawRA[*lRawPos + 1] >> (8 - *lCurrentBitPos)); + lByte = lByte & 255; + int lHufValSSSS = l.LookUpRA[lByte]; + if (lHufValSSSS < 255) { + *lCurrentBitPos = l.SSSSszRA[lHufValSSSS] + *lCurrentBitPos; + *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); + *lCurrentBitPos = *lCurrentBitPos & 7; + } else { //full SSSS is not in the first 8-bits + int lInput = lByte; + int lInputBits = 8; + (*lRawPos)++; // forward 8 bits = precisely 1 byte + do { + lInputBits++; + lInput = (lInput << 1) + readBit(lRawRA, lRawPos, lCurrentBitPos); + if (l.DHTliRA[lInputBits] != 0) { //if any entries with this length + for (int lI = l.DHTstartRA[lInputBits]; lI <= (l.DHTstartRA[lInputBits] + l.DHTliRA[lInputBits] - 1); lI++) { + if (lInput == l.HufCode[lI]) + lHufValSSSS = l.HufVal[lI]; + } //check each code + } //if any entries with this length + if ((lInputBits >= l.MaxHufSi) && (lHufValSSSS > 254)) { //exhausted options CR: added rev13 + lHufValSSSS = l.MaxHufVal; + } + } while (!(lHufValSSSS < 255)); // found; + } //answer in first 8 bits + //The HufVal is referred to as the SSSS in the Codec, so it is called 'lHufValSSSS' + if (lHufValSSSS == 0) //NO CHANGE + return 0; + if (lHufValSSSS == 1) { + if (readBit(lRawRA, lRawPos, lCurrentBitPos) == 0) + return -1; + else + return 1; + } + if (lHufValSSSS == 16) { //ALL CHANGE 16 bit difference: Codec H.1.2.2 "No extra bits are appended after SSSS = 16 is encoded." Osiris fails here + return 32768; + } + //to get here - there is a 2..15 bit difference + int lDiff = readBits(lRawRA, lRawPos, lCurrentBitPos, lHufValSSSS); + if (lDiff <= bitMask(lHufValSSSS - 1)) //add + lDiff = lDiff - bitMask(lHufValSSSS); + return lDiff; +} // decodePixelDifference() + +unsigned char *decode_JPEG_SOF_0XC3(const char *fn, int skipBytes, bool verbose, int *dimX, int *dimY, int *bits, int *frames, int diskBytes) { +//decompress JPEG image named "fn" where image data is located skipBytes into file. diskBytes is compressed size of image (set to 0 if unknown) +//next line breaks MSVC +#define abortGoto(...) do {printError(__VA_ARGS__); free(lRawRA); return NULL;} while(0) + unsigned char *lImgRA8 = NULL; + FILE *reader = fopen(fn, "rb"); + int lSuccess = fseek(reader, 0, SEEK_END); + long lRawSz = ftell(reader) - skipBytes; + if ((diskBytes > 0) && (diskBytes < lRawSz)) //only if diskBytes is known and does not exceed length of file + lRawSz = diskBytes; + if ((lSuccess != 0) || (lRawSz <= 8)) { + printError("Unable to load 0XC3 JPEG %s\n", fn); + return NULL; //read failure + } + lSuccess = fseek(reader, skipBytes, SEEK_SET); //If successful, the function returns zero + if (lSuccess != 0) { + printError("Unable to open 0XC3 JPEG %s\n", fn); + return NULL; //read failure + } + unsigned char *lRawRA = (unsigned char *)malloc(lRawSz); + size_t lSz = fread(lRawRA, 1, lRawSz, reader); + fclose(reader); + if ((lSz < (size_t)lRawSz) || (lRawRA[0] != 0xFF) || (lRawRA[1] != 0xD8) || (lRawRA[2] != 0xFF)) { + abortGoto("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn); //signature failure http://en.wikipedia.org/wiki/List_of_file_signatures + } + if (verbose) + printMessage("JPEG signature 0xFFD8FF found at offset %d of %s\n", skipBytes, fn); + //next: read header + long lRawPos = 2; //Skip initial 0xFFD8, begin with third byte + //long lRawPos = 0; //Skip initial 0xFFD8, begin with third byte + unsigned char btS1, btS2, SOSse, SOSahal, btMarkerType, SOSns = 0x00; //tag + unsigned char SOSpttrans = 0; + unsigned char SOSss = 0; + uint8_t SOFnf = 0; + uint8_t SOFprecision = 0; + uint16_t SOFydim = 0; + uint16_t SOFxdim = 0; + // long SOSarrayPos; //SOFarrayPos + int lnHufTables = 0; + int lFrameCount = 1; + const int kmaxFrames = 4; + struct HufTables l[kmaxFrames + 1]; + do { //read each marker in the header + do { + btS1 = readByte(lRawRA, &lRawPos, lRawSz); + if (btS1 != 0xFF) { + abortGoto("JPEG header tag must begin with 0xFF\n"); + } + btMarkerType = readByte(lRawRA, &lRawPos, lRawSz); + if ((btMarkerType == 0x01) || (btMarkerType == 0xFF) || ((btMarkerType >= 0xD0) && (btMarkerType <= 0xD7))) + btMarkerType = 0; //only process segments with length fields + + } while ((lRawPos < lRawSz) && (btMarkerType == 0)); + uint16_t lSegmentLength = readWord(lRawRA, &lRawPos, lRawSz); //read marker length + long lSegmentEnd = lRawPos + (lSegmentLength - 2); + if (lSegmentEnd > lRawSz) { + abortGoto("Segment larger than image\n"); + } + if (verbose) + printMessage("btMarkerType %#02X length %d@%ld\n", btMarkerType, lSegmentLength, lRawPos); + if (((btMarkerType >= 0xC0) && (btMarkerType <= 0xC3)) || ((btMarkerType >= 0xC5) && (btMarkerType <= 0xCB)) || ((btMarkerType >= 0xCD) && (btMarkerType <= 0xCF))) { + //if Start-Of-Frame (SOF) marker + SOFprecision = readByte(lRawRA, &lRawPos, lRawSz); + SOFydim = readWord(lRawRA, &lRawPos, lRawSz); + SOFxdim = readWord(lRawRA, &lRawPos, lRawSz); + SOFnf = readByte(lRawRA, &lRawPos, lRawSz); + //SOFarrayPos = lRawPos; + lRawPos = (lSegmentEnd); + if (verbose) + printMessage(" [Precision %d X*Y %d*%d Frames %d]\n", SOFprecision, SOFxdim, SOFydim, SOFnf); + if (btMarkerType != 0xC3) { //lImgTypeC3 = true; + abortGoto("This JPEG decoder can only decompress lossless JPEG ITU-T81 images (SoF must be 0XC3, not %#02X)\n", btMarkerType); + } + if ((SOFprecision < 1) || (SOFprecision > 16) || (SOFnf < 1) || (SOFnf == 2) || (SOFnf > 3) || ((SOFnf == 3) && (SOFprecision > 8))) { + abortGoto("Scalar data must be 1..16 bit, RGB data must be 8-bit (%d-bit, %d frames)\n", SOFprecision, SOFnf); + } + } else if (btMarkerType == 0xC4) { //if SOF marker else if define-Huffman-tables marker (DHT) + if (verbose) + printMessage(" [Huffman Length %d]\n", lSegmentLength); + do { + uint8_t DHTnLi = readByte(lRawRA, &lRawPos, lRawSz); //we read but ignore DHTtcth. +#pragma unused(DHTnLi) //we need to increment the input file position, but we do not care what the value is + DHTnLi = 0; + for (int lInc = 1; lInc <= 16; lInc++) { + l[lFrameCount].DHTliRA[lInc] = readByte(lRawRA, &lRawPos, lRawSz); + DHTnLi = DHTnLi + l[lFrameCount].DHTliRA[lInc]; + if (l[lFrameCount].DHTliRA[lInc] != 0) + l[lFrameCount].MaxHufSi = lInc; + if (verbose) + printMessage("DHT has %d combinations with %d bits\n", l[lFrameCount].DHTliRA[lInc], lInc); + } + if (DHTnLi > 17) { + abortGoto("Huffman table corrupted.\n"); + } + int lIncY = 0; //frequency + for (int lInc = 0; lInc <= 31; lInc++) { //lInc := 0 to 31 do begin + l[lFrameCount].HufVal[lInc] = -1; + l[lFrameCount].HufSz[lInc] = -1; + l[lFrameCount].HufCode[lInc] = -1; + } + for (int lInc = 1; lInc <= 16; lInc++) { //set the huffman size values + if (l[lFrameCount].DHTliRA[lInc] > 0) { + l[lFrameCount].DHTstartRA[lInc] = lIncY + 1; + for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lInc]; lIncX++) { + lIncY++; + btS1 = readByte(lRawRA, &lRawPos, lRawSz); + l[lFrameCount].HufVal[lIncY] = btS1; + l[lFrameCount].MaxHufVal = btS1; + if (verbose) + printMessage("DHT combination %d has a value of %d\n", lIncY, btS1); + if (btS1 <= 16) //unsigned ints ALWAYS >0, so no need for(btS1 >= 0) + l[lFrameCount].HufSz[lIncY] = lInc; + else { + abortGoto("Huffman size array corrupted.\n"); + } + } + } + } //set huffman size values + int K = 1; + int Code = 0; + int Si = l[lFrameCount].HufSz[K]; + do { + while (Si == l[lFrameCount].HufSz[K]) { + l[lFrameCount].HufCode[K] = Code; + Code = Code + 1; + K++; + } + if (K <= DHTnLi) { + while (l[lFrameCount].HufSz[K] > Si) { + Code = Code << 1; //Shl!!! + Si = Si + 1; + } //while Si + } //K <= 17 + + } while (K <= DHTnLi); + //if (verbose) + // for (int j = 1; j <= DHTnLi; j++) + // printMessage(" [%d Sz %d Code %d Value %d]\n", j, l[lFrameCount].HufSz[j], l[lFrameCount].HufCode[j], l[lFrameCount].HufVal[j]); + lFrameCount++; + } while ((lSegmentEnd - lRawPos) >= 18); + lnHufTables = lFrameCount - 1; + lRawPos = (lSegmentEnd); + if (verbose) + printMessage(" [FrameCount %d]\n", lnHufTables); + } else if (btMarkerType == 0xDD) { //if DHT marker else if Define restart interval (DRI) marker + abortGoto("btMarkerType == 0xDD: unsupported Restart Segments\n"); + //lRestartSegmentSz = ReadWord(lRawRA, &lRawPos, lRawSz); + //lRawPos = lSegmentEnd; + } else if (btMarkerType == 0xDA) { //if DRI marker else if read Start of Scan (SOS) marker + SOSns = readByte(lRawRA, &lRawPos, lRawSz); + //if Ns = 1 then NOT interleaved, else interleaved: see B.2.3 + // SOSarrayPos = lRawPos; //not required... + if (SOSns > 0) { + for (int lInc = 1; lInc <= SOSns; lInc++) { + btS1 = readByte(lRawRA, &lRawPos, lRawSz); //component identifier 1=Y,2=Cb,3=Cr,4=I,5=Q +#pragma unused(btS1) //dummy value used to increment file position + btS2 = readByte(lRawRA, &lRawPos, lRawSz); //horizontal and vertical sampling factors +#pragma unused(btS2) //dummy value used to increment file position + } + } + SOSss = readByte(lRawRA, &lRawPos, lRawSz); //predictor selection B.3 + SOSse = readByte(lRawRA, &lRawPos, lRawSz); +#pragma unused(SOSse) //dummy value used to increment file position + SOSahal = readByte(lRawRA, &lRawPos, lRawSz); //lower 4bits= pointtransform + SOSpttrans = SOSahal & 16; + if (verbose) + printMessage(" [Predictor: %d Transform %d]\n", SOSss, SOSahal); + lRawPos = (lSegmentEnd); + } else //if SOS marker else skip marker + lRawPos = (lSegmentEnd); + } while ((lRawPos < lRawSz) && (btMarkerType != 0xDA)); //0xDA=Start of scan: loop for reading header + //NEXT: Huffman decoding + if (lnHufTables < 1) { + abortGoto("Decoding error: no Huffman tables.\n"); + } + //NEXT: unpad data - delete byte that follows $FF + //int lIsRestartSegments = 0; + long lIncI = lRawPos; //input position + long lIncO = lRawPos; //output position + do { + lRawRA[lIncO] = lRawRA[lIncI]; + if (lRawRA[lIncI] == 255) { + if (lRawRA[lIncI + 1] == 0) + lIncI = lIncI + 1; + else if (lRawRA[lIncI + 1] == 0xD9) + lIncO = -666; //end of padding + //else + // lIsRestartSegments = lRawRA[lIncI+1]; + } + lIncI++; + lIncO++; + } while (lIncO > 0); + //if (lIsRestartSegments != 0) //detects both restart and corruption https://groups.google.com/forum/#!topic/comp.protocols.dicom/JUuz0B_aE5o + // printWarning("Detected restart segments, decompress with dcmdjpeg or gdcmconv 0xFF%02X.\n", lIsRestartSegments); + //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values + //NEXT: prepare lookup table + for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount++) { + for (int lInc = 0; lInc <= 17; lInc++) + l[lFrameCount].SSSSszRA[lInc] = 123; //Impossible value for SSSS, suggests 8-bits can not describe answer + for (int lInc = 0; lInc <= 255; lInc++) + l[lFrameCount].LookUpRA[lInc] = 255; //Impossible value for SSSS, suggests 8-bits can not describe answer + } + //NEXT: fill lookuptable + for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount++) { + int lIncY = 0; + for (int lSz = 1; lSz <= 8; lSz++) { //set the huffman lookup table for keys with lengths <=8 + if (l[lFrameCount].DHTliRA[lSz] > 0) { + for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lSz]; lIncX++) { + lIncY++; + int lHufVal = l[lFrameCount].HufVal[lIncY]; //SSSS + l[lFrameCount].SSSSszRA[lHufVal] = lSz; + int k = (l[lFrameCount].HufCode[lIncY] << (8 - lSz)) & 255; //K= most sig bits for hufman table + if (lSz < 8) { //fill in all possible bits that exceed the huffman table + int lInc = bitMask(8 - lSz); + for (int lCurrentBitPos = 0; lCurrentBitPos <= lInc; lCurrentBitPos++) { + l[lFrameCount].LookUpRA[k + lCurrentBitPos] = lHufVal; + } + } else + l[lFrameCount].LookUpRA[k] = lHufVal; //SSSS + //printMessage("Frame %d SSSS %d Size %d Code %d SHL %d EmptyBits %ld\n", lFrameCount, lHufRA[lFrameCount][lIncY].HufVal, lHufRA[lFrameCount][lIncY].HufSz,lHufRA[lFrameCount][lIncY].HufCode, k, lInc); + } //Set SSSS + } //Length of size lInc > 0 + } //for lInc := 1 to 8 + } //For each frame, e.g. once each for Red/Green/Blue + //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values + if (lnHufTables < SOFnf) { //use single Hufman table for each frame + for (int lFrameCount = lnHufTables + 1; lFrameCount <= SOFnf; lFrameCount++) { + l[lFrameCount] = l[lnHufTables]; + + } //for each frame + } // if lnHufTables < SOFnf + //NEXT: uncompress data: different loops for different predictors + int lItems = SOFxdim * SOFydim * SOFnf; + // lRawPos++;// <- only for Pascal where array is indexed from 1 not 0 first byte of data + int lCurrentBitPos = 0; //read in a new byte + //depending on SOSss, we see Table H.1 + int lPredA = 0; + int lPredB = 0; + int lPredC = 0; + if (SOSss == 2) //predictor selection 2: above + lPredA = SOFxdim - 1; + else if (SOSss == 3) //predictor selection 3: above+left + lPredA = SOFxdim; + else if ((SOSss == 4) || (SOSss == 5)) { //these use left, above and above+left WEIGHT LEFT + lPredA = 0; //Ra left + lPredB = SOFxdim - 1; //Rb directly above + lPredC = SOFxdim; //Rc UpperLeft:above and to the left + } else if (SOSss == 6) { //also use left, above and above+left, WEIGHT ABOVE + lPredB = 0; + lPredA = SOFxdim - 1; //Rb directly above + lPredC = SOFxdim; //Rc UpperLeft:above and to the left + } else + lPredA = 0; //Ra: directly to left) + if (SOFprecision > 8) { //start - 16 bit data + *bits = 16; + int lPx = -1; //pixel position + int lPredicted = 1 << (SOFprecision - 1 - SOSpttrans); + lImgRA8 = (unsigned char *)malloc(lItems * 2); + uint16_t *lImgRA16 = (uint16_t *)lImgRA8; + for (int i = 0; i < lItems; i++) + lImgRA16[i] = 0; //zero array + int frame = 1; + for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor + lPx++; //writenext voxel + if (lIncX > 1) + lPredicted = lImgRA16[lPx - 1]; + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } + for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { //for all subsequent rows + lPx++; //write next voxel + lPredicted = lImgRA16[lPx - SOFxdim]; //use ABOVE + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + if (SOSss == 4) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA16[lPx - lPredA] + lImgRA16[lPx - lPredB] - lImgRA16[lPx - lPredC]; + lPx++; //writenext voxel + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } //for lIncX + } else if ((SOSss == 5) || (SOSss == 6)) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA16[lPx - lPredA] + ((lImgRA16[lPx - lPredB] - lImgRA16[lPx - lPredC]) >> 1); + lPx++; //writenext voxel + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } //for lIncX + } else if (SOSss == 7) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPx++; //writenext voxel + lPredicted = (lImgRA16[lPx - 1] + lImgRA16[lPx - SOFxdim]) >> 1; + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } //for lIncX + } else { //SOSss 1,2,3 read single values + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA16[lPx - lPredA]; + lPx++; //writenext voxel + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } //for lIncX + } // if..else possible predictors + } //for lIncY + } else if (SOFnf == 3) { //if 16-bit data; else 8-bit 3 frames + *bits = 8; + lImgRA8 = (unsigned char *)malloc(lItems); + int lPx[kmaxFrames + 1], lPredicted[kmaxFrames + 1]; //pixel position + for (int f = 1; f <= SOFnf; f++) { + lPx[f] = ((f - 1) * (SOFxdim * SOFydim)) - 1; + lPredicted[f] = 1 << (SOFprecision - 1 - SOSpttrans); + } + for (int i = 0; i < lItems; i++) + lImgRA8[i] = 255; //zero array + for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor + for (int f = 1; f <= SOFnf; f++) { + lPx[f]++; //writenext voxel + if (lIncX > 1) + lPredicted[f] = lImgRA8[lPx[f] - 1]; + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //first row always predicted by LEFT + for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { //for all subsequent rows + for (int f = 1; f <= SOFnf; f++) { + lPx[f]++; //write next voxel + lPredicted[f] = lImgRA8[lPx[f] - SOFxdim]; //use ABOVE + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } //first column of row always predicted by ABOVE + if (SOSss == 4) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + for (int f = 1; f <= SOFnf; f++) { + lPredicted[f] = lImgRA8[lPx[f] - lPredA] + lImgRA8[lPx[f] - lPredB] - lImgRA8[lPx[f] - lPredC]; + lPx[f]++; //writenext voxel + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //for lIncX + } else if ((SOSss == 5) || (SOSss == 6)) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + for (int f = 1; f <= SOFnf; f++) { + lPredicted[f] = lImgRA8[lPx[f] - lPredA] + ((lImgRA8[lPx[f] - lPredB] - lImgRA8[lPx[f] - lPredC]) >> 1); + lPx[f]++; //writenext voxel + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //for lIncX + } else if (SOSss == 7) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + for (int f = 1; f <= SOFnf; f++) { + lPx[f]++; //writenext voxel + lPredicted[f] = (lImgRA8[lPx[f] - 1] + lImgRA8[lPx[f] - SOFxdim]) >> 1; + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //for lIncX + } else { //SOSss 1,2,3 read single values + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + for (int f = 1; f <= SOFnf; f++) { + lPredicted[f] = lImgRA8[lPx[f] - lPredA]; + lPx[f]++; //writenext voxel + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //for lIncX + } // if..else possible predictors + } //for lIncY + } else { //if 8-bit data 3frames; else 8-bit 1 frames + *bits = 8; + lImgRA8 = (unsigned char *)malloc(lItems); + int lPx = -1; //pixel position + int lPredicted = 1 << (SOFprecision - 1 - SOSpttrans); + for (int i = 0; i < lItems; i++) + lImgRA8[i] = 0; //zero array + for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor + lPx++; //writenext voxel + if (lIncX > 1) + lPredicted = lImgRA8[lPx - 1]; + int dx = decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + lImgRA8[lPx] = lPredicted + dx; + } + for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { //for all subsequent rows + lPx++; //write next voxel + lPredicted = lImgRA8[lPx - SOFxdim]; //use ABOVE + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + if (SOSss == 4) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA8[lPx - lPredA] + lImgRA8[lPx - lPredB] - lImgRA8[lPx - lPredC]; + lPx++; //writenext voxel + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + } //for lIncX + } else if ((SOSss == 5) || (SOSss == 6)) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA8[lPx - lPredA] + ((lImgRA8[lPx - lPredB] - lImgRA8[lPx - lPredC]) >> 1); + lPx++; //writenext voxel + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + } //for lIncX + } else if (SOSss == 7) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPx++; //writenext voxel + lPredicted = (lImgRA8[lPx - 1] + lImgRA8[lPx - SOFxdim]) >> 1; + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + } //for lIncX + } else { //SOSss 1,2,3 read single values + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA8[lPx - lPredA]; + lPx++; //writenext voxel + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + } //for lIncX + } // if..else possible predictors + } //for lIncY + } //if 16bit else 8bit + free(lRawRA); + *dimX = SOFxdim; + *dimY = SOFydim; + *frames = SOFnf; + if (verbose) + printMessage("JPEG ends %ld@%ld\n", lRawPos, lRawPos + skipBytes); + return lImgRA8; +} // decode_JPEG_SOF_0XC3() diff --git a/packages/dcm2niix/jpg_0XC3.h b/packages/dcm2niix/jpg_0XC3.h new file mode 100644 index 00000000000..d04b667ce30 --- /dev/null +++ b/packages/dcm2niix/jpg_0XC3.h @@ -0,0 +1,23 @@ +//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 + +#endif \ No newline at end of file diff --git a/packages/dcm2niix/main_console.cpp b/packages/dcm2niix/main_console.cpp new file mode 100644 index 00000000000..a5aaf7993a0 --- /dev/null +++ b/packages/dcm2niix/main_console.cpp @@ -0,0 +1,602 @@ +//main_console.cpp dcm2niix +// by Chris Rorden on 3/22/14, see license.txt +// Copyright (c) 2014-2021 Chris Rorden. All rights reserved. + +//g++ -O3 main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -s -o dcm2niix -lz + +//if you do not have zlib,you can compile without it +// g++ -O3 -DmyDisableZLib main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -s -o dcm2niix +//or you can build your own copy: +// to compile you will first want to build the Z library, then compile the project +// cd zlib-1.2.8 +// sudo ./configure; +// sudo make + +//to generate combined 32-bit and 64-bit builds for OSX : +// g++ -O3 -x c++ main_console.c nii_dicom.c nifti1_io_core.c nii_ortho.c nii_dicom_batch.c -s -arch x86_64 -o dcm2niix64 -lz +// g++ -O3 -x c++ main_console.c nii_dicom.c nifti1_io_core.c nii_ortho.c nii_dicom_batch.c -s -arch i386 -o dcm2niix32 -lz +// lipo -create dcm2niix32 dcm2niix64 -o dcm2niix + +//On windows with mingw you may get "fatal error: zlib.h: No such file +// to remedy, run "mingw-get install libz-dev" from mingw + +//Alternatively, windows users with VisualStudio can compile this project +// vcvarsall amd64 +// cl /EHsc main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -DmyDisableOpenJPEG /o dcm2niix + +//#define mydebugtest //automatically process directory specified in main, ignore input arguments + +#include +#include +#include //requires VS 2015 or later +#include +#include +#include +#include +//#include +#include "nifti1_io_core.h" +#include "nii_dicom.h" +#include "nii_dicom_batch.h" +#include +#include +#include // clock_t, clock, CLOCKS_PER_SEC + +#if !defined(_WIN64) && !defined(_WIN32) +#include +#include +double get_wall_time() { + struct timeval time; + if (gettimeofday(&time, NULL)) { + // Handle error + return 0; + } + return (double)time.tv_sec + (double)time.tv_usec * .000001; +} +#endif + +const char *removePath(const char *path) { // "/usr/path/filename.exe" -> "filename.exe" + const char *pDelimeter = strrchr(path, '\\'); + if (pDelimeter) + path = pDelimeter + 1; + pDelimeter = strrchr(path, '/'); + if (pDelimeter) + path = pDelimeter + 1; + return path; +} //removePath() + +char bool2Char(bool b) { + if (b) + return ('y'); + return ('n'); +} + +void showHelp(const char *argv[], struct TDCMopts opts) { + const char *cstr = removePath(argv[0]); + printf("usage: %s [options] \n", cstr); + printf(" Options :\n"); + printf(" -1..-9 : gz compression level (1=fastest..9=smallest, default %d)\n", opts.gzLevel); + printf(" -a : adjacent DICOMs (images from same series always in same folder) for faster conversion (n/y, default n)\n"); + printf(" -b : BIDS sidecar (y/n/o [o=only: no NIfTI], default %c)\n", bool2Char(opts.isCreateBIDS)); + printf(" -ba : anonymize BIDS (y/n, default %c)\n", bool2Char(opts.isAnonymizeBIDS)); + printf(" -c : comment stored in NIfTI aux_file (provide up to 24 characters e.g. '-c first_visit')\n"); + printf(" -d : directory search depth. Convert DICOMs in sub-folders of in_folder? (0..9, default %d)\n", opts.dirSearchDepth); + printf(" -e : export as NRRD (y) or MGH (o) instead of NIfTI (y/n/o, default n)\n"); +#ifdef mySegmentByAcq +#define kQstr " %%q=sequence number," +#else +#define kQstr "" +#endif + printf(" -f : filename (%%a=antenna (coil) name, %%b=basename, %%c=comments, %%d=description, %%e=echo number, %%f=folder name, %%g=accession number, %%i=ID of patient, %%j=seriesInstanceUID, %%k=studyInstanceUID, %%m=manufacturer, %%n=name of patient, %%o=mediaObjectInstanceUID, %%p=protocol,%s %%r=instance number, %%s=series number, %%t=time, %%u=acquisition number, %%v=vendor, %%x=study ID; %%z=sequence name; default '%s')\n", kQstr, opts.filename); + printf(" -g : generate defaults file (y/n/o/i [o=only: reset and write defaults; i=ignore: reset defaults], default n)\n"); + printf(" -h : show help\n"); + printf(" -i : ignore derived, localizer and 2D images (y/n, default n)\n"); + char max16Ch = 'n'; + if (opts.isMaximize16BitRange == kMaximize16BitRange_True) + max16Ch = 'y'; + if (opts.isMaximize16BitRange == kMaximize16BitRange_Raw) + max16Ch = 'o'; + printf(" -l : losslessly scale 16-bit integers to use dynamic range (y/n/o [yes=scale, no=no, but uint16->int16, o=original], default %c)\n", max16Ch); + printf(" -m : merge 2D slices from same series regardless of echo, exposure, etc. (n/y or 0/1/2, default 2) [no, yes, auto]\n"); + printf(" -n : only convert this series CRC number - can be used up to %i times (default convert all)\n", MAX_NUM_SERIES); + printf(" -o : output directory (omit to save to input folder)\n"); + printf(" -p : Philips precise float (not display) scaling (y/n, default y)\n"); + printf(" -r : rename instead of convert DICOMs (y/n, default n)\n"); + printf(" -s : single file mode, do not convert other images in folder (y/n, default n)\n"); +//text notes replaced with BIDS: this function is deprecated +//printf(" -t : text notes includes private patient details (y/n, default n)\n"); +#if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only + printf(" -u : up-to-date check\n"); +#endif + printf(" -v : verbose (n/y or 0/1/2, default 0) [no, yes, logorrheic]\n"); + //#define kNAME_CONFLICT_SKIP 0 //0 = write nothing for a file that exists with desired name + //#define kNAME_CONFLICT_OVERWRITE 1 //1 = overwrite existing file with same name + //#define kNAME_CONFLICT_ADD_SUFFIX 2 //default 2 = write with new suffix as a new file + printf(" -w : write behavior for name conflicts (0,1,2, default 2: 0=skip duplicates, 1=overwrite, 2=add suffix)\n"); + printf(" -x : crop 3D acquisitions (y/n/i, default n, use 'i'gnore to neither crop nor rotate 3D acquistions)\n"); + char gzCh = 'n'; + if (opts.isGz) + gzCh = 'y'; +#if defined(_WIN64) || defined(_WIN32) +//n.b. the optimal use of pigz requires pipes that are not provided for Windows +#ifdef myDisableZLib + if (strlen(opts.pigzname) > 0) + printf(" -z : gz compress images (y/n/3, default %c) [y=pigz, n=no, 3=no,3D]\n", gzCh); + else + printf(" -z : gz compress images (y/n/3, default %c) [y=pigz(MISSING!), n=no, 3=no,3D]\n", gzCh); +#else +#ifdef myDisableMiniZ + printf(" -z : gz compress images (y/i/n/3, default %c) [y=pigz, i=internal:zlib, n=no, 3=no,3D]\n", gzCh); +#else + printf(" -z : gz compress images (y/i/n/3, default %c) [y=pigz, i=internal:miniz, n=no, 3=no,3D]\n", gzCh); +#endif +#endif + printf(" --big-endian : byte order (y/n/o, default o) [y=big-end, n=little-end, o=optimal/native]\n"); + printf(" --progress : report progress (y/n, default n)\n"); + printf(" --ignore_trigger_times : disregard values in 0018,1060 and 0020,9153\n"); + printf(" --terse : omit filename post-fixes (can cause overwrites)\n"); + printf(" --version : report version\n"); + printf(" --xml : Slicer format features\n"); + printf(" Defaults stored in Windows registry\n"); + printf(" Examples :\n"); + printf(" %s c:\\DICOM\\dir\n", cstr); + printf(" %s -c \"my comment\" c:\\DICOM\\dir\n", cstr); + printf(" %s -o c:\\out\\dir c:\\DICOM\\dir\n", cstr); + printf(" %s -f mystudy%%s c:\\DICOM\\dir\n", cstr); + printf(" %s -o \"c:\\dir with spaces\\dir\" c:\\dicomdir\n", cstr); +#else +#ifdef myDisableZLib + if (strlen(opts.pigzname) > 0) + printf(" -z : gz compress images (y/o/n/3, default %c) [y=pigz, o=optimal pigz, n=no, 3=no,3D]\n", gzCh); + else + printf(" -z : gz compress images (y/o/n/3, default %c) [y=pigz(MISSING!), o=optimal(requires pigz), n=no, 3=no,3D]\n", gzCh); +#else +#ifdef myDisableMiniZ + printf(" -z : gz compress images (y/o/i/n/3, default %c) [y=pigz, o=optimal pigz, i=internal:zlib, n=no, 3=no,3D]\n", gzCh); +#else + printf(" -z : gz compress images (y/o/i/n/3, default %c) [y=pigz, o=optimal pigz, i=internal:miniz, n=no, 3=no,3D]\n", gzCh); +#endif +#endif + printf(" --big-endian : byte order (y/n/o, default o) [y=big-end, n=little-end, o=optimal/native]\n"); + printf(" --progress : Slicer format progress information (y/n, default n)\n"); + printf(" --ignore_trigger_times : disregard values in 0018,1060 and 0020,9153\n"); + printf(" --terse : omit filename post-fixes (can cause overwrites)\n"); + printf(" --version : report version\n"); + printf(" --xml : Slicer format features\n"); + printf(" Defaults file : %s\n", opts.optsname); + printf(" Examples :\n"); + printf(" %s /Users/chris/dir\n", cstr); + printf(" %s -c \"my comment\" /Users/chris/dir\n", cstr); + printf(" %s -o /users/cr/outdir/ -z y ~/dicomdir\n", cstr); + printf(" %s -f %%p_%%s -b y -ba n ~/dicomdir\n", cstr); + printf(" %s -f mystudy%%s ~/dicomdir\n", cstr); + printf(" %s -o \"~/dir with spaces/dir\" ~/dicomdir\n", cstr); +#endif +} //showHelp() + +int invalidParam(int i, const char *argv[]) { + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == 'o') || (argv[i][0] == 'O') || (argv[i][0] == 'h') || (argv[i][0] == 'H') || (argv[i][0] == 'i') || (argv[i][0] == 'I') || (argv[i][0] == '0') || (argv[i][0] == '1') || (argv[i][0] == '2') || (argv[i][0] == '3')) + return 0; + + //if (argv[i][0] != '-') return 0; + printf(" Error: invalid option '%s %s'\n", argv[i - 1], argv[i]); + return 1; +} + +#if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only + +int checkUpToDate() { +#define URL "/rordenlab/dcm2niix/releases/" +#define APIURL "\"https://api.github.com/repos" URL "latest\"" +#define HTMURL "https://github.com" URL +#define SHELLSCRIPT "#!/usr/bin/env bash\n curl --silent " APIURL " | grep '\"tag_name\":' | sed -E 's/.*\"([^\"]+)\".*/\\1/'" +//check first 13 characters, e.g. "v1.0.20171204" +#define versionChars 13 + FILE *pipe = popen(SHELLSCRIPT, "r"); + char ch, gitvers[versionChars + 1]; + int n = 0; + int nMatch = 0; + while ((ch = fgetc(pipe)) != EOF) { + if (n < versionChars) { + gitvers[n] = ch; + if (gitvers[n] == kDCMvers[n]) + nMatch++; + n++; + } + } + pclose(pipe); + gitvers[n] = 0; //null terminate + if (n < 1) { //script reported nothing + printf("Error: unable to check version with script:\n %s\n", SHELLSCRIPT); + return 3; //different from EXIT_SUCCESS (0) and EXIT_FAILURE (1) + } + if (nMatch == versionChars) { //versions match + printf("Good news: Your version is up to date: %s\n", gitvers); + return EXIT_SUCCESS; + } + //report error + char myvers[versionChars + 1]; + for (int i = 0; i < versionChars; i++) + myvers[i] = kDCMvers[i]; + myvers[versionChars] = 0; //null terminate + int myv = atoi(myvers + 5); //skip "v1.0." + int gitv = atoi(gitvers + 5); //skip "v1.0." + if (myv > gitv) { + printf("Warning: your version ('%s') more recent than stable release ('%s')\n %s\n", myvers, gitvers, HTMURL); + return 2; //different from EXIT_SUCCESS (0) and EXIT_FAILURE (1) + } + printf("Error: your version ('%s') is not the latest release ('%s')\n %s\n", myvers, gitvers, HTMURL); + return EXIT_FAILURE; +} //checkUpToDate() + +#endif //shell script for UNIX only + +void showXML() { + //https://www.slicer.org/wiki/Documentation/Nightly/Developers/SlicerExecutionModel#XML_Schema + printf("\n"); + printf("\n"); + printf("dcm2niix\n"); + printf("DICOM importer\n"); + printf(" \n"); + printf(" At least one parameter\n"); + printf(" \n"); + printf("\n"); +} + +//#define mydebugtest +int main(int argc, const char *argv[]) { + struct TDCMopts opts; + bool isSaveIni = false; + bool isOutNameSpecified = false; + bool isResetDefaults = false; + readIniFile(&opts, argv); //set default preferences +#ifdef mydebugtest + //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/Philips_PARREC_Rotation/NoRotation/DBIEX_4_1.PAR"); + //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/test"); + strcpy(opts.indir, "e:\\t1s"); +#else +#if defined(__APPLE__) +#define kOS "MacOS" +#elif (defined(__linux) || defined(__linux__)) +#define kOS "Linux" +#else +#define kOS "Windows" +#endif + printf("Chris Rorden's dcm2niiX version %s (%llu-bit %s)\n", kDCMvers, (unsigned long long)sizeof(size_t) * 8, kOS); + if (argc < 2) { + showHelp(argv, opts); + return 0; + } + //for (int i = 1; i < argc; i++) { printf(" argument %d= '%s'\n", i, argv[i]);} + int i = 1; + int lastCommandArg = 0; + while (i < (argc)) { //-1 as final parameter is DICOM directory + if ((strlen(argv[i]) > 1) && (argv[i][0] == '-')) { //command + if (argv[i][1] == 'h') { + showHelp(argv, opts); + } else if ((!strcmp(argv[i], "--big-endian")) && ((i + 1) < argc)) { + i++; + if ((littleEndianPlatform()) && ((argv[i][0] == 'y') || (argv[i][0] == 'Y'))) { + opts.isSaveNativeEndian = false; + printf("NIfTI data will be big-endian (byte-swapped)\n"); + } + if ((!littleEndianPlatform()) && ((argv[i][0] == 'n') || (argv[i][0] == 'N'))) { + opts.isSaveNativeEndian = false; + printf("NIfTI data will be little-endian\n"); + } + } else if (!strcmp(argv[i], "--ignore_trigger_times")) { + opts.isIgnoreTriggerTimes = true; + printf("ignore_trigger_times may have unintended consequences (issue 499)\n"); + } else if (!strcmp(argv[i], "--terse")) { + opts.isAddNamePostFixes = false; + } else if (!strcmp(argv[i], "--version")) { + printf("%s\n", kDCMdate); + return kEXIT_REPORT_VERSION; + } else if ((!strcmp(argv[i], "--progress")) && ((i + 1) < argc)) { + i++; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isProgress = 0; + else + opts.isProgress = 1; + if (argv[i][0] == '2') + opts.isProgress = 2; //logorrheic + } else if (!strcmp(argv[i], "--xml")) { + showXML(); + return EXIT_SUCCESS; + } else if ((argv[i][1] == 'a') && ((i + 1) < argc)) { //adjacent DICOMs + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isOneDirAtATime = false; + else + opts.isOneDirAtATime = true; + } else if ((argv[i][1] >= '1') && (argv[i][1] <= '9')) { + opts.gzLevel = abs((int)strtol(argv[i], NULL, 10)); + if (opts.gzLevel > 11) + opts.gzLevel = 11; + } else if ((argv[i][1] == 'b') && ((i + 1) < argc)) { + if (strlen(argv[i]) < 3) { //"-b y" + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isCreateBIDS = false; + else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { + //input only mode (for development): does not create NIfTI or BIDS outputs! + opts.isCreateBIDS = false; + opts.isOnlyBIDS = true; + } else { + opts.isCreateBIDS = true; + if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) + opts.isOnlyBIDS = true; + } + } else if (argv[i][2] == 'a') { //"-ba y" + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isAnonymizeBIDS = false; + else + opts.isAnonymizeBIDS = true; + } else + printf("Error: Unknown command line argument: '%s'\n", argv[i]); + } else if ((argv[i][1] == 'c') && ((i + 1) < argc)) { + i++; + snprintf(opts.imageComments, 24, "%s", argv[i]); + } else if ((argv[i][1] == 'd') && ((i + 1) < argc)) { + i++; + if ((argv[i][0] >= '0') && (argv[i][0] <= '9')) + opts.dirSearchDepth = abs((int)strtol(argv[i], NULL, 10)); + } else if ((argv[i][1] == 'e') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) + opts.saveFormat = kSaveFormatNRRD; + if ((argv[i][0] == 'o') || (argv[i][0] == 'O') || (argv[i][0] == '2')) + opts.saveFormat = kSaveFormatMGH; + } else if ((argv[i][1] == 'g') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) + isSaveIni = true; + if (((argv[i][0] == 'i') || (argv[i][0] == 'I')) && (!isResetDefaults)) { + isResetDefaults = true; + printf("Defaults ignored\n"); + setDefaultOpts(&opts, argv); + i = 0; //re-read all settings for this pass, e.g. "dcm2niix -f %p_%s -d o" should save filename as "%p_%s" + } + if (((argv[i][0] == 'o') || (argv[i][0] == 'O')) && (!isResetDefaults)) { + //reset defaults - do not read, but do write defaults + isSaveIni = true; + isResetDefaults = true; + printf("Defaults reset\n"); + setDefaultOpts(&opts, argv); + //this next line is optional, otherwise "dcm2niix -f %p_%s -d o" and "dcm2niix -d o -f %p_%s" will create different results + i = 0; //re-read all settings for this pass, e.g. "dcm2niix -f %p_%s -d o" should save filename as "%p_%s" + } + } else if ((argv[i][1] == 'i') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isIgnoreDerivedAnd2D = false; + else + opts.isIgnoreDerivedAnd2D = true; + } else if ((argv[i][1] == 'j') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) { + opts.isTestx0021x105E = true; + printf("undocumented '-j y' compares GE slice timing from 0021,105E\n"); + } + } else if ((argv[i][1] == 'l') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) + opts.isMaximize16BitRange = kMaximize16BitRange_Raw; + else if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isMaximize16BitRange = kMaximize16BitRange_False; + else + opts.isMaximize16BitRange = kMaximize16BitRange_True; + } else if ((argv[i][1] == 'm') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isForceStackSameSeries = 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) + opts.isForceStackSameSeries = 1; + if ((argv[i][0] == '2')) + opts.isForceStackSameSeries = 2; + if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) { + opts.isForceStackDCE = false; + //printf("Advanced feature: '-m o' merges images despite varying series number\n"); + } + if ((argv[i][0] == '2')) { + opts.isIgnoreSeriesInstanceUID = true; + printf("Advanced feature: '-m 2' ignores Series Instance UID.\n"); + } + } else if ((argv[i][1] == 'p') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isPhilipsFloatNotDisplayScaling = false; + else + opts.isPhilipsFloatNotDisplayScaling = true; + } else if ((argv[i][1] == 'r') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) + opts.isRenameNotConvert = true; + } else if ((argv[i][1] == 's') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isOnlySingleFile = false; + else + opts.isOnlySingleFile = true; + } else if ((argv[i][1] == 't') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isCreateText = false; + else + opts.isCreateText = true; +#if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only + } else if (argv[i][1] == 'u') { + return checkUpToDate(); +#endif + } else if ((argv[i][1] == 'v') && ((i + 1) >= argc)) { + printf("%s\n", kDCMdate); + return kEXIT_REPORT_VERSION; + } else if ((argv[i][1] == 'v') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) //0: verbose OFF + opts.isVerbose = 0; + else if ((argv[i][0] == 'h') || (argv[i][0] == 'H') || (argv[i][0] == '2')) //2: verbose HYPER + opts.isVerbose = 2; + else + opts.isVerbose = 1; //1: verbose ON + } else if ((argv[i][1] == 'w') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if (argv[i][0] == '0') + opts.nameConflictBehavior = 0; + if (argv[i][0] == '1') + opts.nameConflictBehavior = 1; + if (argv[i][0] == '2') + opts.nameConflictBehavior = 2; + } else if ((argv[i][1] == 'x') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isCrop = false; + else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { + opts.isRotate3DAcq = false; + opts.isCrop = false; + } else + opts.isCrop = true; + } else if ((argv[i][1] == 'y') && ((i + 1) < argc)) { + i++; + bool isFlipY = opts.isFlipY; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) { + opts.isFlipY = true; //force use of internal compression instead of pigz + strcpy(opts.pigzname, ""); + } else if ((argv[i][0] == 'n') || (argv[i][0] == 'N')) + opts.isFlipY = false; + if (isFlipY != opts.isFlipY) + printf("Advanced feature: You are flipping the default order of rows in your image.\n"); + } else if ((argv[i][1] == 'z') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == '3')) { + opts.isGz = false; //uncompressed 3D + opts.isSave3D = true; + } else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { + opts.isGz = true; +#ifndef myDisableZLib + strcpy(opts.pigzname, ""); //force use of internal compression instead of pigz +#endif + } else if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isGz = false; + else + opts.isGz = true; + if (argv[i][0] == 'o') + opts.isPipedGz = true; //pipe to pigz without saving uncompressed to disk + } else if ((argv[i][1] == 'f') && ((i + 1) < argc)) { + i++; + strcpy(opts.filename, argv[i]); + isOutNameSpecified = true; + } else if ((argv[i][1] == 'o') && ((i + 1) < argc)) { + i++; + strcpy(opts.outdir, argv[i]); + } else if ((argv[i][1] == 'n') && ((i + 1) < argc)) { + i++; + double seriesNumber = atof(argv[i]); + if (seriesNumber < 0) + opts.numSeries = -1.0; //report series: convert none + else if ((opts.numSeries >= 0) && (opts.numSeries < MAX_NUM_SERIES)) { + opts.seriesNumber[opts.numSeries] = seriesNumber; + opts.numSeries += 1; + } else { + printf("Warning: too many series specified, ignoring -n %s\n", argv[i]); + } + } else + printf(" Error: invalid option '%s %s'\n", argv[i], argv[i + 1]); + ; + lastCommandArg = i; + } //if parameter is a command + i++; //read next parameter + } //while parameters to read +#ifndef myDisableZLib + if ((opts.isGz) && (opts.dirSearchDepth < 1) && (strlen(opts.pigzname) > 0)) { + strcpy(opts.pigzname, ""); + printf("n.b. Setting directory search depth of zero invokes internal gz (network mode)\n"); + } +#endif + if ((opts.isRenameNotConvert) && (!isOutNameSpecified)) { //sensible naming scheme for renaming option +//strcpy(opts.filename,argv[i]); +//2019 - now include "%o" to append media SOP UID, as instance number is not required to be unique +#if defined(_WIN64) || defined(_WIN32) + strcpy(opts.filename, "%t\\%s_%p\\%4r_%o.dcm"); //nrrd or nhdr (windows folders) +#else + strcpy(opts.filename, "%t/%s_%p/%4r_%o.dcm"); //nrrd or nhdr (unix folders) +#endif + printf("renaming without output filename, assuming '-f %s'\n", opts.filename); + } + if (isSaveIni) + saveIniFile(opts); + //printf("%d %d",argc,lastCommandArg); + if (argc == (lastCommandArg + 1)) { //+1 as array indexed from 0 + //the user did not provide an input filename, report filename structure + char niiFilename[1024]; + strcpy(opts.outdir, ""); //no input supplied + nii_createDummyFilename(niiFilename, opts); + printf("%s\n", niiFilename); + return EXIT_SUCCESS; + } +#endif +#ifndef myEnableMultipleInputs + if ((argc - lastCommandArg - 1) > 1) { + printf("Warning: only processing last of %d input files (recompile with 'myEnableMultipleInputs' to recursively process multiple files)\n", argc - lastCommandArg - 1); + lastCommandArg = argc - 2; + } +#endif +#if !defined(_WIN64) && !defined(_WIN32) + double startWall = get_wall_time(); +#endif + clock_t start = clock(); + for (i = (lastCommandArg + 1); i < argc; i++) { + strcpy(opts.indir, argv[i]); // [argc-1] + int ret = nii_loadDir(&opts); + if (ret != EXIT_SUCCESS) + return ret; + } +#if !defined(_WIN64) && !defined(_WIN32) + printf("Conversion required %f seconds (%f for core code).\n", get_wall_time() - startWall, ((float)(clock() - start)) / CLOCKS_PER_SEC); +#else + printf("Conversion required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC); +#endif + //if (isSaveIni) //we now save defaults earlier, in case of early termination. + // saveIniFile(opts); + return EXIT_SUCCESS; +} diff --git a/packages/dcm2niix/main_console_batch.cpp b/packages/dcm2niix/main_console_batch.cpp new file mode 100644 index 00000000000..e968962e2c9 --- /dev/null +++ b/packages/dcm2niix/main_console_batch.cpp @@ -0,0 +1,130 @@ +// main.m dcm2niix +// by Chris Rorden on 3/22/14, see license.txt +// Copyright (c) 2014 Chris Rorden. All rights reserved. +// yaml batch support by Benjamin Irving, 2016 - maintains copyright + +#include //requires VS 2015 or later +#ifdef _MSC_VER + #include //access() + #ifndef F_OK + #define F_OK 0 /* existence check */ + #endif +#else + #include //access() +#endif +#include +#include +#include +#include +#include +#include +#include // clock_t, clock, CLOCKS_PER_SEC +#include +#include "nii_dicom.h" +#include "nii_dicom_batch.h" + +const char* removePath(const char* path) { // "/usr/path/filename.exe" -> "filename.exe" + const char* pDelimeter = strrchr (path, '\\'); + if (pDelimeter) + path = pDelimeter+1; + pDelimeter = strrchr (path, '/'); + if (pDelimeter) + path = pDelimeter+1; + return path; +} //removePath() + +int rmainbatch(TDCMopts opts) { + clock_t start = clock(); + nii_loadDir(&opts); + printf ("Conversion required %f seconds.\n",((float)(clock()-start))/CLOCKS_PER_SEC); + return EXIT_SUCCESS; +} //rmainbatch() + +void showHelp(const char * argv[]) { + const char *cstr = removePath(argv[0]); + printf("Usage: %s \n", cstr); + printf("\n"); + printf("The configuration file must be in yaml format as shown below\n"); + printf("\n"); + printf("### START YAML FILE ###\n"); + printf("Options:\n"); + printf(" isGz: false\n"); + printf(" isFlipY: false\n"); + printf(" isVerbose: false\n"); + printf(" isCreateBIDS: false\n"); + printf(" isOnlySingleFile: false\n"); + printf("Files:\n"); + printf(" -\n"); + printf(" in_dir: /path/to/first/folder\n"); + printf(" out_dir: /path/to/output/folder\n"); + printf(" filename: dcemri\n"); + printf(" -\n"); + printf(" in_dir: /path/to/second/folder\n"); + printf(" out_dir: /path/to/output/folder\n"); + printf(" filename: fa3\n"); + printf("### END YAML FILE ###\n"); + printf("\n"); +#if defined(_WIN64) || defined(_WIN32) + printf(" Example :\n"); + printf(" %s c:\\dir\\yaml.yml\n", cstr); + + printf(" %s \"c:\\dir with spaces\\yaml.yml\"\n", cstr); +#else + printf(" Examples :\n"); + printf(" %s /Users/chris/yaml.yml\n", cstr); + printf(" %s \"/Users/dir with spaces/yaml.yml\"\n", cstr); +#endif +} //showHelp() + +int main(int argc, const char * argv[]) { + #if defined(__APPLE__) + #define kOS "MacOS" + #elif (defined(__linux) || defined(__linux__)) + #define kOS "Linux" + #else + #define kOS "Windows" + #endif + printf("dcm2niibatch using Chris Rorden's dcm2niiX version %s (%llu-bit %s)\n",kDCMvers, (unsigned long long) sizeof(size_t)*8, kOS); + if (argc != 2) { + if (argc < 2) + printf(" Please provide location of config file\n"); + else + printf(" Do not include additional inputs with a config file\n"); + printf("\n"); + showHelp(argv); + return EXIT_FAILURE; + } + if( access( argv[1], F_OK ) == -1 ) { + printf(" Please provide location of config file\n"); + printf("\n"); + showHelp(argv); + return EXIT_FAILURE; + } + // Process it all via a yaml file + std::string yaml_file = argv[1]; + std::cout << "yaml_path: " << yaml_file << std::endl; + YAML::Node config = YAML::LoadFile(yaml_file); + struct TDCMopts opts; + readIniFile(&opts, argv); //setup defaults, e.g. path to pigz + opts.isCreateBIDS = config["Options"]["isCreateBIDS"].as(); + opts.isOnlySingleFile = config["Options"]["isOnlySingleFile"].as(); + opts.isFlipY = config["Options"]["isFlipY"].as(); + opts.isCreateText = false; + opts.isVerbose = 0; + opts.isGz = config["Options"]["isGz"].as(); //save data as compressed (.nii.gz) or raw (.nii) + /*bool isInternalGz = config["Options"]["isInternalGz"].as(); + if (isInternalGz) { + strcpy(opts.pigzname, "”); //do NOT use pigz: force internal compressor + //in general, pigz is faster unless you have a very slow network, in which case the internal compressor is better + }*/ + for (auto i: config["Files"]) { + std::string indir = i["in_dir"].as(); + strcpy(opts.indir, indir.c_str()); + std::string outdir = i["out_dir"].as(); + strcpy(opts.outdir, outdir.c_str()); + std::string filename = i["filename"].as(); + strcpy(opts.filename, filename.c_str()); + rmainbatch(opts); + } + return EXIT_SUCCESS; +} // main() diff --git a/packages/dcm2niix/makefile b/packages/dcm2niix/makefile new file mode 100644 index 00000000000..49084991997 --- /dev/null +++ b/packages/dcm2niix/makefile @@ -0,0 +1,34 @@ +# Regular use +CFLAGS=-s -O3 + +# Debugging +#CFLAGS=-g + +#Leak tests: +# https://clang.llvm.org/docs/AddressSanitizer.html +# clang++ -O1 -g -fsanitize=address -fno-omit-frame-pointer -I. main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG + + +#run "make" for default build +#run "JPEGLS=1 make" for JPEGLS build +JFLAGS= +ifeq "$(JPEGLS)" "1" + JFLAGS=-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp +endif + +ifneq ($(OS),Windows_NT) + OS = $(shell uname) + ifeq "$(OS)" "Darwin" + #CFLAGS=-dead_strip -O3 + #CFLAGS= -O3 + CFLAGS=-O3 -sectcreate __TEXT __info_plist Info.plist + #Apple notarization requires a Info.plist + # For .app bundles, the Info.plist is a separate file, for executables it is appended as a section + #you can check that the Info.plist section has been inserted with either of these commands + # otool -l ./dcm2niix | grep info_plist -B1 -A10 + # launchctl plist ./dcm2niix + #MacOS links g++ to clang++, for gcc install via homebrew and replace g++ with /usr/local/bin/gcc-9 + endif +endif +all: + g++ $(CFLAGS) -I. $(JFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG diff --git a/packages/dcm2niix/miniz.c b/packages/dcm2niix/miniz.c new file mode 100644 index 00000000000..47d10109519 --- /dev/null +++ b/packages/dcm2niix/miniz.c @@ -0,0 +1,4925 @@ +/*miniz.c 2.08 is available. In April 2018 Martin Rainer (who created the 2.0 release) notes: +The main change is that it supports ZIP64. If you do not need that because your use +case does not run into the ZIP limitations, you should stick with the old version. +Since DICOM images are inherently limited to 2gb, dcm2niix will keep using v1.15 +*/ + +/* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Change History + 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!): + - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug + would only have occurred in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() + (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). + - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size + - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. + Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). + - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes + - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed + - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. + - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti + - Merged MZ_FORCEINLINE fix from hdeanclark + - Fix include before config #ifdef, thanks emil.brink + - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can + set it to 1 for real-time compression). + - Merged in some compiler fixes from paulharris's github repro. + - Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. + - Added example6.c, which dumps an image of the mandelbrot set to a PNG file. + - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. + - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled + - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch + 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. + - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. + - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. + - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly + "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). + - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. + - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. + - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. + - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) + - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). + 4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. + level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. + 5/28/11 v1.11 - Added statement from unlicense.org + 5/27/11 v1.10 - Substantial compressor optimizations: + - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a + - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). + - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. + - Refactored the compression code for better readability and maintainability. + - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large + drop in throughput on some files). + 5/15/11 v1.09 - Initial stable release. + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. +//#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or +// get/set file times, and the C run-time funcs that get/set times won't be called. +// The current downside is the times written to your archives will be from 1979. +//#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. +//#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. +//#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc +// callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user +// functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +//#define MINIZ_NO_MALLOC + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) + // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux + #define MINIZ_NO_TIME +#endif + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) + #include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +#if MINIZ_X86_OR_X64_CPU +// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). +enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s +{ + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. +// (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) +// mem_level must be between [1, 9] (it's checked but ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). +// MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. +// On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). +// MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again +// with more input data, or with more room in the output buffer (except when using single call decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + typedef unsigned char Byte; + typedef unsigned int uInt; + typedef mz_ulong uLong; + typedef Byte Bytef; + typedef uInt uIntf; + typedef char charf; + typedef int intf; + typedef void *voidpf; + typedef uLong uLongf; + typedef void *voidp; + typedef void *const voidpc; + #define Z_NULL 0 + #define Z_NO_FLUSH MZ_NO_FLUSH + #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH + #define Z_SYNC_FLUSH MZ_SYNC_FLUSH + #define Z_FULL_FLUSH MZ_FULL_FLUSH + #define Z_FINISH MZ_FINISH + #define Z_BLOCK MZ_BLOCK + #define Z_OK MZ_OK + #define Z_STREAM_END MZ_STREAM_END + #define Z_NEED_DICT MZ_NEED_DICT + #define Z_ERRNO MZ_ERRNO + #define Z_STREAM_ERROR MZ_STREAM_ERROR + #define Z_DATA_ERROR MZ_DATA_ERROR + #define Z_MEM_ERROR MZ_MEM_ERROR + #define Z_BUF_ERROR MZ_BUF_ERROR + #define Z_VERSION_ERROR MZ_VERSION_ERROR + #define Z_PARAM_ERROR MZ_PARAM_ERROR + #define Z_NO_COMPRESSION MZ_NO_COMPRESSION + #define Z_BEST_SPEED MZ_BEST_SPEED + #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION + #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION + #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY + #define Z_FILTERED MZ_FILTERED + #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY + #define Z_RLE MZ_RLE + #define Z_FIXED MZ_FIXED + #define Z_DEFLATED MZ_DEFLATED + #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS + #define alloc_func mz_alloc_func + #define free_func mz_free_func + #define internal_state mz_internal_state + #define z_stream mz_stream + #define deflateInit mz_deflateInit + #define deflateInit2 mz_deflateInit2 + #define deflateReset mz_deflateReset + #define deflate mz_deflate + #define deflateEnd mz_deflateEnd + #define deflateBound mz_deflateBound + #define compress mz_compress + #define compress2 mz_compress2 + #define compressBound mz_compressBound + #define inflateInit mz_inflateInit + #define inflateInit2 mz_inflateInit2 + #define inflate mz_inflate + #define inflateEnd mz_inflateEnd + #define uncompress mz_uncompress + #define crc32 mz_crc32 + #define adler32 mz_adler32 + #define MAX_WBITS 15 + #define MAX_MEM_LEVEL 9 + #define zError mz_error + #define ZLIB_VERSION MZ_VERSION + #define ZLIB_VERNUM MZ_VERNUM + #define ZLIB_VER_MAJOR MZ_VER_MAJOR + #define ZLIB_VER_MINOR MZ_VER_MINOR + #define ZLIB_VER_REVISION MZ_VER_REVISION + #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION + #define zlibVersion mz_version + #define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. +#ifdef _MSC_VER + #define MZ_MACRO_END while (0, 0) +#else + #define MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum +{ + MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct +{ + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum +{ + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef struct mz_zip_archive_tag +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + mz_uint m_total_files; + mz_zip_mode m_zip_mode; + + mz_uint m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum +{ + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and modified times. +// This function only extracts files, not archive directory records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +// Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. +// For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. +// For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). +// Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. +// Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before +// the archive is finalized the file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. +// To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by the end of central directory record. +// After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). +// An archive must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. +// Note for the archive to be valid, it must have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. +// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must call mz_free() on the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. +// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. +// Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum +{ + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. +// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum +{ + TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS + #define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF + typedef mz_uint64 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (64) +#else + typedef mz_uint32 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 0 + +// tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): +// TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). +enum +{ + TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. +// TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. +// TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). +// TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) +// TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must free() the returned block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. +// Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. +// The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. +// level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL +// If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#else +enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#endif + +// The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. +typedef enum +{ + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum +{ + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. +// pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. +// If pBut_buf_func is NULL the user should always call the tdefl_compress() API. +// flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. +// tdefl_compress_buffer() always consumes the entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS +// Create tdefl_compress() flags given zlib-style compression parameters. +// level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) +// window_bits may be -15 (raw deflate) or 15 (zlib) +// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + +// ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) + +#ifndef MINIZ_HEADER_FILE_ONLY + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1]; + +#include +#include + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC + #define MZ_MALLOC(x) NULL + #define MZ_FREE(x) (void)x, ((void)0) + #define MZ_REALLOC(p, x) NULL +#else + #define MZ_MALLOC(x) malloc(x) + #define MZ_FREE(x) free(x) + #define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a,b) (((a)>(b))?(a):(b)) +#define MZ_MIN(a,b) (((a)<(b))?(a):(b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) + #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else + #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) + #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#ifdef _MSC_VER + #define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) + #define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else + #define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +// ------------------- zlib-style API's + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; + if (!ptr) return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) return MZ_CRC32_INIT; + crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } + return ~crcu32; +} + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +#ifndef MINIZ_NO_ZLIB_APIS + +static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } +static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } +static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; + if (!pStream->avail_out) return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; + for ( ; ; ) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state* pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + + pState = (inflate_state*)pStream->state; + if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; pState->m_first_call = 0; + if (pState->m_last_status < 0) return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for ( ; ; ) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. + else if (flush == MZ_FINISH) + { + // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) +{ + static struct { int m_err; const char *m_pDesc; } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, + { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif //MINIZ_NO_ZLIB_APIS + +// ------------------- Low-level Decompression (completely independent from all compression API's) + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN switch(r->m_state) { case 0: +#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END +#define TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never +// reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define TINFL_GET_BYTE(state_index, c) do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for ( ; ; ) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else c = *pIn_buf_cur++; } MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END + +// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. +// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a +// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the +// bit buffer contains >=15 bits (deflate's max. Huffman code size). +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ + } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ + } while (num_bits < 15); + +// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read +// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully +// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. +// The slow path is only executed at the very end of the input buffer. +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ + int temp; mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ + } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; + static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } + + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } + while (pIn_buf_cur >= pIn_buf_end) + { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) + { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } + else + { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; + r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } + r->m_table_sizes[2] = 19; + } + for ( ; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; + cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) + { + mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for ( ; ; ) + { + mz_uint8 *pSrc; + for ( ; ; ) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } +#else + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + counter = sym2; bit_buf >>= code_len; num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + bit_buf >>= code_len; num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) break; + + num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) break; + new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +// ------------------- Low-level Compression (independent from all decompression API's) + +// Purposely making these tables static for faster init and thread safety. +static const mz_uint16 s_tdefl_len_sym[256] = { + 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, + 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, + 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, + 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, + 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, + 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, + 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, + 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; + +static const mz_uint8 s_tdefl_len_extra[256] = { + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = { + 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = { + 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7 }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = { + 0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26, + 26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28, + 28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = { + 0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 }; + +// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. +typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; +static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32* pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } + for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } + } + return pCur_syms; +} + +// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n==0) return; else if (n==1) { A[0].m_key = 1; return; } + A[0].m_key += A[1].m_key; root = 0; leaf = 2; + for (next=1; next < n-1; next++) + { + if (leaf>=n || A[root].m_key=n || (root=0; next--) A[next].m_key = A[A[next].m_key].m_key+1; + avbl = 1; used = dpth = 0; root = n-2; next = n-1; + while (avbl>0) + { + while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; } + while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } + avbl = 2*used; dpth++; used = 0; + } +} + +// Limits canonical Huffman code table's max code size. +enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; mz_uint32 total = 0; if (code_list_len <= 1) return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; + code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) do { \ + mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ +} MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ +} rle_repeat_count = 0; } } + +#define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ +} rle_z_count = 0; } } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) *p++ = 8; + for ( ; i <= 255; ++i) *p++ = 9; + for ( ; i <= 279; ++i) *p++ = 7; + for ( ; i <= 287; ++i) *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64*)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. + if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) ) + { + mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } + } + else + { + mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p) +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + if (!probe_len) + { + *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; + if (probe_len > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; + c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + // Simple lazy/greedy parsing state machine. + len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output buffer. + if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) ) + { + int n; + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) ) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; + d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; + pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; + do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); + pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; + p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; + } + memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; + *pOut_len = out_buf.m_size; return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) return 0; + out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; + return out_buf.m_size; +} + +#ifndef MINIZ_NO_ZLIB_APIS +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +// level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} +#endif //MINIZ_NO_ZLIB_APIS + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) +#endif + +// Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at +// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. +// This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; + if (!pComp) return NULL; + MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } + // write dummy header + for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + // write real header + *pLen_out = out_buf.m_size-41; + { + static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; + mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, + 0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0, + (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54}; + c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +// ------------------- .ZIP archive reading + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef MINIZ_NO_STDIO + #define MZ_FILE void * +#else + #include + #include + + #if defined(_MSC_VER) || defined(__MINGW64__) + static FILE *mz_fopen(const char *pFilename, const char *pMode) + { + FILE* pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; + } + static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) + { + FILE* pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; + } + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN mz_fopen + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 _ftelli64 + #define MZ_FSEEK64 _fseeki64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN mz_freopen + #define MZ_DELETE_FILE remove + #elif defined(__MINGW32__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__TINYC__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftell + #define MZ_FSEEK64 fseek + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__GNUC__) && _LARGEFILE64_SOURCE + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen64(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT stat64 + #define MZ_FILE_STAT stat64 + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) + #define MZ_DELETE_FILE remove + #else + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello + #define MZ_FSEEK64 fseeko + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #endif // #ifdef _MSC_VER +#endif // #ifdef MINIZ_NO_STDIO + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. +enum +{ + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + MZ_FILE *m_pFile; + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; + if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; + pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; + memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif + +#ifndef MINIZ_NO_STDIO +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef MINIZ_NO_TIME + (void)pFilename; *pDOS_date = *pDOS_time = 0; +#else + struct MZ_FILE_STAT_STRUCT file_stat; + // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); +#endif // #ifdef MINIZ_NO_TIME + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time) +{ + struct utimbuf t; t.actime = access_time; t.modtime = modified_time; + return !utime(pFilename, &t); +} +#endif // #ifndef MINIZ_NO_TIME +#endif // #ifndef MINIZ_NO_STDIO + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) + { + int child, root = start; + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= size) + break; + child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + start--; + } + + end = size - 1; + while (end > 0) + { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= end) + break; + child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags) +{ + mz_uint cdir_size, num_this_disk, cdir_disk_index; + mz_uint64 cdir_ofs; + mz_int64 cur_file_ofs; + const mz_uint8 *p; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + // Find the end of central directory record by scanning the file from the end towards the beginning. + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for ( ; ; ) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + for (i = n - 4; i >= 0; --i) + if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + break; + if (i >= 0) + { + cur_file_ofs += i; + break; + } + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return MZ_FALSE; + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + // Read and verify the end of central directory record. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || + ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) + return MZ_FALSE; + + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return MZ_FALSE; + + if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return MZ_FALSE; + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + + // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return MZ_FALSE; + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return MZ_FALSE; + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return MZ_FALSE; + + // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, comp_size, decomp_size, disk_index; + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return MZ_FALSE; + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) + return MZ_FALSE; + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index != num_this_disk) && (disk_index != 1)) + return MZ_FALSE; + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return MZ_FALSE; + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return MZ_FALSE; + n -= total_header_size; p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return MZ_FALSE; + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags) +{ + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + mz_uint64 file_size; + MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return MZ_FALSE; + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + file_size = MZ_FTELL64(pFile); + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, external_attr; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. + // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; + + return MZ_FALSE; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; + + return MZ_TRUE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) + { + int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint file_index; size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); if (name_len > 0xFFFF) return -1; + comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + return -1; +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return MZ_FALSE; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input buffer. + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#endif + return MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#endif + return NULL; + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + { + if (pSize) *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + // Decompress the file either directly from memory or from a file input buffer. + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) + { +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#endif + return MZ_FALSE; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); + cur_file_ofs += file_stat.m_comp_size; + #pragma unused(cur_file_ofs) + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + #pragma unused(comp_remaining) + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + status = TINFL_STATUS_FAILED; + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; +#ifndef MINIZ_NO_TIME + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + return status; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} +#endif + +// ------------------- .ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } +static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (pZip->m_file_offset_alignment) + { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); +#ifdef _MSC_VER + if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#else + if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#endif + return 0; + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + MZ_FILE *pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max size + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + pFilename; return MZ_FALSE; +#else + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } +#endif // #ifdef MINIZ_NO_STDIO + } + else if (pState->m_pMem) + { + // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; + + // Start writing new files at the archive's current central directory location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) + { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + { + time_t cur_time; time(&cur_time); + mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif // #ifndef MINIZ_NO_TIME + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } + + // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return MZ_FALSE; + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) + return MZ_FALSE; + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + if (uncomp_size > 0xFFFFFFFF) + { + // No zip64 support yet + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) + { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + if (!level) + { + while (uncomp_remaining) + { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + for ( ; ; ) + { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; + + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + break; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + MZ_FCLOSE(pSrc_file); pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; const mz_uint8 *pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) + return MZ_FALSE; + + while (comp_bytes_remaining) + { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; + } + + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + cur_src_file_ofs += n; + #pragma unused(cur_src_file_ofs) + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; + + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; + + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + + pState = pZip->m_pState; + + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_archive_size += sizeof(hdr); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } + else + { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) + { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } + } + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) + { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + int file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + return NULL; + + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; + + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + + mz_zip_reader_end(&zip_archive); + return p; +} + +#endif // #ifndef MINIZ_NO_STDIO + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_FILE_ONLY + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ diff --git a/packages/dcm2niix/nifti1.h b/packages/dcm2niix/nifti1.h new file mode 100644 index 00000000000..eb935e052ca --- /dev/null +++ b/packages/dcm2niix/nifti1.h @@ -0,0 +1,1501 @@ +/** \file nifti1.h + \brief Official definition of the nifti1 header. Written by Bob Cox, SSCC, NIMH. + + HISTORY: + + 29 Nov 2007 [rickr] + - added DT_RGBA32 and NIFTI_TYPE_RGBA32 + - added NIFTI_INTENT codes: + TIME_SERIES, NODE_INDEX, RGB_VECTOR, RGBA_VECTOR, SHAPE + + 08 Mar 2019 [PT,DRG] + - Updated to include [qs]form_code = 5 + + */ + +#ifndef _NIFTI_HEADER_ +#define _NIFTI_HEADER_ + +/***************************************************************************** + ** This file defines the "NIFTI-1" header format. ** + ** It is derived from 2 meetings at the NIH (31 Mar 2003 and ** + ** 02 Sep 2003) of the Data Format Working Group (DFWG), ** + ** chartered by the NIfTI (Neuroimaging Informatics Technology ** + ** Initiative) at the National Institutes of Health (NIH). ** + **--------------------------------------------------------------** + ** Neither the National Institutes of Health (NIH), the DFWG, ** + ** nor any of the members or employees of these institutions ** + ** imply any warranty of usefulness of this material for any ** + ** purpose, and do not assume any liability for damages, ** + ** incidental or otherwise, caused by any use of this document. ** + ** If these conditions are not acceptable, do not use this! ** + **--------------------------------------------------------------** + ** Author: Robert W Cox (NIMH, Bethesda) ** + ** Advisors: John Ashburner (FIL, London), ** + ** Stephen Smith (FMRIB, Oxford), ** + ** Mark Jenkinson (FMRIB, Oxford) ** +******************************************************************************/ + +/*---------------------------------------------------------------------------*/ +/* Note that the ANALYZE 7.5 file header (dbh.h) is + (c) Copyright 1986-1995 + Biomedical Imaging Resource + Mayo Foundation + Incorporation of components of dbh.h are by permission of the + Mayo Foundation. + + Changes from the ANALYZE 7.5 file header in this file are released to the + public domain, including the functional comments and any amusing asides. +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/*! INTRODUCTION TO NIFTI-1: + ------------------------ + The twin (and somewhat conflicting) goals of this modified ANALYZE 7.5 + format are: + (a) To add information to the header that will be useful for functional + neuroimaging data analysis and display. These additions include: + - More basic data types. + - Two affine transformations to specify voxel coordinates. + - "Intent" codes and parameters to describe the meaning of the data. + - Affine scaling of the stored data values to their "true" values. + - Optional storage of the header and image data in one file (.nii). + (b) To maintain compatibility with non-NIFTI-aware ANALYZE 7.5 compatible + software (i.e., such a program should be able to do something useful + with a NIFTI-1 dataset -- at least, with one stored in a traditional + .img/.hdr file pair). + + Most of the unused fields in the ANALYZE 7.5 header have been taken, + and some of the lesser-used fields have been co-opted for other purposes. + Notably, most of the data_history substructure has been co-opted for + other purposes, since the ANALYZE 7.5 format describes this substructure + as "not required". + + NIFTI-1 FLAG (MAGIC STRINGS): + ---------------------------- + To flag such a struct as being conformant to the NIFTI-1 spec, the last 4 + bytes of the header must be either the C String "ni1" or "n+1"; + in hexadecimal, the 4 bytes + 6E 69 31 00 or 6E 2B 31 00 + (in any future version of this format, the '1' will be upgraded to '2', + etc.). Normally, such a "magic number" or flag goes at the start of the + file, but trying to avoid clobbering widely-used ANALYZE 7.5 fields led to + putting this marker last. However, recall that "the last shall be first" + (Matthew 20:16). + + If a NIFTI-aware program reads a header file that is NOT marked with a + NIFTI magic string, then it should treat the header as an ANALYZE 7.5 + structure. + + NIFTI-1 FILE STORAGE: + -------------------- + "ni1" means that the image data is stored in the ".img" file corresponding + to the header file (starting at file offset 0). + + "n+1" means that the image data is stored in the same file as the header + information. We recommend that the combined header+data filename suffix + be ".nii". When the dataset is stored in one file, the first byte of image + data is stored at byte location (int)vox_offset in this combined file. + The minimum allowed value of vox_offset is 352; for compatibility with + some software, vox_offset should be an integral multiple of 16. + + GRACE UNDER FIRE: + ---------------- + Most NIFTI-aware programs will only be able to handle a subset of the full + range of datasets possible with this format. All NIFTI-aware programs + should take care to check if an input dataset conforms to the program's + needs and expectations (e.g., check datatype, intent_code, etc.). If the + input dataset can't be handled by the program, the program should fail + gracefully (e.g., print a useful warning; not crash). + + SAMPLE CODES: + ------------ + The associated files nifti1_io.h and nifti1_io.c provide a sample + implementation in C of a set of functions to read, write, and manipulate + NIFTI-1 files. The file nifti1_test.c is a sample program that uses + the nifti1_io.c functions. +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* HEADER STRUCT DECLARATION: + ------------------------- + In the comments below for each field, only NIFTI-1 specific requirements + or changes from the ANALYZE 7.5 format are described. For convenience, + the 348 byte header is described as a single struct, rather than as the + ANALYZE 7.5 group of 3 substructs. + + Further comments about the interpretation of various elements of this + header are after the data type definition itself. Fields that are + marked as ++UNUSED++ have no particular interpretation in this standard. + (Also see the UNUSED FIELDS comment section, far below.) + + The presumption below is that the various C types have particular sizes: + sizeof(int) = sizeof(float) = 4 ; sizeof(short) = 2 +-----------------------------------------------------------------------------*/ + +/*=================*/ +#ifdef __cplusplus +extern "C" { +#endif +/*=================*/ + +/*! \struct nifti_1_header + \brief Data structure defining the fields in the nifti1 header. + This binary header should be found at the beginning of a valid + NIFTI-1 header file. + */ + /*************************/ /************************/ +struct nifti_1_header { /* NIFTI-1 usage */ /* ANALYZE 7.5 field(s) */ + /*************************/ /************************/ + + /*--- was header_key substruct ---*/ + int sizeof_hdr; /*!< MUST be 348 */ /* int sizeof_hdr; */ + char data_type[10]; /*!< ++UNUSED++ */ /* char data_type[10]; */ + char db_name[18]; /*!< ++UNUSED++ */ /* char db_name[18]; */ + int extents; /*!< ++UNUSED++ */ /* int extents; */ + short session_error; /*!< ++UNUSED++ */ /* short session_error; */ + char regular; /*!< ++UNUSED++ */ /* char regular; */ + char dim_info; /*!< MRI slice ordering. */ /* char hkey_un0; */ + + /*--- was image_dimension substruct ---*/ + short dim[8]; /*!< Data array dimensions.*/ /* short dim[8]; */ + float intent_p1 ; /*!< 1st intent parameter. */ /* short unused8; */ + /* short unused9; */ + float intent_p2 ; /*!< 2nd intent parameter. */ /* short unused10; */ + /* short unused11; */ + float intent_p3 ; /*!< 3rd intent parameter. */ /* short unused12; */ + /* short unused13; */ + short intent_code ; /*!< NIFTI_INTENT_* code. */ /* short unused14; */ + short datatype; /*!< Defines data type! */ /* short datatype; */ + short bitpix; /*!< Number bits/voxel. */ /* short bitpix; */ + short slice_start; /*!< First slice index. */ /* short dim_un0; */ + float pixdim[8]; /*!< Grid spacings. */ /* float pixdim[8]; */ + float vox_offset; /*!< Offset into .nii file */ /* float vox_offset; */ + float scl_slope ; /*!< Data scaling: slope. */ /* float funused1; */ + float scl_inter ; /*!< Data scaling: offset. */ /* float funused2; */ + short slice_end; /*!< Last slice index. */ /* float funused3; */ + char slice_code ; /*!< Slice timing order. */ + char xyzt_units ; /*!< Units of pixdim[1..4] */ + float cal_max; /*!< Max display intensity */ /* float cal_max; */ + float cal_min; /*!< Min display intensity */ /* float cal_min; */ + float slice_duration;/*!< Time for 1 slice. */ /* float compressed; */ + float toffset; /*!< Time axis shift. */ /* float verified; */ + int glmax; /*!< ++UNUSED++ */ /* int glmax; */ + int glmin; /*!< ++UNUSED++ */ /* int glmin; */ + + /*--- was data_history substruct ---*/ + char descrip[80]; /*!< any text you like. */ /* char descrip[80]; */ + char aux_file[24]; /*!< auxiliary filename. */ /* char aux_file[24]; */ + + short qform_code ; /*!< NIFTI_XFORM_* code. */ /*-- all ANALYZE 7.5 ---*/ + short sform_code ; /*!< NIFTI_XFORM_* code. */ /* fields below here */ + /* are replaced */ + float quatern_b ; /*!< Quaternion b param. */ + float quatern_c ; /*!< Quaternion c param. */ + float quatern_d ; /*!< Quaternion d param. */ + float qoffset_x ; /*!< Quaternion x shift. */ + float qoffset_y ; /*!< Quaternion y shift. */ + float qoffset_z ; /*!< Quaternion z shift. */ + + float srow_x[4] ; /*!< 1st row affine transform. */ + float srow_y[4] ; /*!< 2nd row affine transform. */ + float srow_z[4] ; /*!< 3rd row affine transform. */ + + char intent_name[16];/*!< 'name' or meaning of data. */ + + char magic[4] ; /*!< MUST be "ni1\0" or "n+1\0". */ + +} ; /**** 348 bytes total ****/ + +typedef struct nifti_1_header nifti_1_header ; + +/*---------------------------------------------------------------------------*/ +/* HEADER EXTENSIONS: + ----------------- + After the end of the 348 byte header (e.g., after the magic field), + the next 4 bytes are a char array field named "extension". By default, + all 4 bytes of this array should be set to zero. In a .nii file, these + 4 bytes will always be present, since the earliest start point for + the image data is byte #352. In a separate .hdr file, these bytes may + or may not be present. If not present (i.e., if the length of the .hdr + file is 348 bytes), then a NIfTI-1 compliant program should use the + default value of extension={0,0,0,0}. The first byte (extension[0]) + is the only value of this array that is specified at present. The other + 3 bytes are reserved for future use. + + If extension[0] is nonzero, it indicates that extended header information + is present in the bytes following the extension array. In a .nii file, + this extended header data is before the image data (and vox_offset + must be set correctly to allow for this). In a .hdr file, this extended + data follows extension and proceeds (potentially) to the end of the file. + + The format of extended header data is weakly specified. Each extension + must be an integer multiple of 16 bytes long. The first 8 bytes of each + extension comprise 2 integers: + int esize , ecode ; + These values may need to be byte-swapped, as indicated by dim[0] for + the rest of the header. + * esize is the number of bytes that form the extended header data + + esize must be a positive integral multiple of 16 + + this length includes the 8 bytes of esize and ecode themselves + * ecode is a non-negative integer that indicates the format of the + extended header data that follows + + different ecode values are assigned to different developer groups + + at present, the "registered" values for code are + = 0 = unknown private format (not recommended!) + = 2 = DICOM format (i.e., attribute tags and values) + = 4 = AFNI group (i.e., ASCII XML-ish elements) + In the interests of interoperability (a primary rationale for NIfTI), + groups developing software that uses this extension mechanism are + encouraged to document and publicize the format of their extensions. + To this end, the NIfTI DFWG will assign even numbered codes upon request + to groups submitting at least rudimentary documentation for the format + of their extension; at present, the contact is mailto:rwcox@nih.gov. + The assigned codes and documentation will be posted on the NIfTI + website. All odd values of ecode (and 0) will remain unassigned; + at least, until the even ones are used up, when we get to 2,147,483,646. + + Note that the other contents of the extended header data section are + totally unspecified by the NIfTI-1 standard. In particular, if binary + data is stored in such a section, its byte order is not necessarily + the same as that given by examining dim[0]; it is incumbent on the + programs dealing with such data to determine the byte order of binary + extended header data. + + Multiple extended header sections are allowed, each starting with an + esize,ecode value pair. The first esize value, as described above, + is at bytes #352-355 in the .hdr or .nii file (files start at byte #0). + If this value is positive, then the second (esize2) will be found + starting at byte #352+esize1 , the third (esize3) at byte #352+esize1+esize2, + et cetera. Of course, in a .nii file, the value of vox_offset must + be compatible with these extensions. If a malformed file indicates + that an extended header data section would run past vox_offset, then + the entire extended header section should be ignored. In a .hdr file, + if an extended header data section would run past the end-of-file, + that extended header data should also be ignored. + + With the above scheme, a program can successively examine the esize + and ecode values, and skip over each extended header section if the + program doesn't know how to interpret the data within. Of course, any + program can simply ignore all extended header sections simply by jumping + straight to the image data using vox_offset. +-----------------------------------------------------------------------------*/ + +/*! \struct nifti1_extender + \brief This structure represents a 4-byte string that should follow the + binary nifti_1_header data in a NIFTI-1 header file. If the char + values are {1,0,0,0}, the file is expected to contain extensions, + values of {0,0,0,0} imply the file does not contain extensions. + Other sequences of values are not currently defined. + */ +struct nifti1_extender { char extension[4] ; } ; +typedef struct nifti1_extender nifti1_extender ; + +/*! \struct nifti1_extension + \brief Data structure defining the fields of a header extension. + */ +struct nifti1_extension { + int esize ; /*!< size of extension, in bytes (must be multiple of 16) */ + int ecode ; /*!< extension code, one of the NIFTI_ECODE_ values */ + char * edata ; /*!< raw data, with no byte swapping (length is esize-8) */ +} ; +typedef struct nifti1_extension nifti1_extension ; + +/*---------------------------------------------------------------------------*/ +/* DATA DIMENSIONALITY (as in ANALYZE 7.5): + --------------------------------------- + dim[0] = number of dimensions; + - if dim[0] is outside range 1..7, then the header information + needs to be byte swapped appropriately + - ANALYZE supports dim[0] up to 7, but NIFTI-1 reserves + dimensions 1,2,3 for space (x,y,z), 4 for time (t), and + 5,6,7 for anything else needed. + + dim[i] = length of dimension #i, for i=1..dim[0] (must be positive) + - also see the discussion of intent_code, far below + + pixdim[i] = voxel width along dimension #i, i=1..dim[0] (positive) + - cf. ORIENTATION section below for use of pixdim[0] + - the units of pixdim can be specified with the xyzt_units + field (also described far below). + + Number of bits per voxel value is in bitpix, which MUST correspond with + the datatype field. The total number of bytes in the image data is + dim[1] * ... * dim[dim[0]] * bitpix / 8 + + In NIFTI-1 files, dimensions 1,2,3 are for space, dimension 4 is for time, + and dimension 5 is for storing multiple values at each spatiotemporal + voxel. Some examples: + - A typical whole-brain FMRI experiment's time series: + - dim[0] = 4 + - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM + - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC + - dim[3] = 20 pixdim[3] = 5.0 + - dim[4] = 120 pixdim[4] = 2.0 + - A typical T1-weighted anatomical volume: + - dim[0] = 3 + - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM + - dim[2] = 256 pixdim[2] = 1.0 + - dim[3] = 128 pixdim[3] = 1.1 + - A single slice EPI time series: + - dim[0] = 4 + - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM + - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC + - dim[3] = 1 pixdim[3] = 5.0 + - dim[4] = 1200 pixdim[4] = 0.2 + - A 3-vector stored at each point in a 3D volume: + - dim[0] = 5 + - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM + - dim[2] = 256 pixdim[2] = 1.0 + - dim[3] = 128 pixdim[3] = 1.1 + - dim[4] = 1 pixdim[4] = 0.0 + - dim[5] = 3 intent_code = NIFTI_INTENT_VECTOR + - A single time series with a 3x3 matrix at each point: + - dim[0] = 5 + - dim[1] = 1 xyzt_units = NIFTI_UNITS_SEC + - dim[2] = 1 + - dim[3] = 1 + - dim[4] = 1200 pixdim[4] = 0.2 + - dim[5] = 9 intent_code = NIFTI_INTENT_GENMATRIX + - intent_p1 = intent_p2 = 3.0 (indicates matrix dimensions) +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* DATA STORAGE: + ------------ + If the magic field is "n+1", then the voxel data is stored in the + same file as the header. In this case, the voxel data starts at offset + (int)vox_offset into the header file. Thus, vox_offset=352.0 means that + the data starts immediately after the NIFTI-1 header. If vox_offset is + greater than 352, the NIFTI-1 format does not say much about the + contents of the dataset file between the end of the header and the + start of the data. + + FILES: + ----- + If the magic field is "ni1", then the voxel data is stored in the + associated ".img" file, starting at offset 0 (i.e., vox_offset is not + used in this case, and should be set to 0.0). + + When storing NIFTI-1 datasets in pairs of files, it is customary to name + the files in the pattern "name.hdr" and "name.img", as in ANALYZE 7.5. + When storing in a single file ("n+1"), the file name should be in + the form "name.nii" (the ".nft" and ".nif" suffixes are already taken; + cf. http://www.icdatamaster.com/n.html ). + + BYTE ORDERING: + ------------- + The byte order of the data arrays is presumed to be the same as the byte + order of the header (which is determined by examining dim[0]). + + Floating point types are presumed to be stored in IEEE-754 format. +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* DETAILS ABOUT vox_offset: + ------------------------ + In a .nii file, the vox_offset field value is interpreted as the start + location of the image data bytes in that file. In a .hdr/.img file pair, + the vox_offset field value is the start location of the image data + bytes in the .img file. + * If vox_offset is less than 352 in a .nii file, it is equivalent + to 352 (i.e., image data never starts before byte #352 in a .nii file). + * The default value for vox_offset in a .nii file is 352. + * In a .hdr file, the default value for vox_offset is 0. + * vox_offset should be an integer multiple of 16; otherwise, some + programs may not work properly (e.g., SPM). This is to allow + memory-mapped input to be properly byte-aligned. + Note that since vox_offset is an IEEE-754 32 bit float (for compatibility + with the ANALYZE-7.5 format), it effectively has a 24 bit mantissa. All + integers from 0 to 2^24 can be represented exactly in this format, but not + all larger integers are exactly storable as IEEE-754 32 bit floats. However, + unless you plan to have vox_offset be potentially larger than 16 MB, this + should not be an issue. (Actually, any integral multiple of 16 up to 2^27 + can be represented exactly in this format, which allows for up to 128 MB + of random information before the image data. If that isn't enough, then + perhaps this format isn't right for you.) + + In a .img file (i.e., image data stored separately from the NIfTI-1 + header), data bytes between #0 and #vox_offset-1 (inclusive) are completely + undefined and unregulated by the NIfTI-1 standard. One potential use of + having vox_offset > 0 in the .hdr/.img file pair storage method is to make + the .img file be a copy of (or link to) a pre-existing image file in some + other format, such as DICOM; then vox_offset would be set to the offset of + the image data in this file. (It may not be possible to follow the + "multiple-of-16 rule" with an arbitrary external file; using the NIfTI-1 + format in such a case may lead to a file that is incompatible with software + that relies on vox_offset being a multiple of 16.) + + In a .nii file, data bytes between #348 and #vox_offset-1 (inclusive) may + be used to store user-defined extra information; similarly, in a .hdr file, + any data bytes after byte #347 are available for user-defined extra + information. The (very weak) regulation of this extra header data is + described elsewhere. +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* DATA SCALING: + ------------ + If the scl_slope field is nonzero, then each voxel value in the dataset + should be scaled as + y = scl_slope * x + scl_inter + where x = voxel value stored + y = "true" voxel value + Normally, we would expect this scaling to be used to store "true" floating + values in a smaller integer datatype, but that is not required. That is, + it is legal to use scaling even if the datatype is a float type (crazy, + perhaps, but legal). + - However, the scaling is to be ignored if datatype is DT_RGB24. + - If datatype is a complex type, then the scaling is to be + applied to both the real and imaginary parts. + + The cal_min and cal_max fields (if nonzero) are used for mapping (possibly + scaled) dataset values to display colors: + - Minimum display intensity (black) corresponds to dataset value cal_min. + - Maximum display intensity (white) corresponds to dataset value cal_max. + - Dataset values below cal_min should display as black also, and values + above cal_max as white. + - Colors "black" and "white", of course, may refer to any scalar display + scheme (e.g., a color lookup table specified via aux_file). + - cal_min and cal_max only make sense when applied to scalar-valued + datasets (i.e., dim[0] < 5 or dim[5] = 1). +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* TYPE OF DATA (acceptable values for datatype field): + --------------------------------------------------- + Values of datatype smaller than 256 are ANALYZE 7.5 compatible. + Larger values are NIFTI-1 additions. These are all multiples of 256, so + that no bits below position 8 are set in datatype. But there is no need + to use only powers-of-2, as the original ANALYZE 7.5 datatype codes do. + + The additional codes are intended to include a complete list of basic + scalar types, including signed and unsigned integers from 8 to 64 bits, + floats from 32 to 128 bits, and complex (float pairs) from 64 to 256 bits. + + Note that most programs will support only a few of these datatypes! + A NIFTI-1 program should fail gracefully (e.g., print a warning message) + when it encounters a dataset with a type it doesn't like. +-----------------------------------------------------------------------------*/ + +#undef DT_UNKNOWN /* defined in dirent.h on some Unix systems */ + +/*! \defgroup NIFTI1_DATATYPES + \brief nifti1 datatype codes + @{ + */ + /*--- the original ANALYZE 7.5 type codes ---*/ +#define DT_NONE 0 +#define DT_UNKNOWN 0 /* what it says, dude */ +#define DT_BINARY 1 /* binary (1 bit/voxel) */ +#define DT_UNSIGNED_CHAR 2 /* unsigned char (8 bits/voxel) */ +#define DT_SIGNED_SHORT 4 /* signed short (16 bits/voxel) */ +#define DT_SIGNED_INT 8 /* signed int (32 bits/voxel) */ +#define DT_FLOAT 16 /* float (32 bits/voxel) */ +#define DT_COMPLEX 32 /* complex (64 bits/voxel) */ +#define DT_DOUBLE 64 /* double (64 bits/voxel) */ +#define DT_RGB 128 /* RGB triple (24 bits/voxel) */ +#define DT_ALL 255 /* not very useful (?) */ + + /*----- another set of names for the same ---*/ +#define DT_UINT8 2 +#define DT_INT16 4 +#define DT_INT32 8 +#define DT_FLOAT32 16 +#define DT_COMPLEX64 32 +#define DT_FLOAT64 64 +#define DT_RGB24 128 + + /*------------------- new codes for NIFTI ---*/ +#define DT_INT8 256 /* signed char (8 bits) */ +#define DT_UINT16 512 /* unsigned short (16 bits) */ +#define DT_UINT32 768 /* unsigned int (32 bits) */ +#define DT_INT64 1024 /* long long (64 bits) */ +#define DT_UINT64 1280 /* unsigned long long (64 bits) */ +#define DT_FLOAT128 1536 /* long double (128 bits) */ +#define DT_COMPLEX128 1792 /* double pair (128 bits) */ +#define DT_COMPLEX256 2048 /* long double pair (256 bits) */ +#define DT_RGBA32 2304 /* 4 byte RGBA (32 bits/voxel) */ +/* @} */ + + + /*------- aliases for all the above codes ---*/ + +/*! \defgroup NIFTI1_DATATYPE_ALIASES + \brief aliases for the nifti1 datatype codes + @{ + */ + /*! unsigned char. */ +#define NIFTI_TYPE_UINT8 2 + /*! signed short. */ +#define NIFTI_TYPE_INT16 4 + /*! signed int. */ +#define NIFTI_TYPE_INT32 8 + /*! 32 bit float. */ +#define NIFTI_TYPE_FLOAT32 16 + /*! 64 bit complex = 2 32 bit floats. */ +#define NIFTI_TYPE_COMPLEX64 32 + /*! 64 bit float = double. */ +#define NIFTI_TYPE_FLOAT64 64 + /*! 3 8 bit bytes. */ +#define NIFTI_TYPE_RGB24 128 + /*! signed char. */ +#define NIFTI_TYPE_INT8 256 + /*! unsigned short. */ +#define NIFTI_TYPE_UINT16 512 + /*! unsigned int. */ +#define NIFTI_TYPE_UINT32 768 + /*! signed long long. */ +#define NIFTI_TYPE_INT64 1024 + /*! unsigned long long. */ +#define NIFTI_TYPE_UINT64 1280 + /*! 128 bit float = long double. */ +#define NIFTI_TYPE_FLOAT128 1536 + /*! 128 bit complex = 2 64 bit floats. */ +#define NIFTI_TYPE_COMPLEX128 1792 + /*! 256 bit complex = 2 128 bit floats */ +#define NIFTI_TYPE_COMPLEX256 2048 + /*! 4 8 bit bytes. */ +#define NIFTI_TYPE_RGBA32 2304 +/* @} */ + + /*-------- sample typedefs for complicated types ---*/ +#if 0 +typedef struct { float r,i; } complex_float ; +typedef struct { double r,i; } complex_double ; +typedef struct { long double r,i; } complex_longdouble ; +typedef struct { unsigned char r,g,b; } rgb_byte ; +#endif + +/*---------------------------------------------------------------------------*/ +/* INTERPRETATION OF VOXEL DATA: + ---------------------------- + The intent_code field can be used to indicate that the voxel data has + some particular meaning. In particular, a large number of codes is + given to indicate that the the voxel data should be interpreted as + being drawn from a given probability distribution. + + VECTOR-VALUED DATASETS: + ---------------------- + The 5th dimension of the dataset, if present (i.e., dim[0]=5 and + dim[5] > 1), contains multiple values (e.g., a vector) to be stored + at each spatiotemporal location. For example, the header values + - dim[0] = 5 + - dim[1] = 64 + - dim[2] = 64 + - dim[3] = 20 + - dim[4] = 1 (indicates no time axis) + - dim[5] = 3 + - datatype = DT_FLOAT + - intent_code = NIFTI_INTENT_VECTOR + mean that this dataset should be interpreted as a 3D volume (64x64x20), + with a 3-vector of floats defined at each point in the 3D grid. + + A program reading a dataset with a 5th dimension may want to reformat + the image data to store each voxels' set of values together in a struct + or array. This programming detail, however, is beyond the scope of the + NIFTI-1 file specification! Uses of dimensions 6 and 7 are also not + specified here. + + STATISTICAL PARAMETRIC DATASETS (i.e., SPMs): + -------------------------------------------- + Values of intent_code from NIFTI_FIRST_STATCODE to NIFTI_LAST_STATCODE + (inclusive) indicate that the numbers in the dataset should be interpreted + as being drawn from a given distribution. Most such distributions have + auxiliary parameters (e.g., NIFTI_INTENT_TTEST has 1 DOF parameter). + + If the dataset DOES NOT have a 5th dimension, then the auxiliary parameters + are the same for each voxel, and are given in header fields intent_p1, + intent_p2, and intent_p3. + + If the dataset DOES have a 5th dimension, then the auxiliary parameters + are different for each voxel. For example, the header values + - dim[0] = 5 + - dim[1] = 128 + - dim[2] = 128 + - dim[3] = 1 (indicates a single slice) + - dim[4] = 1 (indicates no time axis) + - dim[5] = 2 + - datatype = DT_FLOAT + - intent_code = NIFTI_INTENT_TTEST + mean that this is a 2D dataset (128x128) of t-statistics, with the + t-statistic being in the first "plane" of data and the degrees-of-freedom + parameter being in the second "plane" of data. + + If the dataset 5th dimension is used to store the voxel-wise statistical + parameters, then dim[5] must be 1 plus the number of parameters required + by that distribution (e.g., intent_code=NIFTI_INTENT_TTEST implies dim[5] + must be 2, as in the example just above). + + Note: intent_code values 2..10 are compatible with AFNI 1.5x (which is + why there is no code with value=1, which is obsolescent in AFNI). + + OTHER INTENTIONS: + ---------------- + The purpose of the intent_* fields is to help interpret the values + stored in the dataset. Some non-statistical values for intent_code + and conventions are provided for storing other complex data types. + + The intent_name field provides space for a 15 character (plus 0 byte) + 'name' string for the type of data stored. Examples: + - intent_code = NIFTI_INTENT_ESTIMATE; intent_name = "T1"; + could be used to signify that the voxel values are estimates of the + NMR parameter T1. + - intent_code = NIFTI_INTENT_TTEST; intent_name = "House"; + could be used to signify that the voxel values are t-statistics + for the significance of 'activation' response to a House stimulus. + - intent_code = NIFTI_INTENT_DISPVECT; intent_name = "ToMNI152"; + could be used to signify that the voxel values are a displacement + vector that transforms each voxel (x,y,z) location to the + corresponding location in the MNI152 standard brain. + - intent_code = NIFTI_INTENT_SYMMATRIX; intent_name = "DTI"; + could be used to signify that the voxel values comprise a diffusion + tensor image. + + If no data name is implied or needed, intent_name[0] should be set to 0. +-----------------------------------------------------------------------------*/ + + /*! default: no intention is indicated in the header. */ + +#define NIFTI_INTENT_NONE 0 + + /*-------- These codes are for probability distributions ---------------*/ + /* Most distributions have a number of parameters, + below denoted by p1, p2, and p3, and stored in + - intent_p1, intent_p2, intent_p3 if dataset doesn't have 5th dimension + - image data array if dataset does have 5th dimension + + Functions to compute with many of the distributions below can be found + in the CDF library from U Texas. + + Formulas for and discussions of these distributions can be found in the + following books: + + [U] Univariate Discrete Distributions, + NL Johnson, S Kotz, AW Kemp. + + [C1] Continuous Univariate Distributions, vol. 1, + NL Johnson, S Kotz, N Balakrishnan. + + [C2] Continuous Univariate Distributions, vol. 2, + NL Johnson, S Kotz, N Balakrishnan. */ + /*----------------------------------------------------------------------*/ + + /*! [C2, chap 32] Correlation coefficient R (1 param): + p1 = degrees of freedom + R/sqrt(1-R*R) is t-distributed with p1 DOF. */ + +/*! \defgroup NIFTI1_INTENT_CODES + \brief nifti1 intent codes, to describe intended meaning of dataset contents + @{ + */ +#define NIFTI_INTENT_CORREL 2 + + /*! [C2, chap 28] Student t statistic (1 param): p1 = DOF. */ + +#define NIFTI_INTENT_TTEST 3 + + /*! [C2, chap 27] Fisher F statistic (2 params): + p1 = numerator DOF, p2 = denominator DOF. */ + +#define NIFTI_INTENT_FTEST 4 + + /*! [C1, chap 13] Standard normal (0 params): Density = N(0,1). */ + +#define NIFTI_INTENT_ZSCORE 5 + + /*! [C1, chap 18] Chi-squared (1 param): p1 = DOF. + Density(x) proportional to exp(-x/2) * x^(p1/2-1). */ + +#define NIFTI_INTENT_CHISQ 6 + + /*! [C2, chap 25] Beta distribution (2 params): p1=a, p2=b. + Density(x) proportional to x^(a-1) * (1-x)^(b-1). */ + +#define NIFTI_INTENT_BETA 7 + + /*! [U, chap 3] Binomial distribution (2 params): + p1 = number of trials, p2 = probability per trial. + Prob(x) = (p1 choose x) * p2^x * (1-p2)^(p1-x), for x=0,1,...,p1. */ + +#define NIFTI_INTENT_BINOM 8 + + /*! [C1, chap 17] Gamma distribution (2 params): + p1 = shape, p2 = scale. + Density(x) proportional to x^(p1-1) * exp(-p2*x). */ + +#define NIFTI_INTENT_GAMMA 9 + + /*! [U, chap 4] Poisson distribution (1 param): p1 = mean. + Prob(x) = exp(-p1) * p1^x / x! , for x=0,1,2,.... */ + +#define NIFTI_INTENT_POISSON 10 + + /*! [C1, chap 13] Normal distribution (2 params): + p1 = mean, p2 = standard deviation. */ + +#define NIFTI_INTENT_NORMAL 11 + + /*! [C2, chap 30] Noncentral F statistic (3 params): + p1 = numerator DOF, p2 = denominator DOF, + p3 = numerator noncentrality parameter. */ + +#define NIFTI_INTENT_FTEST_NONC 12 + + /*! [C2, chap 29] Noncentral chi-squared statistic (2 params): + p1 = DOF, p2 = noncentrality parameter. */ + +#define NIFTI_INTENT_CHISQ_NONC 13 + + /*! [C2, chap 23] Logistic distribution (2 params): + p1 = location, p2 = scale. + Density(x) proportional to sech^2((x-p1)/(2*p2)). */ + +#define NIFTI_INTENT_LOGISTIC 14 + + /*! [C2, chap 24] Laplace distribution (2 params): + p1 = location, p2 = scale. + Density(x) proportional to exp(-abs(x-p1)/p2). */ + +#define NIFTI_INTENT_LAPLACE 15 + + /*! [C2, chap 26] Uniform distribution: p1 = lower end, p2 = upper end. */ + +#define NIFTI_INTENT_UNIFORM 16 + + /*! [C2, chap 31] Noncentral t statistic (2 params): + p1 = DOF, p2 = noncentrality parameter. */ + +#define NIFTI_INTENT_TTEST_NONC 17 + + /*! [C1, chap 21] Weibull distribution (3 params): + p1 = location, p2 = scale, p3 = power. + Density(x) proportional to + ((x-p1)/p2)^(p3-1) * exp(-((x-p1)/p2)^p3) for x > p1. */ + +#define NIFTI_INTENT_WEIBULL 18 + + /*! [C1, chap 18] Chi distribution (1 param): p1 = DOF. + Density(x) proportional to x^(p1-1) * exp(-x^2/2) for x > 0. + p1 = 1 = 'half normal' distribution + p1 = 2 = Rayleigh distribution + p1 = 3 = Maxwell-Boltzmann distribution. */ + +#define NIFTI_INTENT_CHI 19 + + /*! [C1, chap 15] Inverse Gaussian (2 params): + p1 = mu, p2 = lambda + Density(x) proportional to + exp(-p2*(x-p1)^2/(2*p1^2*x)) / x^3 for x > 0. */ + +#define NIFTI_INTENT_INVGAUSS 20 + + /*! [C2, chap 22] Extreme value type I (2 params): + p1 = location, p2 = scale + cdf(x) = exp(-exp(-(x-p1)/p2)). */ + +#define NIFTI_INTENT_EXTVAL 21 + + /*! Data is a 'p-value' (no params). */ + +#define NIFTI_INTENT_PVAL 22 + + /*! Data is ln(p-value) (no params). + To be safe, a program should compute p = exp(-abs(this_value)). + The nifti_stats.c library returns this_value + as positive, so that this_value = -log(p). */ + + +#define NIFTI_INTENT_LOGPVAL 23 + + /*! Data is log10(p-value) (no params). + To be safe, a program should compute p = pow(10.,-abs(this_value)). + The nifti_stats.c library returns this_value + as positive, so that this_value = -log10(p). */ + +#define NIFTI_INTENT_LOG10PVAL 24 + + /*! Smallest intent_code that indicates a statistic. */ + +#define NIFTI_FIRST_STATCODE 2 + + /*! Largest intent_code that indicates a statistic. */ + +#define NIFTI_LAST_STATCODE 24 + + /*---------- these values for intent_code aren't for statistics ----------*/ + + /*! To signify that the value at each voxel is an estimate + of some parameter, set intent_code = NIFTI_INTENT_ESTIMATE. + The name of the parameter may be stored in intent_name. */ + +#define NIFTI_INTENT_ESTIMATE 1001 + + /*! To signify that the value at each voxel is an index into + some set of labels, set intent_code = NIFTI_INTENT_LABEL. + The filename with the labels may stored in aux_file. */ + +#define NIFTI_INTENT_LABEL 1002 + + /*! To signify that the value at each voxel is an index into the + NeuroNames labels set, set intent_code = NIFTI_INTENT_NEURONAME. */ + +#define NIFTI_INTENT_NEURONAME 1003 + + /*! To store an M x N matrix at each voxel: + - dataset must have a 5th dimension (dim[0]=5 and dim[5]>1) + - intent_code must be NIFTI_INTENT_GENMATRIX + - dim[5] must be M*N + - intent_p1 must be M (in float format) + - intent_p2 must be N (ditto) + - the matrix values A[i][[j] are stored in row-order: + - A[0][0] A[0][1] ... A[0][N-1] + - A[1][0] A[1][1] ... A[1][N-1] + - etc., until + - A[M-1][0] A[M-1][1] ... A[M-1][N-1] */ + +#define NIFTI_INTENT_GENMATRIX 1004 + + /*! To store an NxN symmetric matrix at each voxel: + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_SYMMATRIX + - dim[5] must be N*(N+1)/2 + - intent_p1 must be N (in float format) + - the matrix values A[i][[j] are stored in row-order: + - A[0][0] + - A[1][0] A[1][1] + - A[2][0] A[2][1] A[2][2] + - etc.: row-by-row */ + +#define NIFTI_INTENT_SYMMATRIX 1005 + + /*! To signify that the vector value at each voxel is to be taken + as a displacement field or vector: + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_DISPVECT + - dim[5] must be the dimensionality of the displacement + vector (e.g., 3 for spatial displacement, 2 for in-plane) */ + +#define NIFTI_INTENT_DISPVECT 1006 /* specifically for displacements */ +#define NIFTI_INTENT_VECTOR 1007 /* for any other type of vector */ + + /*! To signify that the vector value at each voxel is really a + spatial coordinate (e.g., the vertices or nodes of a surface mesh): + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_POINTSET + - dim[0] = 5 + - dim[1] = number of points + - dim[2] = dim[3] = dim[4] = 1 + - dim[5] must be the dimensionality of space (e.g., 3 => 3D space). + - intent_name may describe the object these points come from + (e.g., "pial", "gray/white" , "EEG", "MEG"). */ + +#define NIFTI_INTENT_POINTSET 1008 + + /*! To signify that the vector value at each voxel is really a triple + of indexes (e.g., forming a triangle) from a pointset dataset: + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_TRIANGLE + - dim[0] = 5 + - dim[1] = number of triangles + - dim[2] = dim[3] = dim[4] = 1 + - dim[5] = 3 + - datatype should be an integer type (preferably DT_INT32) + - the data values are indexes (0,1,...) into a pointset dataset. */ + +#define NIFTI_INTENT_TRIANGLE 1009 + + /*! To signify that the vector value at each voxel is a quaternion: + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_QUATERNION + - dim[0] = 5 + - dim[5] = 4 + - datatype should be a floating point type */ + +#define NIFTI_INTENT_QUATERNION 1010 + + /*! Dimensionless value - no params - although, as in _ESTIMATE + the name of the parameter may be stored in intent_name. */ + +#define NIFTI_INTENT_DIMLESS 1011 + + /*---------- these values apply to GIFTI datasets ----------*/ + + /*! To signify that the value at each location is from a time series. */ + +#define NIFTI_INTENT_TIME_SERIES 2001 + + /*! To signify that the value at each location is a node index, from + a complete surface dataset. */ + +#define NIFTI_INTENT_NODE_INDEX 2002 + + /*! To signify that the vector value at each location is an RGB triplet, + of whatever type. + - dataset must have a 5th dimension + - dim[0] = 5 + - dim[1] = number of nodes + - dim[2] = dim[3] = dim[4] = 1 + - dim[5] = 3 + */ + +#define NIFTI_INTENT_RGB_VECTOR 2003 + + /*! To signify that the vector value at each location is a 4 valued RGBA + vector, of whatever type. + - dataset must have a 5th dimension + - dim[0] = 5 + - dim[1] = number of nodes + - dim[2] = dim[3] = dim[4] = 1 + - dim[5] = 4 + */ + +#define NIFTI_INTENT_RGBA_VECTOR 2004 + + /*! To signify that the value at each location is a shape value, such + as the curvature. */ + +#define NIFTI_INTENT_SHAPE 2005 + +/* @} */ + +/*---------------------------------------------------------------------------*/ +/* 3D IMAGE (VOLUME) ORIENTATION AND LOCATION IN SPACE: + --------------------------------------------------- + There are 3 different methods by which continuous coordinates can + attached to voxels. The discussion below emphasizes 3D volumes, and + the continuous coordinates are referred to as (x,y,z). The voxel + index coordinates (i.e., the array indexes) are referred to as (i,j,k), + with valid ranges: + i = 0 .. dim[1]-1 + j = 0 .. dim[2]-1 (if dim[0] >= 2) + k = 0 .. dim[3]-1 (if dim[0] >= 3) + The (x,y,z) coordinates refer to the CENTER of a voxel. In methods + 2 and 3, the (x,y,z) axes refer to a subject-based coordinate system, + with + +x = Right +y = Anterior +z = Superior. + This is a right-handed coordinate system. However, the exact direction + these axes point with respect to the subject depends on qform_code + (Method 2) and sform_code (Method 3). + + N.B.: The i index varies most rapidly, j index next, k index slowest. + Thus, voxel (i,j,k) is stored starting at location + (i + j*dim[1] + k*dim[1]*dim[2]) * (bitpix/8) + into the dataset array. + + N.B.: The ANALYZE 7.5 coordinate system is + +x = Left +y = Anterior +z = Superior + which is a left-handed coordinate system. This backwardness is + too difficult to tolerate, so this NIFTI-1 standard specifies the + coordinate order which is most common in functional neuroimaging. + + N.B.: The 3 methods below all give the locations of the voxel centers + in the (x,y,z) coordinate system. In many cases, programs will wish + to display image data on some other grid. In such a case, the program + will need to convert its desired (x,y,z) values into (i,j,k) values + in order to extract (or interpolate) the image data. This operation + would be done with the inverse transformation to those described below. + + N.B.: Method 2 uses a factor 'qfac' which is either -1 or 1; qfac is + stored in the otherwise unused pixdim[0]. If pixdim[0]=0.0 (which + should not occur), we take qfac=1. Of course, pixdim[0] is only used + when reading a NIFTI-1 header, not when reading an ANALYZE 7.5 header. + + N.B.: The units of (x,y,z) can be specified using the xyzt_units field. + + METHOD 1 (the "old" way, used only when qform_code = 0): + ------------------------------------------------------- + The coordinate mapping from (i,j,k) to (x,y,z) is the ANALYZE + 7.5 way. This is a simple scaling relationship: + + x = pixdim[1] * i + y = pixdim[2] * j + z = pixdim[3] * k + + No particular spatial orientation is attached to these (x,y,z) + coordinates. (NIFTI-1 does not have the ANALYZE 7.5 orient field, + which is not general and is often not set properly.) This method + is not recommended, and is present mainly for compatibility with + ANALYZE 7.5 files. + + METHOD 2 (used when qform_code > 0, which should be the "normal" case): + --------------------------------------------------------------------- + The (x,y,z) coordinates are given by the pixdim[] scales, a rotation + matrix, and a shift. This method is intended to represent + "scanner-anatomical" coordinates, which are often embedded in the + image header (e.g., DICOM fields (0020,0032), (0020,0037), (0028,0030), + and (0018,0050)), and represent the nominal orientation and location of + the data. This method can also be used to represent "aligned" + coordinates, which would typically result from some post-acquisition + alignment of the volume to a standard orientation (e.g., the same + subject on another day, or a rigid rotation to true anatomical + orientation from the tilted position of the subject in the scanner). + The formula for (x,y,z) in terms of header parameters and (i,j,k) is: + + [ x ] [ R11 R12 R13 ] [ pixdim[1] * i ] [ qoffset_x ] + [ y ] = [ R21 R22 R23 ] [ pixdim[2] * j ] + [ qoffset_y ] + [ z ] [ R31 R32 R33 ] [ qfac * pixdim[3] * k ] [ qoffset_z ] + + The qoffset_* shifts are in the NIFTI-1 header. Note that the center + of the (i,j,k)=(0,0,0) voxel (first value in the dataset array) is + just (x,y,z)=(qoffset_x,qoffset_y,qoffset_z). + + The rotation matrix R is calculated from the quatern_* parameters. + This calculation is described below. + + The scaling factor qfac is either 1 or -1. The rotation matrix R + defined by the quaternion parameters is "proper" (has determinant 1). + This may not fit the needs of the data; for example, if the image + grid is + i increases from Left-to-Right + j increases from Anterior-to-Posterior + k increases from Inferior-to-Superior + Then (i,j,k) is a left-handed triple. In this example, if qfac=1, + the R matrix would have to be + + [ 1 0 0 ] + [ 0 -1 0 ] which is "improper" (determinant = -1). + [ 0 0 1 ] + + If we set qfac=-1, then the R matrix would be + + [ 1 0 0 ] + [ 0 -1 0 ] which is proper. + [ 0 0 -1 ] + + This R matrix is represented by quaternion [a,b,c,d] = [0,1,0,0] + (which encodes a 180 degree rotation about the x-axis). + + METHOD 3 (used when sform_code > 0): + ----------------------------------- + The (x,y,z) coordinates are given by a general affine transformation + of the (i,j,k) indexes: + + x = srow_x[0] * i + srow_x[1] * j + srow_x[2] * k + srow_x[3] + y = srow_y[0] * i + srow_y[1] * j + srow_y[2] * k + srow_y[3] + z = srow_z[0] * i + srow_z[1] * j + srow_z[2] * k + srow_z[3] + + The srow_* vectors are in the NIFTI_1 header. Note that no use is + made of pixdim[] in this method. + + WHY 3 METHODS? + -------------- + Method 1 is provided only for backwards compatibility. The intention + is that Method 2 (qform_code > 0) represents the nominal voxel locations + as reported by the scanner, or as rotated to some fiducial orientation and + location. Method 3, if present (sform_code > 0), is to be used to give + the location of the voxels in some standard space. The sform_code + indicates which standard space is present. Both methods 2 and 3 can be + present, and be useful in different contexts (method 2 for displaying the + data on its original grid; method 3 for displaying it on a standard grid). + + In this scheme, a dataset would originally be set up so that the + Method 2 coordinates represent what the scanner reported. Later, + a registration to some standard space can be computed and inserted + in the header. Image display software can use either transform, + depending on its purposes and needs. + + In Method 2, the origin of coordinates would generally be whatever + the scanner origin is; for example, in MRI, (0,0,0) is the center + of the gradient coil. + + In Method 3, the origin of coordinates would depend on the value + of sform_code; for example, for the Talairach coordinate system, + (0,0,0) corresponds to the Anterior Commissure. + + QUATERNION REPRESENTATION OF ROTATION MATRIX (METHOD 2) + ------------------------------------------------------- + The orientation of the (x,y,z) axes relative to the (i,j,k) axes + in 3D space is specified using a unit quaternion [a,b,c,d], where + a*a+b*b+c*c+d*d=1. The (b,c,d) values are all that is needed, since + we require that a = sqrt(1.0-(b*b+c*c+d*d)) be nonnegative. The (b,c,d) + values are stored in the (quatern_b,quatern_c,quatern_d) fields. + + The quaternion representation is chosen for its compactness in + representing rotations. The (proper) 3x3 rotation matrix that + corresponds to [a,b,c,d] is + + [ a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c ] + R = [ 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b ] + [ 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b ] + + [ R11 R12 R13 ] + = [ R21 R22 R23 ] + [ R31 R32 R33 ] + + If (p,q,r) is a unit 3-vector, then rotation of angle h about that + direction is represented by the quaternion + + [a,b,c,d] = [cos(h/2), p*sin(h/2), q*sin(h/2), r*sin(h/2)]. + + Requiring a >= 0 is equivalent to requiring -Pi <= h <= Pi. (Note that + [-a,-b,-c,-d] represents the same rotation as [a,b,c,d]; there are 2 + quaternions that can be used to represent a given rotation matrix R.) + To rotate a 3-vector (x,y,z) using quaternions, we compute the + quaternion product + + [0,x',y',z'] = [a,b,c,d] * [0,x,y,z] * [a,-b,-c,-d] + + which is equivalent to the matrix-vector multiply + + [ x' ] [ x ] + [ y' ] = R [ y ] (equivalence depends on a*a+b*b+c*c+d*d=1) + [ z' ] [ z ] + + Multiplication of 2 quaternions is defined by the following: + + [a,b,c,d] = a*1 + b*I + c*J + d*K + where + I*I = J*J = K*K = -1 (I,J,K are square roots of -1) + I*J = K J*K = I K*I = J + J*I = -K K*J = -I I*K = -J (not commutative!) + For example + [a,b,0,0] * [0,0,0,1] = [0,0,-b,a] + since this expands to + (a+b*I)*(K) = (a*K+b*I*K) = (a*K-b*J). + + The above formula shows how to go from quaternion (b,c,d) to + rotation matrix and direction cosines. Conversely, given R, + we can compute the fields for the NIFTI-1 header by + + a = 0.5 * sqrt(1+R11+R22+R33) (not stored) + b = 0.25 * (R32-R23) / a => quatern_b + c = 0.25 * (R13-R31) / a => quatern_c + d = 0.25 * (R21-R12) / a => quatern_d + + If a=0 (a 180 degree rotation), alternative formulas are needed. + See the nifti1_io.c function mat44_to_quatern() for an implementation + of the various cases in converting R to [a,b,c,d]. + + Note that R-transpose (= R-inverse) would lead to the quaternion + [a,-b,-c,-d]. + + The choice to specify the qoffset_x (etc.) values in the final + coordinate system is partly to make it easy to convert DICOM images to + this format. The DICOM attribute "Image Position (Patient)" (0020,0032) + stores the (Xd,Yd,Zd) coordinates of the center of the first voxel. + Here, (Xd,Yd,Zd) refer to DICOM coordinates, and Xd=-x, Yd=-y, Zd=z, + where (x,y,z) refers to the NIFTI coordinate system discussed above. + (i.e., DICOM +Xd is Left, +Yd is Posterior, +Zd is Superior, + whereas +x is Right, +y is Anterior , +z is Superior. ) + Thus, if the (0020,0032) DICOM attribute is extracted into (px,py,pz), then + qoffset_x = -px qoffset_y = -py qoffset_z = pz + is a reasonable setting when qform_code=NIFTI_XFORM_SCANNER_ANAT. + + That is, DICOM's coordinate system is 180 degrees rotated about the z-axis + from the neuroscience/NIFTI coordinate system. To transform between DICOM + and NIFTI, you just have to negate the x- and y-coordinates. + + The DICOM attribute (0020,0037) "Image Orientation (Patient)" gives the + orientation of the x- and y-axes of the image data in terms of 2 3-vectors. + The first vector is a unit vector along the x-axis, and the second is + along the y-axis. If the (0020,0037) attribute is extracted into the + value (xa,xb,xc,ya,yb,yc), then the first two columns of the R matrix + would be + [ -xa -ya ] + [ -xb -yb ] + [ xc yc ] + The negations are because DICOM's x- and y-axes are reversed relative + to NIFTI's. The third column of the R matrix gives the direction of + displacement (relative to the subject) along the slice-wise direction. + This orientation is not encoded in the DICOM standard in a simple way; + DICOM is mostly concerned with 2D images. The third column of R will be + either the cross-product of the first 2 columns or its negative. It is + possible to infer the sign of the 3rd column by examining the coordinates + in DICOM attribute (0020,0032) "Image Position (Patient)" for successive + slices. However, this method occasionally fails for reasons that I + (RW Cox) do not understand. +-----------------------------------------------------------------------------*/ + + /* [qs]form_code value: */ /* x,y,z coordinate system refers to: */ + /*-----------------------*/ /*---------------------------------------*/ + +/*! \defgroup NIFTI1_XFORM_CODES + \brief nifti1 xform codes to describe the "standard" coordinate system + @{ + */ + /*! Arbitrary coordinates (Method 1). */ + +#define NIFTI_XFORM_UNKNOWN 0 + + /*! Scanner-based anatomical coordinates */ + +#define NIFTI_XFORM_SCANNER_ANAT 1 + + /*! Coordinates aligned to another file's, + or to anatomical "truth". */ + +#define NIFTI_XFORM_ALIGNED_ANAT 2 + + /*! Coordinates aligned to Talairach- + Tournoux Atlas; (0,0,0)=AC, etc. */ + +#define NIFTI_XFORM_TALAIRACH 3 + + /*! MNI 152 normalized coordinates. */ + +#define NIFTI_XFORM_MNI_152 4 + + /*! Normalized coordinates (for + any general standard template + space). Added March 8, 2019. */ + +#define NIFTI_XFORM_TEMPLATE_OTHER 5 + +/* @} */ + +/*---------------------------------------------------------------------------*/ +/* UNITS OF SPATIAL AND TEMPORAL DIMENSIONS: + ---------------------------------------- + The codes below can be used in xyzt_units to indicate the units of pixdim. + As noted earlier, dimensions 1,2,3 are for x,y,z; dimension 4 is for + time (t). + - If dim[4]=1 or dim[0] < 4, there is no time axis. + - A single time series (no space) would be specified with + - dim[0] = 4 (for scalar data) or dim[0] = 5 (for vector data) + - dim[1] = dim[2] = dim[3] = 1 + - dim[4] = number of time points + - pixdim[4] = time step + - xyzt_units indicates units of pixdim[4] + - dim[5] = number of values stored at each time point + + Bits 0..2 of xyzt_units specify the units of pixdim[1..3] + (e.g., spatial units are values 1..7). + Bits 3..5 of xyzt_units specify the units of pixdim[4] + (e.g., temporal units are multiples of 8). + + This compression of 2 distinct concepts into 1 byte is due to the + limited space available in the 348 byte ANALYZE 7.5 header. The + macros XYZT_TO_SPACE and XYZT_TO_TIME can be used to mask off the + undesired bits from the xyzt_units fields, leaving "pure" space + and time codes. Inversely, the macro SPACE_TIME_TO_XYZT can be + used to assemble a space code (0,1,2,...,7) with a time code + (0,8,16,32,...,56) into the combined value for xyzt_units. + + Note that codes are provided to indicate the "time" axis units are + actually frequency in Hertz (_HZ), in part-per-million (_PPM) + or in radians-per-second (_RADS). + + The toffset field can be used to indicate a nonzero start point for + the time axis. That is, time point #m is at t=toffset+m*pixdim[4] + for m=0..dim[4]-1. +-----------------------------------------------------------------------------*/ + +/*! \defgroup NIFTI1_UNITS + \brief nifti1 units codes to describe the unit of measurement for + each dimension of the dataset + @{ + */ + /*! NIFTI code for unspecified units. */ +#define NIFTI_UNITS_UNKNOWN 0 + + /** Space codes are multiples of 1. **/ + /*! NIFTI code for meters. */ +#define NIFTI_UNITS_METER 1 + /*! NIFTI code for millimeters. */ +#define NIFTI_UNITS_MM 2 + /*! NIFTI code for micrometers. */ +#define NIFTI_UNITS_MICRON 3 + + /** Time codes are multiples of 8. **/ + /*! NIFTI code for seconds. */ +#define NIFTI_UNITS_SEC 8 + /*! NIFTI code for milliseconds. */ +#define NIFTI_UNITS_MSEC 16 + /*! NIFTI code for microseconds. */ +#define NIFTI_UNITS_USEC 24 + + /*** These units are for spectral data: ***/ + /*! NIFTI code for Hertz. */ +#define NIFTI_UNITS_HZ 32 + /*! NIFTI code for ppm. */ +#define NIFTI_UNITS_PPM 40 + /*! NIFTI code for radians per second. */ +#define NIFTI_UNITS_RADS 48 +/* @} */ + +#undef XYZT_TO_SPACE +#undef XYZT_TO_TIME +#define XYZT_TO_SPACE(xyzt) ( (xyzt) & 0x07 ) +#define XYZT_TO_TIME(xyzt) ( (xyzt) & 0x38 ) + +#undef SPACE_TIME_TO_XYZT +#define SPACE_TIME_TO_XYZT(ss,tt) ( (((char)(ss)) & 0x07) \ + | (((char)(tt)) & 0x38) ) + +/*---------------------------------------------------------------------------*/ +/* MRI-SPECIFIC SPATIAL AND TEMPORAL INFORMATION: + --------------------------------------------- + A few fields are provided to store some extra information + that is sometimes important when storing the image data + from an FMRI time series experiment. (After processing such + data into statistical images, these fields are not likely + to be useful.) + + { freq_dim } = These fields encode which spatial dimension (1,2, or 3) + { phase_dim } = corresponds to which acquisition dimension for MRI data. + { slice_dim } = + Examples: + Rectangular scan multi-slice EPI: + freq_dim = 1 phase_dim = 2 slice_dim = 3 (or some permutation) + Spiral scan multi-slice EPI: + freq_dim = phase_dim = 0 slice_dim = 3 + since the concepts of frequency- and phase-encoding directions + don't apply to spiral scan + + slice_duration = If this is positive, AND if slice_dim is nonzero, + indicates the amount of time used to acquire 1 slice. + slice_duration*dim[slice_dim] can be less than pixdim[4] + with a clustered acquisition method, for example. + + slice_code = If this is nonzero, AND if slice_dim is nonzero, AND + if slice_duration is positive, indicates the timing + pattern of the slice acquisition. The following codes + are defined: + NIFTI_SLICE_SEQ_INC == sequential increasing + NIFTI_SLICE_SEQ_DEC == sequential decreasing + NIFTI_SLICE_ALT_INC == alternating increasing + NIFTI_SLICE_ALT_DEC == alternating decreasing + NIFTI_SLICE_ALT_INC2 == alternating increasing #2 + NIFTI_SLICE_ALT_DEC2 == alternating decreasing #2 + { slice_start } = Indicates the start and end of the slice acquisition + { slice_end } = pattern, when slice_code is nonzero. These values + are present to allow for the possible addition of + "padded" slices at either end of the volume, which + don't fit into the slice timing pattern. If there + are no padding slices, then slice_start=0 and + slice_end=dim[slice_dim]-1 are the correct values. + For these values to be meaningful, slice_start must + be non-negative and slice_end must be greater than + slice_start. Otherwise, they should be ignored. + + The following table indicates the slice timing pattern, relative to + time=0 for the first slice acquired, for some sample cases. Here, + dim[slice_dim]=7 (there are 7 slices, labeled 0..6), slice_duration=0.1, + and slice_start=1, slice_end=5 (1 padded slice on each end). + + slice + index SEQ_INC SEQ_DEC ALT_INC ALT_DEC ALT_INC2 ALT_DEC2 + 6 : n/a n/a n/a n/a n/a n/a n/a = not applicable + 5 : 0.4 0.0 0.2 0.0 0.4 0.2 (slice time offset + 4 : 0.3 0.1 0.4 0.3 0.1 0.0 doesn't apply to + 3 : 0.2 0.2 0.1 0.1 0.3 0.3 slices outside + 2 : 0.1 0.3 0.3 0.4 0.0 0.1 the range + 1 : 0.0 0.4 0.0 0.2 0.2 0.4 slice_start .. + 0 : n/a n/a n/a n/a n/a n/a slice_end) + + The SEQ slice_codes are sequential ordering (uncommon but not unknown), + either increasing in slice number or decreasing (INC or DEC), as + illustrated above. + + The ALT slice codes are alternating ordering. The 'standard' way for + these to operate (without the '2' on the end) is for the slice timing + to start at the edge of the slice_start .. slice_end group (at slice_start + for INC and at slice_end for DEC). For the 'ALT_*2' slice_codes, the + slice timing instead starts at the first slice in from the edge (at + slice_start+1 for INC2 and at slice_end-1 for DEC2). This latter + acquisition scheme is found on some Siemens scanners. + + The fields freq_dim, phase_dim, slice_dim are all squished into the single + byte field dim_info (2 bits each, since the values for each field are + limited to the range 0..3). This unpleasantness is due to lack of space + in the 348 byte allowance. + + The macros DIM_INFO_TO_FREQ_DIM, DIM_INFO_TO_PHASE_DIM, and + DIM_INFO_TO_SLICE_DIM can be used to extract these values from the + dim_info byte. + + The macro FPS_INTO_DIM_INFO can be used to put these 3 values + into the dim_info byte. +-----------------------------------------------------------------------------*/ + +#undef DIM_INFO_TO_FREQ_DIM +#undef DIM_INFO_TO_PHASE_DIM +#undef DIM_INFO_TO_SLICE_DIM + +#define DIM_INFO_TO_FREQ_DIM(di) ( ((di) ) & 0x03 ) +#define DIM_INFO_TO_PHASE_DIM(di) ( ((di) >> 2) & 0x03 ) +#define DIM_INFO_TO_SLICE_DIM(di) ( ((di) >> 4) & 0x03 ) + +#undef FPS_INTO_DIM_INFO +#define FPS_INTO_DIM_INFO(fd,pd,sd) ( ( ( ((char)(fd)) & 0x03) ) | \ + ( ( ((char)(pd)) & 0x03) << 2 ) | \ + ( ( ((char)(sd)) & 0x03) << 4 ) ) + +/*! \defgroup NIFTI1_SLICE_ORDER + \brief nifti1 slice order codes, describing the acquisition order + of the slices + @{ + */ +#define NIFTI_SLICE_UNKNOWN 0 +#define NIFTI_SLICE_SEQ_INC 1 +#define NIFTI_SLICE_SEQ_DEC 2 +#define NIFTI_SLICE_ALT_INC 3 +#define NIFTI_SLICE_ALT_DEC 4 +#define NIFTI_SLICE_ALT_INC2 5 /* 05 May 2005: RWCox */ +#define NIFTI_SLICE_ALT_DEC2 6 /* 05 May 2005: RWCox */ +/* @} */ + +/*---------------------------------------------------------------------------*/ +/* UNUSED FIELDS: + ------------- + Some of the ANALYZE 7.5 fields marked as ++UNUSED++ may need to be set + to particular values for compatibility with other programs. The issue + of interoperability of ANALYZE 7.5 files is a murky one -- not all + programs require exactly the same set of fields. (Unobscuring this + murkiness is a principal motivation behind NIFTI-1.) + + Some of the fields that may need to be set for other (non-NIFTI aware) + software to be happy are: + + extents dbh.h says this should be 16384 + regular dbh.h says this should be the character 'r' + glmin, } dbh.h says these values should be the min and max voxel + glmax } values for the entire dataset + + It is best to initialize ALL fields in the NIFTI-1 header to 0 + (e.g., with calloc()), then fill in what is needed. +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* MISCELLANEOUS C MACROS +-----------------------------------------------------------------------------*/ + +/*.................*/ +/*! Given a nifti_1_header struct, check if it has a good magic number. + Returns NIFTI version number (1..9) if magic is good, 0 if it is not. */ + +#define NIFTI_VERSION(h) \ + ( ( (h).magic[0]=='n' && (h).magic[3]=='\0' && \ + ( (h).magic[1]=='i' || (h).magic[1]=='+' ) && \ + ( (h).magic[2]>='1' && (h).magic[2]<='9' ) ) \ + ? (h).magic[2]-'0' : 0 ) + +/*.................*/ +/*! Check if a nifti_1_header struct says if the data is stored in the + same file or in a separate file. Returns 1 if the data is in the same + file as the header, 0 if it is not. */ + +#define NIFTI_ONEFILE(h) ( (h).magic[1] == '+' ) + +/*.................*/ +/*! Check if a nifti_1_header struct needs to be byte swapped. + Returns 1 if it needs to be swapped, 0 if it does not. */ + +#define NIFTI_NEEDS_SWAP(h) ( (h).dim[0] < 0 || (h).dim[0] > 7 ) + +/*.................*/ +/*! Check if a nifti_1_header struct contains a 5th (vector) dimension. + Returns size of 5th dimension if > 1, returns 0 otherwise. */ + +#define NIFTI_5TH_DIM(h) ( ((h).dim[0]>4 && (h).dim[5]>1) ? (h).dim[5] : 0 ) + +/*****************************************************************************/ + +/*=================*/ +#ifdef __cplusplus +} +#endif +/*=================*/ + +#endif /* _NIFTI_HEADER_ */ diff --git a/packages/dcm2niix/nifti1_io_core.cpp b/packages/dcm2niix/nifti1_io_core.cpp new file mode 100644 index 00000000000..f02b08f5b57 --- /dev/null +++ b/packages/dcm2niix/nifti1_io_core.cpp @@ -0,0 +1,872 @@ +//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 +#ifndef USING_MGH_NIFTI_IO +void nifti_swap_8bytes( size_t n , void *ar ) // 4 bytes at a time +{ + size_t ii ; + unsigned char * cp0 = (unsigned char *)ar, * cp1, * cp2 ; + unsigned char tval ; + for( ii=0 ; ii < n ; ii++ ){ + cp1 = cp0; cp2 = cp0+7; + tval = *cp1; *cp1 = *cp2; *cp2 = tval; + cp1++; cp2--; + tval = *cp1; *cp1 = *cp2; *cp2 = tval; + cp1++; cp2--; + tval = *cp1; *cp1 = *cp2; *cp2 = tval; + cp1++; cp2--; + tval = *cp1; *cp1 = *cp2; *cp2 = tval; + cp0 += 8; + } + return ; +} + +void nifti_swap_4bytes( size_t n , void *ar ) // 4 bytes at a time +{ + size_t ii ; + unsigned char * cp0 = (unsigned char *)ar, * cp1, * cp2 ; + unsigned char tval ; + for( ii=0 ; ii < n ; ii++ ){ + cp1 = cp0; cp2 = cp0+3; + tval = *cp1; *cp1 = *cp2; *cp2 = tval; + cp1++; cp2--; + tval = *cp1; *cp1 = *cp2; *cp2 = tval; + cp0 += 4; + } + return ; +} + +void nifti_swap_2bytes( size_t n , void *ar ) // 2 bytes at a time +{ + size_t ii ; + unsigned char * cp1 = (unsigned char *)ar, * cp2 ; + unsigned char tval; + for( ii=0 ; ii < n ; ii++ ){ + cp2 = cp1 + 1; + tval = *cp1; *cp1 = *cp2; *cp2 = tval; + cp1 += 2; + } + return ; +} + +/*-------------------------------------------------------------------------*/ +/*! Byte swap NIFTI-1 file header in various places and ways. + + If is_nifti, swap all (even UNUSED) fields of NIfTI header. + Else, swap as a nifti_analyze75 struct. +*//*---------------------------------------------------------------------- */ +void swap_nifti_header( struct nifti_1_header *h, int is_nifti ) +{ + + /* otherwise, swap all NIFTI fields */ + + nifti_swap_4bytes(1, &h->sizeof_hdr); + nifti_swap_4bytes(1, &h->extents); + nifti_swap_2bytes(1, &h->session_error); + + nifti_swap_2bytes(8, h->dim); + nifti_swap_4bytes(1, &h->intent_p1); + nifti_swap_4bytes(1, &h->intent_p2); + nifti_swap_4bytes(1, &h->intent_p3); + + nifti_swap_2bytes(1, &h->intent_code); + nifti_swap_2bytes(1, &h->datatype); + nifti_swap_2bytes(1, &h->bitpix); + nifti_swap_2bytes(1, &h->slice_start); + + nifti_swap_4bytes(8, h->pixdim); + + nifti_swap_4bytes(1, &h->vox_offset); + nifti_swap_4bytes(1, &h->scl_slope); + nifti_swap_4bytes(1, &h->scl_inter); + nifti_swap_2bytes(1, &h->slice_end); + + nifti_swap_4bytes(1, &h->cal_max); + nifti_swap_4bytes(1, &h->cal_min); + nifti_swap_4bytes(1, &h->slice_duration); + nifti_swap_4bytes(1, &h->toffset); + nifti_swap_4bytes(1, &h->glmax); + nifti_swap_4bytes(1, &h->glmin); + + nifti_swap_2bytes(1, &h->qform_code); + nifti_swap_2bytes(1, &h->sform_code); + + nifti_swap_4bytes(1, &h->quatern_b); + nifti_swap_4bytes(1, &h->quatern_c); + nifti_swap_4bytes(1, &h->quatern_d); + nifti_swap_4bytes(1, &h->qoffset_x); + nifti_swap_4bytes(1, &h->qoffset_y); + nifti_swap_4bytes(1, &h->qoffset_z); + + nifti_swap_4bytes(4, h->srow_x); + nifti_swap_4bytes(4, h->srow_y); + nifti_swap_4bytes(4, h->srow_z); + + return ; +} +#endif +#endif + +bool littleEndianPlatform () +{ + uint32_t value = 1; + return (*((char *) &value) == 1); +} + +int isSameFloat (float a, float b) { + return (fabs (a - b) <= FLT_EPSILON); +} + +int isSameDouble (double a, double b) { + return (fabs (a - b) <= DBL_EPSILON); +} + +ivec3 setiVec3(int x, int y, int z) +{ + ivec3 v = {{x, y, z}}; + return v; +} + +vec3 setVec3(float x, float y, float z) +{ + vec3 v = {{x, y, z}}; + return v; +} + +vec4 setVec4(float x, float y, float z) +{ + vec4 v= {{x, y, z, 1}}; + return v; +} + +vec3 crossProduct(vec3 u, vec3 v) +{ + return setVec3(u.v[1]*v.v[2] - v.v[1]*u.v[2], + -u.v[0]*v.v[2] + v.v[0]*u.v[2], + u.v[0]*v.v[1] - v.v[0]*u.v[1]); +} + +float dotProduct(vec3 u, vec3 v) +{ + return (u.v[0]*v.v[0] + v.v[1]*u.v[1] + v.v[2]*u.v[2]); +} + +vec3 nifti_vect33_norm (vec3 v) { //normalize vector length + vec3 vO = v; + float vLen = sqrt( (v.v[0]*v.v[0]) + + (v.v[1]*v.v[1]) + + (v.v[2]*v.v[2])); + if (vLen <= FLT_EPSILON) return vO; //avoid divide by zero + for (int i = 0; i < 3; i++) + vO.v[i] = v.v[i]/vLen; + return vO; +} + +vec4 nifti_vect44_norm (vec4 v) { //normalize vector length + vec4 vO = v; + float vLen = sqrt( (v.v[0]*v.v[0]) + + (v.v[1]*v.v[1]) + + (v.v[2]*v.v[2]) + + (v.v[3]*v.v[3])); + if (vLen <= FLT_EPSILON) return vO; //avoid divide by zero + for (int i = 0; i < 4; i++) + vO.v[i] = v.v[i]/vLen; + return vO; +} + +vec3 nifti_vect33mat33_mul(vec3 v, mat33 m ) { //multiply vector * 3x3matrix + vec3 vO; + for (int i=0; i<3; i++) { //multiply Pcrs * m + vO.v[i] = 0; + for(int j=0; j<3; j++) + vO.v[i] += m.m[i][j]*v.v[j]; + } + return vO; +} + +vec4 nifti_vect44mat44_mul(vec4 v, mat44 m ) { //multiply vector * 4x4matrix + vec4 vO; + for (int i=0; i<4; i++) { //multiply Pcrs * m + vO.v[i] = 0; + for(int j=0; j<4; j++) + vO.v[i] += m.m[i][j]*v.v[j]; + } + return vO; +} + +mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]) { + //create NIfTI header based on values from DICOM header + //note orient has 6 values, indexed from 1, patient position and xyzMM have 3 values indexed from 1 + mat33 Q, diagVox; + Q.m[0][0] = orient[1]; Q.m[0][1] = orient[2] ; Q.m[0][2] = orient[3] ; // load Q + Q.m[1][0] = orient[4]; Q.m[1][1] = orient[5] ; Q.m[1][2] = orient[6]; + //printMessage("Orient %g %g %g %g %g %g\n",orient[1],orient[2],orient[3],orient[4],orient[5],orient[6] ); + /* normalize row 1 */ + double val = Q.m[0][0]*Q.m[0][0] + Q.m[0][1]*Q.m[0][1] + Q.m[0][2]*Q.m[0][2] ; + if( val > 0.0l ){ + val = 1.0l / sqrt(val) ; + Q.m[0][0] *= (float)val ; Q.m[0][1] *= (float)val ; Q.m[0][2] *= (float)val ; + } else { + Q.m[0][0] = 1.0l ; Q.m[0][1] = 0.0l ; Q.m[0][2] = 0.0l ; + } + /* normalize row 2 */ + val = Q.m[1][0]*Q.m[1][0] + Q.m[1][1]*Q.m[1][1] + Q.m[1][2]*Q.m[1][2] ; + if( val > 0.0l ){ + val = 1.0l / sqrt(val) ; + Q.m[1][0] *= (float)val ; Q.m[1][1] *= (float)val ; Q.m[1][2] *= (float)val ; + } else { + Q.m[1][0] = 0.0l ; Q.m[1][1] = 1.0l ; Q.m[1][2] = 0.0l ; + } + /* row 3 is the cross product of rows 1 and 2*/ + Q.m[2][0] = Q.m[0][1]*Q.m[1][2] - Q.m[0][2]*Q.m[1][1] ; /* cross */ + Q.m[2][1] = Q.m[0][2]*Q.m[1][0] - Q.m[0][0]*Q.m[1][2] ; /* product */ + Q.m[2][2] = Q.m[0][0]*Q.m[1][1] - Q.m[0][1]*Q.m[1][0] ; + Q = nifti_mat33_transpose(Q); + if (nifti_mat33_determ(Q) < 0.0) { + Q.m[0][2] = -Q.m[0][2]; + Q.m[1][2] = -Q.m[1][2]; + Q.m[2][2] = -Q.m[2][2]; + } + //next scale matrix + LOAD_MAT33(diagVox, xyzMM[1],0.0l,0.0l, 0.0l,xyzMM[2],0.0l, 0.0l,0.0l, xyzMM[3]); + Q = nifti_mat33_mul(Q,diagVox); + mat44 Q44; //4x4 matrix includes translations + LOAD_MAT44(Q44, Q.m[0][0],Q.m[0][1],Q.m[0][2],patientPosition[1], + Q.m[1][0],Q.m[1][1],Q.m[1][2],patientPosition[2], + Q.m[2][0],Q.m[2][1],Q.m[2][2],patientPosition[3]); + return Q44; +} + +#ifndef USING_R +#ifndef USING_MGH_NIFTI_IO +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 +#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 +#ifndef USING_MGH_NIFTI_IO +mat33 nifti_mat33_inverse( mat33 R ) /* inverse of 3x3 matrix */ +{ + double r11,r12,r13,r21,r22,r23,r31,r32,r33 , deti ; + 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 +#endif + +// Eigen decomposition for symmetric 3x3 matrices, port of public domain Java Matrix library JAMA. +// Connelly Barnes http://barnesc.blogspot.com/2007/02/eigenvectors-of-3x3-symmetric-matrix.html +// see also https://www.geometrictools.com/Documentation/RobustEigenSymmetric3x3.pdf +//begin: Connelly Barnes code +#include + +#ifdef MAX +#undef MAX +#endif + +#define MAX(a, b) ((a)>(b)?(a):(b)) + +#define n 3 + +static double hypot2(double x, double y) { + return sqrt(x*x+y*y); +} + +// Symmetric Householder reduction to tridiagonal form. + +static void tred2(double V[n][n], double d[n], double e[n]) { +// This is derived from the Algol procedures tred2 by +// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for +// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding +// Fortran subroutine in EISPACK. + for (int j = 0; j < n; j++) { + d[j] = V[n-1][j]; + } + // Householder reduction to tridiagonal form. + for (int i = n-1; i > 0; i--) { + // Scale to avoid under/overflow. + double scale = 0.0; + double h = 0.0; + for (int k = 0; k < i; k++) { + scale = scale + fabs(d[k]); + } + if (scale == 0.0) { + e[i] = d[i-1]; + for (int j = 0; j < i; j++) { + d[j] = V[i-1][j]; + V[i][j] = 0.0; + V[j][i] = 0.0; + } + } else { + // Generate Householder vector. + for (int k = 0; k < i; k++) { + d[k] /= scale; + h += d[k] * d[k]; + } + double f = d[i-1]; + double g = sqrt(h); + if (f > 0) { + g = -g; + } + e[i] = scale * g; + h = h - f * g; + d[i-1] = f - g; + for (int j = 0; j < i; j++) { + e[j] = 0.0; + } + // Apply similarity transformation to remaining columns. + for (int j = 0; j < i; j++) { + f = d[j]; + V[j][i] = f; + g = e[j] + V[j][j] * f; + for (int k = j+1; k <= i-1; k++) { + g += V[k][j] * d[k]; + e[k] += V[k][j] * f; + } + e[j] = g; + } + f = 0.0; + for (int j = 0; j < i; j++) { + e[j] /= h; + f += e[j] * d[j]; + } + double hh = f / (h + h); + for (int j = 0; j < i; j++) { + e[j] -= hh * d[j]; + } + for (int j = 0; j < i; j++) { + f = d[j]; + g = e[j]; + for (int k = j; k <= i-1; k++) { + V[k][j] -= (f * e[k] + g * d[k]); + } + d[j] = V[i-1][j]; + V[i][j] = 0.0; + } + } + d[i] = h; + } + // Accumulate transformations. + for (int i = 0; i < n-1; i++) { + V[n-1][i] = V[i][i]; + V[i][i] = 1.0; + double h = d[i+1]; + if (h != 0.0) { + for (int k = 0; k <= i; k++) { + d[k] = V[k][i+1] / h; + } + for (int j = 0; j <= i; j++) { + double g = 0.0; + for (int k = 0; k <= i; k++) { + g += V[k][i+1] * V[k][j]; + } + for (int k = 0; k <= i; k++) { + V[k][j] -= g * d[k]; + } + } + } + for (int k = 0; k <= i; k++) { + V[k][i+1] = 0.0; + } + } + for (int j = 0; j < n; j++) { + d[j] = V[n-1][j]; + V[n-1][j] = 0.0; + } + V[n-1][n-1] = 1.0; + e[0] = 0.0; +} + +// Symmetric tridiagonal QL algorithm. + +static void tql2(double V[n][n], double d[n], double e[n]) { +// This is derived from the Algol procedures tql2, by +// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for +// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding +// Fortran subroutine in EISPACK. + for (int i = 1; i < n; i++) { + e[i-1] = e[i]; + } + e[n-1] = 0.0; + double f = 0.0; + double tst1 = 0.0; + double eps = pow(2.0,-52.0); + for (int l = 0; l < n; l++) { + // Find small subdiagonal element + tst1 = MAX(tst1,fabs(d[l]) + fabs(e[l])); + int m = l; + while (m < n) { + if (fabs(e[m]) <= eps*tst1) { + break; + } + m++; + } + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) { + int iter = 0; + do { + iter = iter + 1; // (Could check iteration count here.) + // Compute implicit shift + double g = d[l]; + double p = (d[l+1] - g) / (2.0 * e[l]); + double r = hypot2(p,1.0); + if (p < 0) { + r = -r; + } + d[l] = e[l] / (p + r); + d[l+1] = e[l] * (p + r); + double dl1 = d[l+1]; + double h = g - d[l]; + for (int i = l+2; i < n; i++) { + d[i] -= h; + } + f = f + h; + // Implicit QL transformation. + p = d[m]; + double c = 1.0; + double c2 = c; + double c3 = c; + double el1 = e[l+1]; + double s = 0.0; + double s2 = 0.0; + for (int i = m-1; i >= l; i--) { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = hypot2(p,e[i]); + e[i+1] = s * r; + s = e[i] / r; + c = p / r; + p = c * d[i] - s * g; + d[i+1] = h + s * (c * g + s * d[i]); + // Accumulate transformation. + for (int k = 0; k < n; k++) { + h = V[k][i+1]; + V[k][i+1] = s * V[k][i] + c * h; + V[k][i] = c * V[k][i] - s * h; + } + } + p = -s * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + // Check for convergence. + } while (fabs(e[l]) > eps*tst1); + } + d[l] = d[l] + f; + e[l] = 0.0; + } + // Sort eigenvalues and corresponding vectors. + for (int i = 0; i < n-1; i++) { + int k = i; + double p = d[i]; + for (int j = i+1; j < n; j++) { + if (d[j] < p) { + k = j; + p = d[j]; + } + } + if (k != i) { + d[k] = d[i]; + d[i] = p; + for (int j = 0; j < n; j++) { + p = V[j][i]; + V[j][i] = V[j][k]; + V[j][k] = p; + } + } + } +} + +void eigen_decomposition(double A[n][n], double V[n][n], double d[n]) { + double e[n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + V[i][j] = A[i][j]; + } + } + tred2(V, d, e); + tql2(V, d, e); +} +//end: Connelly Barnes code + +/*void printMat(char ch, double A[3][3]) { + printf("%c=[%g %g %g; %g %g %g; %g %g %g];\n", ch, + A[0][0],A[0][1],A[0][2], + A[1][0],A[1][1],A[1][2], + A[2][0],A[2][1],A[2][2]); +}*/ + +vec3 nifti_mat33_eig3(double bxx, double bxy, double bxz, double byy, double byz, double bzz) { + double A[3][3]; + A[0][0] = bxx; + A[0][1] = bxy; + A[0][2] = bxz; + A[1][0] = bxy; + A[1][1] = byy; + A[1][2] = byz; + A[2][0] = bxz; + A[2][1] = byz; + A[2][2] = bzz; + double V[3][3]; + double d[3]; + eigen_decomposition(A,V,d); + //printMat('A',A); + //printf("[V,D] = eig(A) %%where A*V = V*D\n"); + //printMat('V',V); + //printf("D = [%g 0 0; 0 %g 0; 0 0 %g]\n", d[0], d[1], d[2]); + vec3 v3; + v3.v[0] = V[0][2]; + v3.v[1] = V[1][2]; + v3.v[2] = V[2][2]; + if (v3.v[0] < 0.0) { + //B-matrix underspecified to describe B-vector + // Describes direction but not polarity of B-vector + // e.g. We get an eigenvector, but eigenvalue can be + or - + // https://github.com/rordenlab/dcm2niix/issues/265 + // Like B2q We set the vector to have a positive x component by convention. + // https://raw.githubusercontent.com/matthew-brett/nibabel/master/nibabel/nicom/dwiparams.py + // This ensures eddy will see this as a half shell and not attempt to interpret arbitrary signs + v3.v[0] = -v3.v[0]; + v3.v[1] = -v3.v[1]; + v3.v[2] = -v3.v[2]; + } + //printf("bvec = [%g 0 0; 0 %g 0; 0 0 %g]\n", v3.v[0], v3.v[1], v3.v[2]); + return v3; +} + + + + + + + diff --git a/packages/dcm2niix/nifti1_io_core.h b/packages/dcm2niix/nifti1_io_core.h new file mode 100644 index 00000000000..d8cfb0338ca --- /dev/null +++ b/packages/dcm2niix/nifti1_io_core.h @@ -0,0 +1,110 @@ +//this minimal set of nifti routines is based on nifti1_io without the dependencies (zlib) and a few extra functions +// http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.h +// http://niftilib.sourceforge.net +#ifndef _NIFTI_IO_CORE_HEADER_ +#define _NIFTI_IO_CORE_HEADER_ + +#ifdef USING_R +#define STRICT_R_HEADERS +#include +#include "RNifti.h" +#else +#include "nifti1.h" +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include //requires VS 2015 or later + +#include + +#ifndef USING_R +typedef struct { /** 3x3 matrix struct **/ + float m[3][3] ; +} mat33 ; +typedef struct { /** 4x4 matrix struct **/ + float m[4][4] ; +} mat44 ; +#endif + + +mat44 nifti_mat44_inverse( mat44 R ) ; + +mat33 nifti_mat33_inverse( mat33 R ) ; +float nifti_mat33_determ ( mat33 R ) ; +mat33 nifti_mat33_mul ( mat33 A , mat33 B ) ; + +void nifti_swap_2bytes ( size_t n , void *ar ) ; +void nifti_swap_4bytes ( size_t n , void *ar ) ; +void nifti_swap_8bytes ( size_t n , void *ar ) ; + +void swap_nifti_header ( struct nifti_1_header *h , int is_nifti ) ; + + +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 ); + + + +/* Orientation codes that might be returned from nifti_mat44_to_orientation().*/ + +/*** start additions for dcm2niix ***/ +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); + +int isSameFloat (float a, float b) ; +int isSameDouble (double a, double b) ; +bool littleEndianPlatform (); + + +vec3 nifti_mat33_eig3(double bxx, double bxy, double bxz, double byy, double byz, double bzz); +mat33 nifti_mat33_transpose( mat33 A ); +mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]); +mat44 nifti_mat44_mul( mat44 A , mat44 B ); +vec3 crossProduct(vec3 u, vec3 v); +vec3 nifti_vect33_norm (vec3 v); +vec4 nifti_vect44_norm (vec4 v); +vec3 nifti_vect33mat33_mul(vec3 v, mat33 m ); +ivec3 setiVec3(int x, int y, int z); +vec3 setVec3(float x, float y, float z); +vec4 setVec4(float x, float y, float z); + +vec4 nifti_vect44mat44_mul(vec4 v, mat44 m ); +/*** end additions for dcm2niix ***/ + +#ifdef __cplusplus +} +#endif + +#endif /* _NIFTI_IO_CORE_HEADER_ */ diff --git a/packages/dcm2niix/nii_dicom.cpp b/packages/dcm2niix/nii_dicom.cpp new file mode 100644 index 00000000000..83e59397067 --- /dev/null +++ b/packages/dcm2niix/nii_dicom.cpp @@ -0,0 +1,7529 @@ +//#define MY_DEBUG +#if defined(_WIN64) || defined(_WIN32) +#include //write to registry +#endif +#ifdef _MSC_VER +#include +#define getcwd _getcwd +#define chdir _chrdir +#include "io.h" +#include +//#define snprintf _snprintf +//#define vsnprintf _vsnprintf +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#ifdef _WIN32 +#pragma comment(lib, "advapi32") +#endif +#else +#include +#endif +//#include //clock() +#ifndef USING_R +#include "nifti1.h" +#endif +#include "jpg_0XC3.h" +#include "nifti1_io_core.h" +#include "nii_dicom.h" +#include "print.h" +#include //toupper +#include +#include +#include +#include +#include +#include +#include +#include // discriminate files from folders +#include + +#ifdef USING_R +#undef isnan +#define isnan ISNAN +#endif + +#ifndef myDisableClassicJPEG +#ifdef myTurboJPEG +#include +#else +#include "ujpeg.h" +#endif +#endif +#ifdef myEnableJasper +#include +#endif +#ifndef myDisableOpenJPEG +#include "openjpeg.h" + +#ifdef myEnableJasper +ERROR : YOU CAN NOT COMPILE WITH myEnableJasper AND NOT myDisableOpenJPEG OPTIONS SET SIMULTANEOUSLY +#endif + +unsigned char * imagetoimg(opj_image_t *image) { + int numcmpts = image->numcomps; + int sgnd = image->comps[0].sgnd; + int width = image->comps[0].w; + int height = image->comps[0].h; + int bpp = (image->comps[0].prec + 7) >> 3; //e.g. 12 bits requires 2 bytes + int imgbytes = bpp * width * height * numcmpts; + bool isOK = true; + if (numcmpts > 1) { + for (int comp = 1; comp < numcmpts; comp++) { //check RGB data + if (image->comps[0].w != image->comps[comp].w) + isOK = false; + if (image->comps[0].h != image->comps[comp].h) + isOK = false; + if (image->comps[0].dx != image->comps[comp].dx) + isOK = false; + if (image->comps[0].dy != image->comps[comp].dy) + isOK = false; + if (image->comps[0].prec != image->comps[comp].prec) + isOK = false; + if (image->comps[0].sgnd != image->comps[comp].sgnd) + isOK = false; + } + if (numcmpts != 3) + isOK = false; //we only handle Gray and RedGreenBlue, not GrayAlpha or RedGreenBlueAlpha + if (image->comps[0].prec != 8) + isOK = false; //only 8-bit for RGB data + } + if ((image->comps[0].prec < 1) || (image->comps[0].prec > 16)) + isOK = false; //currently we only handle 1 and 2 byte data + if (!isOK) { + printMessage("jpeg decode failure w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); + return NULL; + } +#ifdef MY_DEBUG + printMessage("w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); +#endif + //extract the data + if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { + printError("Catastrophic decompression error\n"); + return NULL; + } + unsigned char *img = (unsigned char *)malloc(imgbytes); + uint16_t *img16ui = (uint16_t *)img; //unsigned 16-bit + int16_t *img16i = (int16_t *)img; //signed 16-bit + if (sgnd) + bpp = -bpp; + if (bpp == -1) { + free(img); + printError("Signed 8-bit DICOM?\n"); + return NULL; + } + //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB + int pix = 0; //output pixel + for (int cmptno = 0; cmptno < numcmpts; ++cmptno) { + int cpix = 0; //component pixel + int *v = image->comps[cmptno].data; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + switch (bpp) { + case 1: + img[pix] = (unsigned char)v[cpix]; + break; + case 2: + img16ui[pix] = (uint16_t)v[cpix]; + break; + case -2: + img16i[pix] = (int16_t)v[cpix]; + break; + } + pix++; + cpix++; + } //for x + } //for y + } //for each component + return img; +} // imagetoimg() + +typedef struct bufinfo { + unsigned char *buf; + unsigned char *cur; + size_t len; +} BufInfo; + +static void my_stream_free(void *p_user_data) { //do nothing + //BufInfo d = (BufInfo) p_user_data; + //free(d.buf); +} // my_stream_free() + +static OPJ_UINT32 opj_read_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) { + OPJ_UINT32 l_nb_read; + if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) { + l_nb_read = p_nb_bytes; + } else { + l_nb_read = (OPJ_UINT32)(p_file->buf + p_file->len - p_file->cur); + } + memcpy(p_buffer, p_file->cur, l_nb_read); + p_file->cur += l_nb_read; + + return l_nb_read ? l_nb_read : ((OPJ_UINT32)-1); +} //opj_read_from_buffer() + +static OPJ_UINT32 opj_write_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) { + memcpy(p_file->cur, p_buffer, p_nb_bytes); + p_file->cur += p_nb_bytes; + p_file->len += p_nb_bytes; + return p_nb_bytes; +} // opj_write_from_buffer() + +static OPJ_SIZE_T opj_skip_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) { + if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) { + p_file->cur += p_nb_bytes; + return p_nb_bytes; + } + p_file->cur = p_file->buf + p_file->len; + return (OPJ_SIZE_T)-1; +} //opj_skip_from_buffer() + +//fix for https://github.com/neurolabusc/dcm_qa/issues/5 +static OPJ_BOOL opj_seek_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) { +//printf("opj_seek_from_buffer %d + %d -> %d + %d\n", p_file->cur , p_nb_bytes, p_file->buf, p_file->len); + if (p_nb_bytes < p_file->len) { + p_file->cur = p_file->buf + p_nb_bytes; + return OPJ_TRUE; + } + p_file->cur = p_file->buf + p_file->len; + return OPJ_FALSE; +} //opj_seek_from_buffer() + +opj_stream_t *opj_stream_create_buffer_stream(BufInfo *p_file, OPJ_UINT32 p_size, OPJ_BOOL p_is_read_stream) { + opj_stream_t *l_stream; + if (!p_file) + return NULL; + l_stream = opj_stream_create(p_size, p_is_read_stream); + if (!l_stream) + return NULL; + opj_stream_set_user_data(l_stream, p_file, my_stream_free); + opj_stream_set_user_data_length(l_stream, p_file->len); + opj_stream_set_read_function(l_stream, (opj_stream_read_fn)opj_read_from_buffer); + opj_stream_set_write_function(l_stream, (opj_stream_write_fn)opj_write_from_buffer); + opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn)opj_skip_from_buffer); + opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn)opj_seek_from_buffer); + return l_stream; +} //opj_stream_create_buffer_stream() + +unsigned char *nii_loadImgCoreOpenJPEG(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { + //OpenJPEG library is not well documented and has changed between versions + //Since the JPEG is embedded in a DICOM we need to skip bytes at the start of the file + // In theory we might also want to strip data that exists AFTER the image, see gdcmJPEG2000Codec.c + unsigned char *ret = NULL; + opj_dparameters_t params; + opj_codec_t *codec; + opj_image_t *jpx; + opj_stream_t *stream; + FILE *reader = fopen(imgname, "rb"); + fseek(reader, 0, SEEK_END); + long size = ftell(reader) - dcm.imageStart; + if (size <= 8) + return NULL; + fseek(reader, dcm.imageStart, SEEK_SET); + unsigned char *data = (unsigned char *)malloc(size); + size_t sz = fread(data, 1, size, reader); + fclose(reader); + if (sz < size) + return NULL; + OPJ_CODEC_FORMAT format = OPJ_CODEC_JP2; + //DICOM JPEG2k is SUPPOSED to start with codestream, but some vendors include a header + if (data[0] == 0xFF && data[1] == 0x4F && data[2] == 0xFF && data[3] == 0x51) + format = OPJ_CODEC_J2K; + opj_set_default_decoder_parameters(¶ms); + BufInfo dx; + dx.buf = data; + dx.cur = data; + dx.len = size; + stream = opj_stream_create_buffer_stream(&dx, (OPJ_UINT32)size, true); + if (stream == NULL) + return NULL; + codec = opj_create_decompress(format); + // setup the decoder decoding parameters using user parameters + if (!opj_setup_decoder(codec, ¶ms)) + goto cleanup2; + // Read the main header of the codestream and if necessary the JP2 boxes + if (!opj_read_header(stream, codec, &jpx)) { + printError("OpenJPEG failed to read the header %s (offset %d)\n", imgname, dcm.imageStart); +//comment these next lines to abort: include these to create zero-padded slice +#ifdef MY_ZEROFILLBROKENJPGS + //fix broken slices https://github.com/scitran-apps/dcm2niix/issues/4 + printError("Zero-filled slice created\n"); + int imgbytes = (hdr.bitpix / 8) * hdr.dim[1] * hdr.dim[2]; + ret = (unsigned char *)calloc(imgbytes, 1); +#endif + goto cleanup2; + } + // Get the decoded image + if (!(opj_decode(codec, stream, jpx) && opj_end_decompress(codec, stream))) { + printError("OpenJPEG j2k_to_image failed to decode %s\n", imgname); + goto cleanup1; + } + ret = imagetoimg(jpx); +cleanup1: + opj_image_destroy(jpx); +cleanup2: + free(dx.buf); + opj_stream_destroy(stream); + opj_destroy_codec(codec); + return ret; +} +#endif //myDisableOpenJPEG + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +float deFuzz(float v) { + if (fabs(v) < 0.00001) + return 0; + else + return v; +} + +#ifdef MY_DEBUG +void reportMat33(char *str, mat33 A) { + printMessage("%s = [%g %g %g ; %g %g %g; %g %g %g ]\n", str, + deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]), + deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]), + deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2])); +} + +void reportMat44(char *str, mat44 A) { +//example: reportMat44((char*)"out",*R); + printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n", str, + deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]), deFuzz(A.m[0][3]), + deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]), deFuzz(A.m[1][3]), + deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2]), deFuzz(A.m[2][3])); +} +#endif + +int verify_slice_dir(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, mat44 *R, int isVerbose) { +//returns slice direction: 1=sag,2=coronal,3=axial, -= flipped + if (h->dim[3] < 2) + return 0; //don't care direction for single slice + int iSL = 1; //find Z-slice direction: row with highest magnitude of 3rd column + if ((fabs(R->m[1][2]) >= fabs(R->m[0][2])) && (fabs(R->m[1][2]) >= fabs(R->m[2][2]))) + iSL = 2; // + if ((fabs(R->m[2][2]) >= fabs(R->m[0][2])) && (fabs(R->m[2][2]) >= fabs(R->m[1][2]))) + iSL = 3; //axial acquisition + float pos = NAN; + if (!isnan(d2.patientPosition[iSL])) { //patient position fields exist + pos = d2.patientPosition[iSL]; + if (isSameFloat(pos, d.patientPosition[iSL])) + pos = NAN; +#ifdef MY_DEBUG + if (!isnan(pos)) + printMessage("position determined using lastFile %f\n", pos); +#endif + } + if (isnan(pos) && (!isnan(d.patientPositionLast[iSL]))) { //patient position fields exist + pos = d.patientPositionLast[iSL]; + if (isSameFloat(pos, d.patientPosition[iSL])) + pos = NAN; +#ifdef MY_DEBUG + if (!isnan(pos)) + printMessage("position determined using last (4d) %f\n", pos); +#endif + } + if (isnan(pos) && (!isnan(d.stackOffcentre[iSL]))) + pos = d.stackOffcentre[iSL]; + if (isnan(pos) && (!isnan(d.lastScanLoc))) + pos = d.lastScanLoc; + vec4 x; + x.v[0] = 0.0; + x.v[1] = 0.0; + x.v[2] = (float)(h->dim[3] - 1.0); + x.v[3] = 1.0; + vec4 pos1v = nifti_vect44mat44_mul(x, *R); + float pos1 = pos1v.v[iSL - 1]; //-1 as C indexed from 0 + bool flip = false; + if (!isnan(pos)) // we have real SliceLocation for last slice or volume center + flip = (pos > R->m[iSL - 1][3]) != (pos1 > R->m[iSL - 1][3]); // same direction?, note C indices from 0 + else { // we do some guess work and warn user + vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]); + vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]); + //printMessage("rd %g %g %g\n",readV.v[0],readV.v[1],readV.v[2]); + //printMessage("ph %g %g %g\n",phaseV.v[0],phaseV.v[1],phaseV.v[2]); + vec3 sliceV = crossProduct(readV, phaseV); //order important: this is our hail mary + flip = ((sliceV.v[0] + sliceV.v[1] + sliceV.v[2]) < 0); + //printMessage("verify slice dir %g %g %g\n",sliceV.v[0],sliceV.v[1],sliceV.v[2]); + if (isVerbose) { //1st pass only + if (!d.isDerived) { //do not warn user if image is derived + printWarning("Unable to determine slice direction: please check whether slices are flipped\n"); + } else { + printWarning("Unable to determine slice direction: please check whether slices are flipped (derived image)\n"); + } + } + } + if (flip) { + for (int i = 0; i < 4; i++) + R->m[i][2] = -R->m[i][2]; + } + if (flip) + iSL = -iSL; +#ifdef MY_DEBUG + printMessage("verify slice dir %d %d %d\n", h->dim[1], h->dim[2], h->dim[3]); + //reportMat44((char*)"Rout",*R); + printMessage("flip = %d\n", flip); + printMessage("sliceDir = %d\n", iSL); + printMessage(" pos1 = %f\n", pos1); +#endif + return iSL; +} //verify_slice_dir() + +mat44 noNaN(mat44 Q44, bool isVerbose, bool *isBogus) //simplify any headers that have NaN values +{ + mat44 ret = Q44; + bool isNaN44 = false; + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + if (isnan(ret.m[i][j])) + isNaN44 = true; + if (isNaN44) { + *isBogus = true; + if (isVerbose) + printWarning("Bogus spatial matrix (perhaps non-spatial image): inspect spatial orientation\n"); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + if (i == j) + ret.m[i][j] = 1; + else + ret.m[i][j] = 0; + ret.m[1][1] = -1; + } //if isNaN detected + return ret; +} + +#define kSessionOK 0 +#define kSessionBadMatrix 1 + +void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose) { + bool isBogus = false; + mat44 Q44 = noNaN(Q44i, isVerbose, &isBogus); + if ((h->session_error == kSessionBadMatrix) || (isBogus)) { + h->session_error = kSessionBadMatrix; + h->sform_code = NIFTI_XFORM_UNKNOWN; + } else + h->sform_code = NIFTI_XFORM_SCANNER_ANAT; + h->srow_x[0] = Q44.m[0][0]; + h->srow_x[1] = Q44.m[0][1]; + h->srow_x[2] = Q44.m[0][2]; + h->srow_x[3] = Q44.m[0][3]; + h->srow_y[0] = Q44.m[1][0]; + h->srow_y[1] = Q44.m[1][1]; + h->srow_y[2] = Q44.m[1][2]; + h->srow_y[3] = Q44.m[1][3]; + h->srow_z[0] = Q44.m[2][0]; + h->srow_z[1] = Q44.m[2][1]; + h->srow_z[2] = Q44.m[2][2]; + h->srow_z[3] = Q44.m[2][3]; + float dumdx, dumdy, dumdz; + nifti_mat44_to_quatern(Q44, &h->quatern_b, &h->quatern_c, &h->quatern_d, &h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz, &h->pixdim[0]); + h->qform_code = h->sform_code; +} //setQSForm() + +#ifdef my_unused + +ivec3 maxCol(mat33 R) { +//return index of maximum column in 3x3 matrix, e.g. [1 0 0; 0 1 0; 0 0 1] -> 1,2,3 + ivec3 ixyz; + mat33 foo; + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + foo.m[i][j] = fabs(R.m[i][j]); + //ixyz.v[0] : row with largest value in column 1 + ixyz.v[0] = 1; + if ((foo.m[1][0] > foo.m[0][0]) && (foo.m[1][0] >= foo.m[2][0])) + ixyz.v[0] = 2; //2nd column largest column + else if ((foo.m[2][0] > foo.m[0][0]) && (foo.m[2][0] > foo.m[1][0])) + ixyz.v[0] = 3; //3rd column largest column + //ixyz.v[1] : row with largest value in column 2, but not the same row as ixyz.v[1] + if (ixyz.v[0] == 1) { + ixyz.v[1] = 2; + if (foo.m[2][1] > foo.m[1][1]) + ixyz.v[1] = 3; + } else if (ixyz.v[0] == 2) { + ixyz.v[1] = 1; + if (foo.m[2][1] > foo.m[0][1]) + ixyz.v[1] = 3; + } else { //ixyz.v[0] == 3 + ixyz.v[1] = 1; + if (foo.m[1][1] > foo.m[0][1]) + ixyz.v[1] = 2; + } + //ixyz.v[2] : 3rd row, constrained by previous rows + ixyz.v[2] = 6 - ixyz.v[1] - ixyz.v[0]; //sum of 1+2+3 + return ixyz; +} + +int sign(float x) { +//returns -1,0,1 depending on if X is less than, equal to or greater than zero + if (x < 0) + return -1; + else if (x > 0) + return 1; + return 0; +} + +// Subfunction: get dicom xform matrix and related info +// This is a direct port of Xiangrui Li's dicm2nii function +mat44 xform_mat(struct TDICOMdata d) { + vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]); + vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]); + vec3 sliceV = crossProduct(readV, phaseV); + mat33 R; + LOAD_MAT33(R, readV.v[0], readV.v[1], readV.v[2], + phaseV.v[0], phaseV.v[1], phaseV.v[2], + sliceV.v[0], sliceV.v[1], sliceV.v[2]); + R = nifti_mat33_transpose(R); + //reportMat33((char*)"R",R); + ivec3 ixyz = maxCol(R); + //printMessage("%d %d %d\n", ixyz.v[0], ixyz.v[1], ixyz.v[2]); + int iSL = ixyz.v[2]; // 1/2/3 for Sag/Cor/Tra slice + float cosSL = R.m[iSL - 1][2]; + //printMessage("cosSL\t%g\n", cosSL); + //vec3 pixdim = setVec3(d.xyzMM[1], d.xyzMM[2], d.xyzMM[3]); + //printMessage("%g %g %g\n", pixdim.v[0], pixdim.v[1], pixdim.v[2]); + mat33 pixdim; + LOAD_MAT33(pixdim, d.xyzMM[1], 0.0, 0.0, + 0.0, d.xyzMM[2], 0.0, + 0.0, 0.0, d.xyzMM[3]); + R = nifti_mat33_mul(R, pixdim); + //reportMat33((char*)"R",R); + mat44 R44; + LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.patientPosition[1], + R.m[1][0], R.m[1][1], R.m[1][2], d.patientPosition[2], + R.m[2][0], R.m[2][1], R.m[2][2], d.patientPosition[3]); + //reportMat44((char*)"R",R44); + //rest are former: R = verify_slice_dir(R, s, dim, iSL) + if ((d.xyzDim[3] < 2) && (d.CSA.mosaicSlices < 2)) + return R44; //don't care direction for single slice + vec3 dim = setVec3(d.xyzDim[1], d.xyzDim[2], d.xyzDim[3]); + if (d.CSA.mosaicSlices > 1) { //Siemens mosaic: use dim(1) since no transpose to img + float nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices)); + dim.v[0] = dim.v[0] / nRowCol; + dim.v[1] = dim.v[1] / nRowCol; + dim.v[2] = d.CSA.mosaicSlices; + vec4 dim4 = setVec4((nRowCol - 1) * dim.v[0] / 2.0f, (nRowCol - 1) * dim.v[1] / 2.0f, 0); + vec4 offset = nifti_vect44mat44_mul(dim4, R44); + //printMessage("%g %g %g\n", dim.v[0], dim.v[1], dim.v[2]); + //printMessage("%g %g %g\n", dim4.v[0], dim4.v[1], dim4.v[2]); + //printMessage("%g %g %g %g\n", offset.v[0], offset.v[1], offset.v[2], offset.v[3]); + //printMessage("nRowCol\t%g\n", nRowCol); + R44.m[0][3] = offset.v[0]; + R44.m[1][3] = offset.v[1]; + R44.m[2][3] = offset.v[2]; + //R44.m[3][3] = offset.v[3]; + if (sign(d.CSA.sliceNormV[iSL]) != sign(cosSL)) { + R44.m[0][2] = -R44.m[0][2]; + R44.m[1][2] = -R44.m[1][2]; + R44.m[2][2] = -R44.m[2][2]; + R44.m[3][2] = -R44.m[3][2]; + } + //reportMat44((char*)"iR44",R44); + return R44; + } else if (true) { + //SliceNormalVector TO DO + printMessage("Not completed"); +#ifndef USING_R + exit(2); +#endif + return R44; + } + printMessage("Unable to determine spatial transform\n"); +#ifndef USING_R + exit(1); +#else + return R44; +#endif +} + +mat44 set_nii_header(struct TDICOMdata d) { + mat44 R = xform_mat(d); + //R(1:2,:) = -R(1:2,:); % dicom LPS to nifti RAS, xform matrix before reorient + for (int i = 0; i < 2; i++) + for (int j = 0; j < 4; j++) + R.m[i][j] = -R.m[i][j]; +#ifdef MY_DEBUG + reportMat44((char *)"R44", R); +#endif +} +#endif + +// This code predates Xiangrui Li's set_nii_header function +mat44 set_nii_header_x(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int *sliceDir, int isVerbose) { + *sliceDir = 0; + mat44 Q44 = nifti_dicom2mat(d.orient, d.patientPosition, d.xyzMM); + //Q44 = doQuadruped(Q44); + if (d.isSegamiOasis == true) { + //Segami reconstructions appear to disregard DICOM spatial parameters: assume center of volume is isocenter and no table tilt + // Consider sample image with d.orient (0020,0037) = -1 0 0; 0 1 0: this suggests image RAI (L->R, P->A, S->I) but the vendors viewing software suggests LPS + //Perhaps we should ignore 0020,0037 and 0020,0032 as they are hidden in sequence 0054,0022, but in this case no positioning is provided + // http://www.cs.ucl.ac.uk/fileadmin/cmic/Documents/DavidAtkinson/DICOM.pdf + // https://www.slicer.org/wiki/Coordinate_systems + LOAD_MAT44(Q44, -h->pixdim[1], 0, 0, 0, 0, -h->pixdim[2], 0, 0, 0, 0, h->pixdim[3], 0); //X and Y dimensions flipped in NIfTI (RAS) vs DICOM (LPS) + vec4 originVx = setVec4((h->dim[1] + 1.0f) / 2.0f, (h->dim[2] + 1.0f) / 2.0f, (h->dim[3] + 1.0f) / 2.0f); + vec4 originMm = nifti_vect44mat44_mul(originVx, Q44); + for (int i = 0; i < 3; i++) + Q44.m[i][3] = -originMm.v[i]; //set origin to center voxel + if (isVerbose) { + //printMessage("origin (vx) %g %g %g\n",originVx.v[0],originVx.v[1],originVx.v[2]); + //printMessage("origin (mm) %g %g %g\n",originMm.v[0],originMm.v[1],originMm.v[2]); + printWarning("Segami coordinates defy DICOM convention, please check orientation\n"); + } + return Q44; + } + //next line only for Siemens mosaic: ignore for UIH grid + // https://github.com/xiangruili/dicm2nii/commit/47ad9e6d9bc8a999344cbd487d602d420fb1509f + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.mosaicSlices > 1)) { + double nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices)); + double lFactorX = (d.xyzDim[1] - (d.xyzDim[1] / nRowCol)) / 2.0; + double lFactorY = (d.xyzDim[2] - (d.xyzDim[2] / nRowCol)) / 2.0; + Q44.m[0][3] = (float)((Q44.m[0][0] * lFactorX) + (Q44.m[0][1] * lFactorY) + Q44.m[0][3]); + Q44.m[1][3] = (float)((Q44.m[1][0] * lFactorX) + (Q44.m[1][1] * lFactorY) + Q44.m[1][3]); + Q44.m[2][3] = (float)((Q44.m[2][0] * lFactorX) + (Q44.m[2][1] * lFactorY) + Q44.m[2][3]); + for (int c = 0; c < 2; c++) + for (int r = 0; r < 4; r++) + Q44.m[c][r] = -Q44.m[c][r]; + mat33 Q; + LOAD_MAT33(Q, d.orient[1], d.orient[4], d.CSA.sliceNormV[1], + d.orient[2], d.orient[5], d.CSA.sliceNormV[2], + d.orient[3], d.orient[6], d.CSA.sliceNormV[3]); + if (nifti_mat33_determ(Q) < 0) { //Siemens sagittal are R>>L, whereas NIfTI is L>>R, we retain Siemens order on disk so ascending is still ascending, but we need to have the spatial transform reflect this. + mat44 det; + *sliceDir = kSliceOrientMosaicNegativeDeterminant; //we need to handle DTI vectors accordingly + LOAD_MAT44(det, 1.0l, 0.0l, 0.0l, 0.0l, 0.0l, 1.0l, 0.0l, 0.0l, 0.0l, 0.0l, -1.0l, 0.0l); + //patient_to_tal.m[2][3] = 1-d.CSA.MosaicSlices; + Q44 = nifti_mat44_mul(Q44, det); + } + } else { //not a mosaic + *sliceDir = verify_slice_dir(d, d2, h, &Q44, isVerbose); + for (int c = 0; c < 4; c++) // LPS to nifti RAS, xform matrix before reorient + for (int r = 0; r < 2; r++) //swap rows 1 & 2 + Q44.m[r][c] = -Q44.m[r][c]; + } +#ifdef MY_DEBUG + reportMat44((char *)"Q44", Q44); +#endif + return Q44; +} + +int headerDcm2NiiSForm(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //fill header s and q form + //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c + //returns sliceDir: 0=unknown,1=sag,2=coro,3=axial,-=reversed slices + int sliceDir = 0; + if (h->dim[3] < 2) { + mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); + setQSForm(h, Q44, isVerbose); + return sliceDir; //don't care direction for single slice + } + h->sform_code = NIFTI_XFORM_UNKNOWN; + h->qform_code = NIFTI_XFORM_UNKNOWN; + bool isOK = false; + for (int i = 1; i <= 6; i++) + if (d.orient[i] != 0.0) + isOK = true; + if (!isOK) { + //we will have to guess, assume axial acquisition saved in standard Siemens style? + d.orient[1] = 1.0f; + d.orient[2] = 0.0f; + d.orient[3] = 0.0f; + d.orient[1] = 0.0f; + d.orient[2] = 1.0f; + d.orient[3] = 0.0f; + if ((d.isDerived) || ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3) && (d.manufacturer == kMANUFACTURER_SIEMENS))) { + printMessage("Unable to determine spatial orientation: 0020,0037 missing (probably not a problem: derived image)\n"); + } else { + printMessage("Unable to determine spatial orientation: 0020,0037 missing (Type 1 attribute: not a valid DICOM) Series %ld\n", d.seriesNum); + } + } + mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); + setQSForm(h, Q44, isVerbose); + return sliceDir; +} //headerDcm2NiiSForm() + +int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //final pass after de-mosaic + char txt[1024] = {""}; + if (h->slice_code == NIFTI_SLICE_UNKNOWN) + h->slice_code = d.CSA.sliceOrder; + if (h->slice_code == NIFTI_SLICE_UNKNOWN) + h->slice_code = d2.CSA.sliceOrder; //sometimes the first slice order is screwed up https://github.com/eauerbach/CMRR-MB/issues/29 + if (d.modality == kMODALITY_MR) + sprintf(txt, "TE=%.2g;Time=%.3f", d.TE, d.acquisitionTime); + else + sprintf(txt, "Time=%.3f", d.acquisitionTime); + if (d.CSA.phaseEncodingDirectionPositive >= 0) { + char dtxt[1024] = {""}; + sprintf(dtxt, ";phase=%d", d.CSA.phaseEncodingDirectionPositive); + strcat(txt, dtxt); + } + //from dicm2nii 20151117 InPlanePhaseEncodingDirection + if (d.phaseEncodingRC == 'R') + h->dim_info = (3 << 4) + (1 << 2) + 2; + if (d.phaseEncodingRC == 'C') + h->dim_info = (3 << 4) + (2 << 2) + 1; + if (d.CSA.multiBandFactor > 1) { + char dtxt[1024] = {""}; + sprintf(dtxt, ";mb=%d", d.CSA.multiBandFactor); + strcat(txt, dtxt); + } + // GCC 8 warns about truncation using snprintf + // snprintf(h->descrip,80, "%s",txt); + memcpy(h->descrip, txt, 79); + h->descrip[79] = '\0'; + if (strlen(d.imageComments) > 0) + snprintf(h->aux_file, 24, "%.23s", d.imageComments); + return headerDcm2NiiSForm(d, d2, h, isVerbose); +} //headerDcm2Nii2() + +int dcmStrLen(int len, int kMaxLen) { + if (len < kMaxLen) + return len + 1; + else + return kMaxLen; +} //dcmStrLen() + +struct TDICOMdata clear_dicom_data() { + struct TDICOMdata d; + //d.dti4D = NULL; + d.locationsInAcquisition = 0; + d.locationsInAcquisitionConflict = 0; //for GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081 + d.modality = kMODALITY_UNKNOWN; + d.effectiveEchoSpacingGE = 0; + for (int i = 0; i < 4; i++) { + d.CSA.dtiV[i] = 0; + d.patientPosition[i] = NAN; + //d.patientPosition2nd[i] = NAN; //used to distinguish XYZT vs XYTZ for Philips 4D + d.patientPositionLast[i] = NAN; //used to compute slice direction for Philips 4D + d.stackOffcentre[i] = NAN; + d.angulation[i] = 0.0f; + d.xyzMM[i] = 1; + } + for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) + d.dimensionIndexValues[i] = 0; + //d.CSA.sliceTiming[0] = -1.0f; //impossible value denotes not known + for (int z = 0; z < kMaxEPI3D; z++) + d.CSA.sliceTiming[z] = -1.0; + d.CSA.numDti = 0; + for (int i = 0; i < 5; i++) + d.xyzDim[i] = 1; + for (int i = 0; i < 7; i++) + d.orient[i] = 0.0f; + strcpy(d.patientName, ""); + strcpy(d.patientID, ""); + strcpy(d.accessionNumber, ""); + strcpy(d.imageType, ""); + strcpy(d.imageComments, ""); + strcpy(d.imageBaseName, ""); + strcpy(d.phaseEncodingDirectionDisplayedUIH, ""); + strcpy(d.studyDate, ""); + strcpy(d.studyTime, ""); + strcpy(d.protocolName, ""); + strcpy(d.seriesDescription, ""); + strcpy(d.sequenceName, ""); + strcpy(d.scanningSequence, ""); + strcpy(d.sequenceVariant, ""); + strcpy(d.manufacturersModelName, ""); + strcpy(d.institutionalDepartmentName, ""); + strcpy(d.procedureStepDescription, ""); + strcpy(d.institutionName, ""); + strcpy(d.referringPhysicianName, ""); + strcpy(d.institutionAddress, ""); + strcpy(d.deviceSerialNumber, ""); + strcpy(d.softwareVersions, ""); + strcpy(d.stationName, ""); + strcpy(d.scanOptions, ""); + //strcpy(d.mrAcquisitionType, ""); + strcpy(d.seriesInstanceUID, ""); + strcpy(d.instanceUID, ""); + strcpy(d.studyID, ""); + strcpy(d.studyInstanceUID, ""); + strcpy(d.bodyPartExamined, ""); + strcpy(d.coilName, ""); + strcpy(d.coilElements, ""); + strcpy(d.radiopharmaceutical, ""); + strcpy(d.convolutionKernel, ""); + strcpy(d.parallelAcquisitionTechnique, ""); + strcpy(d.imageOrientationText, ""); + strcpy(d.unitsPT, ""); + strcpy(d.decayCorrection, ""); + strcpy(d.attenuationCorrectionMethod, ""); + strcpy(d.reconstructionMethod, ""); + d.phaseEncodingLines = 0; + //~ d.patientPositionSequentialRepeats = 0; + //~ d.patientPositionRepeats = 0; + d.isHasPhase = false; + d.isHasReal = false; + d.isHasImaginary = false; + d.isHasMagnitude = false; + //d.maxGradDynVol = -1; //PAR/REC only + d.sliceOrient = kSliceOrientUnknown; + d.dateTime = (double)19770703150928.0; + d.acquisitionTime = 0.0f; + d.acquisitionDate = 0.0f; + d.manufacturer = kMANUFACTURER_UNKNOWN; + d.isPlanarRGB = false; + d.lastScanLoc = NAN; + d.TR = 0.0; + d.TE = 0.0; +#ifdef MGH_FREESURFER + d.TI = -1.0; // default when there is no TI in dicom file +#else + d.TI = 0.0; +#endif + d.flipAngle = 0.0; + d.bandwidthPerPixelPhaseEncode = 0.0; + d.acquisitionDuration = 0.0; + d.imagingFrequency = 0.0; + d.numberOfAverages = 0.0; + d.fieldStrength = 0.0; + d.SAR = 0.0; + d.pixelBandwidth = 0.0; + d.zSpacing = 0.0; + d.zThick = 0.0; + //~ d.numberOfDynamicScans = 0; + d.echoNum = 1; + d.echoTrainLength = 0; + d.waterFatShift = 0.0; + d.groupDelay = 0.0; + d.decayFactor = 0.0; + d.percentSampling = 0.0; + d.phaseFieldofView = 0.0; + d.dwellTime = 0; + d.protocolBlockStartGE = 0; + d.protocolBlockLengthGE = 0; + d.phaseEncodingSteps = 0; + d.coilCrc = 0; + d.seriesUidCrc = 0; + d.instanceUidCrc = 0; + d.accelFactPE = 0.0; + d.accelFactOOP = 0.0; + //d.patientPositionNumPhilips = 0; + d.imageBytes = 0; + d.intenScale = 1; + d.intenScalePhilips = 0; + d.intenIntercept = 0; + d.gantryTilt = 0.0; + d.exposureTimeMs = 0.0; + d.xRayTubeCurrent = 0.0; + d.radionuclidePositronFraction = 0.0; + d.radionuclideHalfLife = 0.0; + d.doseCalibrationFactor = 0.0; + d.ecat_isotope_halflife = 0.0; + d.frameDuration = -1.0; + d.ecat_dosage = 0.0; + d.radionuclideTotalDose = 0.0; + d.seriesNum = 1; + d.acquNum = 0; + d.imageNum = 1; + d.imageStart = 0; + d.is3DAcq = false; //e.g. MP-RAGE, SPACE, TFE + d.is2DAcq = false; // + d.isDerived = false; //0008,0008 = DERIVED,CSAPARALLEL,POSDISP + d.isSegamiOasis = false; //these images do not store spatial coordinates + d.isBVecWorldCoordinates = false; //bvecs can be in image space (GE) or world coordinates (Siemens) + d.isGrayscaleSoftcopyPresentationState = false; + d.isRawDataStorage = false; + d.isPartialFourier = false; + d.isIR = false; + d.isEPI = false; + d.isDiffusion = false; + d.isVectorFromBMatrix = false; + d.isStackableSeries = false; //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252 + d.isXA10A = false; //https://github.com/rordenlab/dcm2niix/issues/236 + d.triggerDelayTime = 0.0; + d.RWVScale = 0.0; + d.RWVIntercept = 0.0; + d.isScaleOrTEVaries = false; + d.isScaleVariesEnh = false; //issue363 + d.bitsAllocated = 16; //bits + d.bitsStored = 0; + d.samplesPerPixel = 1; + d.pixelPaddingValue = NAN; + d.isValid = false; + d.isXRay = false; + d.isMultiEcho = false; + d.isSigned = false; //default is unsigned! + d.isFloat = false; //default is for integers, not single or double precision + d.isResampled = false; //assume data not resliced to remove gantry tilt problems + d.isLocalizer = false; + d.isNonParallelSlices = false; + d.isCoilVaries = false; + d.compressionScheme = 0; //none + d.isExplicitVR = true; + d.isLittleEndian = true; //DICOM initially always little endian + d.converted2NII = 0; + d.numberOfDiffusionDirectionGE = -1; + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNKNOWN; + d.rtia_timerGE = -1.0; + d.rawDataRunNumber = -1; + d.maxEchoNumGE = -1; + d.epiVersionGE = -1; + d.internalepiVersionGE = -1; + d.durationLabelPulseGE = -1; + d.aslFlags = kASL_FLAG_NONE; + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; + d.mtState = -1; + d.numberOfExcitations = -1; + d.numberOfArms = -1; + d.numberOfPointsPerArm = -1; + d.phaseNumber = - 1; //Philips Multi-Phase ASL + d.spoiling = kSPOILING_UNKOWN; + d.interp3D = -1; + for (int i = 0; i < kMaxOverlay; i++) + d.overlayStart[i] = 0; + d.isHasOverlay = false; + d.isPrivateCreatorRemap = false; + d.isRealIsPhaseMapHz = false; + d.numberOfImagesInGridUIH = 0; + d.phaseEncodingRC = '?'; + d.patientSex = '?'; + d.patientWeight = 0.0; + strcpy(d.patientBirthDate, ""); + strcpy(d.patientAge, ""); + d.CSA.bandwidthPerPixelPhaseEncode = 0.0; + d.CSA.mosaicSlices = 0; + d.CSA.sliceNormV[1] = 0.0; + d.CSA.sliceNormV[2] = 0.0; + d.CSA.sliceNormV[3] = 1.0; //default Siemens Image Numbering is F>>H https://www.mccauslandcenter.sc.edu/crnl/tools/stc + d.CSA.sliceOrder = NIFTI_SLICE_UNKNOWN; + d.CSA.slice_start = 0; + d.CSA.slice_end = 0; + d.CSA.protocolSliceNumber1 = 0; + d.CSA.phaseEncodingDirectionPositive = -1; //unknown + d.CSA.isPhaseMap = false; + d.CSA.multiBandFactor = 1; + d.CSA.SeriesHeader_offset = 0; + d.CSA.SeriesHeader_length = 0; + return d; +} //clear_dicom_data() + +int isdigitdot(int c) { //returns true if digit or '.' + if (c == '.') + return 1; + return isdigit(c); +} + +void dcmStrDigitsDotOnlyKey(char key, char *lStr) { +//e.g. string "F:2.50" returns 2.50 if key==":" + size_t len = strlen(lStr); + if (len < 1) + return; + bool isKey = false; + for (int i = 0; i < (int)len; i++) { + if (!isdigitdot(lStr[i])) { + isKey = (lStr[i] == key); + lStr[i] = ' '; + } else if (!isKey) + lStr[i] = ' '; + } +} //dcmStrDigitsOnlyKey() + +void dcmStrDigitsOnlyKey(char key, char *lStr) { +//e.g. string "p2s3" returns 2 if key=="p" and 3 if key=="s" + size_t len = strlen(lStr); + if (len < 1) + return; + bool isKey = false; + for (int i = 0; i < (int)len; i++) { + if (!isdigit(lStr[i])) { + isKey = (lStr[i] == key); + lStr[i] = ' '; + } else if (!isKey) + lStr[i] = ' '; + } +} //dcmStrDigitsOnlyKey() + +void dcmStrDigitsOnly(char *lStr) { +//e.g. change "H11" to " 11" + size_t len = strlen(lStr); + if (len < 1) + return; + for (int i = 0; i < (int)len; i++) + if (!isdigit(lStr[i])) + lStr[i] = ' '; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ +uint32_t mz_crc32X(unsigned char *ptr, size_t buf_len) { + static const uint32_t s_crc32[16] = {0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,0xedb88320, 0xf00f9344, + 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c}; + uint32_t crcu32 = 0; + if (!ptr) + return crcu32; + crcu32 = ~crcu32; + while (buf_len--) { + uint8_t b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; +} + +void dcmStr(int lLength, unsigned char lBuffer[], char *lOut, bool isStrLarge = false) { + if (lLength < 1) + return; + char *cString = (char *)malloc(sizeof(char) * (lLength + 1)); + cString[lLength] = 0; + memcpy(cString, (char *)&lBuffer[0], lLength); +//memcpy(cString, test, lLength); +//printMessage("X%dX\n", (unsigned char)d.patientName[1]); +#ifdef ISO8859 + for (int i = 0; i < lLength; i++) + //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 + if (cString[i] < 1) { + unsigned char c = (unsigned char)cString[i]; + if ((c >= 192) && (c <= 198)) + cString[i] = 'A'; + if (c == 199) + cString[i] = 'C'; + if ((c >= 200) && (c <= 203)) + cString[i] = 'E'; + if ((c >= 204) && (c <= 207)) + cString[i] = 'I'; + if (c == 208) + cString[i] = 'D'; + if (c == 209) + cString[i] = 'N'; + if ((c >= 210) && (c <= 214)) + cString[i] = 'O'; + if (c == 215) + cString[i] = 'x'; + if (c == 216) + cString[i] = 'O'; + if ((c >= 217) && (c <= 220)) + cString[i] = 'O'; + if (c == 221) + cString[i] = 'Y'; + if ((c >= 224) && (c <= 230)) + cString[i] = 'a'; + if (c == 231) + cString[i] = 'c'; + if ((c >= 232) && (c <= 235)) + cString[i] = 'e'; + if ((c >= 236) && (c <= 239)) + cString[i] = 'i'; + if (c == 240) + cString[i] = 'o'; + if (c == 241) + cString[i] = 'n'; + if ((c >= 242) && (c <= 246)) + cString[i] = 'o'; + if (c == 248) + cString[i] = 'o'; + if ((c >= 249) && (c <= 252)) + cString[i] = 'u'; + if (c == 253) + cString[i] = 'y'; + if (c == 255) + cString[i] = 'y'; + } +#endif + //we no longer sanitize strings, see issue 425 + int len = lLength; + if (cString[len - 1] == ' ') + len--; + //while ((len > 0) && (cString[len]=='_')) len--; //remove trailing '_' + cString[len] = 0; //null-terminate, strlcpy does this anyway + int maxLen = kDICOMStr; + if (isStrLarge) + maxLen = kDICOMStrLarge; + len = dcmStrLen(len, maxLen); + if (len == maxLen) { //we need space for null-termination + if (cString[len - 2] == '_') + len = len - 2; + } + memcpy(lOut, cString, len - 1); + lOut[len - 1] = 0; + free(cString); +} //dcmStr() + +#ifdef MY_OLD +//this code works on Intel but not some older systems https://github.com/rordenlab/dcm2niix/issues/327 +float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 32-bit float +//http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian + bool swap = (littleEndian != littleEndianPlatform()); + float retVal = 0; + if (lByteLength < 4) + return retVal; + memcpy(&retVal, (char *)&lBuffer[0], 4); + if (!swap) + return retVal; + float swapVal; + char *inFloat = (char *)&retVal; + char *outFloat = (char *)&swapVal; + outFloat[0] = inFloat[3]; + outFloat[1] = inFloat[2]; + outFloat[2] = inFloat[1]; + outFloat[3] = inFloat[0]; + //printMessage("swapped val = %f\n",swapVal); + return swapVal; +} //dcmFloat() + +double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { //read binary 64-bit float +//http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian + bool swap = (littleEndian != littleEndianPlatform()); + double retVal = 0.0f; + if (lByteLength < 8) + return retVal; + memcpy(&retVal, (char *)&lBuffer[0], 8); + if (!swap) + return retVal; + char *floatToConvert = (char *)&lBuffer; + char *returnFloat = (char *)&retVal; + //swap the bytes into a temporary buffer + returnFloat[0] = floatToConvert[7]; + returnFloat[1] = floatToConvert[6]; + returnFloat[2] = floatToConvert[5]; + returnFloat[3] = floatToConvert[4]; + returnFloat[4] = floatToConvert[3]; + returnFloat[5] = floatToConvert[2]; + returnFloat[6] = floatToConvert[1]; + returnFloat[7] = floatToConvert[0]; + //printMessage("swapped val = %f\n",retVal); + return retVal; +} //dcmFloatDouble() +#else + +float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 32-bit float + //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian + if (lByteLength < 4) + return 0.0; + bool swap = (littleEndian != littleEndianPlatform()); + union { + uint32_t i; + float f; + uint8_t c[4]; + } i, o; + memcpy(&i.i, (char *)&lBuffer[0], 4); + //printf("%02x%02x%02x%02x\n",i.c[0], i.c[1], i.c[2], i.c[3]); + if (!swap) + return i.f; + o.c[0] = i.c[3]; + o.c[1] = i.c[2]; + o.c[2] = i.c[1]; + o.c[3] = i.c[0]; + //printf("swp %02x%02x%02x%02x\n",o.c[0], o.c[1], o.c[2], o.c[3]); + return o.f; +} //dcmFloat() + +double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { //read binary 64-bit float + //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian + if (lByteLength < 8) + return 0.0; + bool swap = (littleEndian != littleEndianPlatform()); + union { + uint32_t i; + double d; + uint8_t c[8]; + } i, o; + memcpy(&i.i, (char *)&lBuffer[0], 8); + if (!swap) + return i.d; + o.c[0] = i.c[7]; + o.c[1] = i.c[6]; + o.c[2] = i.c[5]; + o.c[3] = i.c[4]; + o.c[4] = i.c[3]; + o.c[5] = i.c[2]; + o.c[6] = i.c[1]; + o.c[7] = i.c[0]; + return o.d; +} //dcmFloatDouble() +#endif + +int dcmInt(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 16 or 32 bit integer + if (littleEndian) { + if (lByteLength <= 3) + return lBuffer[0] | (lBuffer[1] << 8); //shortint vs word? + return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24); //shortint vs word? + } + if (lByteLength <= 3) + return lBuffer[1] | (lBuffer[0] << 8); //shortint vs word? + return lBuffer[3] + (lBuffer[2] << 8) + (lBuffer[1] << 16) + (lBuffer[0] << 24); //shortint vs word? +} //dcmInt() + +uint32_t dcmAttributeTag(unsigned char lBuffer[], bool littleEndian) { + // read Attribute Tag (AT) value + // return in Group + (Element << 16) format + if (littleEndian) + return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24); + return lBuffer[1] + (lBuffer[0] << 8) + (lBuffer[3] << 16) + (lBuffer[2] << 24); +} //dcmInt() + +int dcmStrInt(const int lByteLength, const unsigned char lBuffer[]) { //read int stored as a string + char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); + cString[lByteLength] = 0; + memcpy(cString, (const unsigned char *)(&lBuffer[0]), lByteLength); + int ret = atoi(cString); + free(cString); + return ret; +} //dcmStrInt() + +int dcmStrManufacturer(const int lByteLength, unsigned char lBuffer[]) { //read float stored as a string + if (lByteLength < 2) + return kMANUFACTURER_UNKNOWN; + //#ifdef _MSC_VER + char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); + //#else + // char cString[lByteLength + 1]; + //#endif + int ret = kMANUFACTURER_UNKNOWN; + cString[lByteLength] = 0; + memcpy(cString, (char *)&lBuffer[0], lByteLength); + if ((toupper(cString[0]) == 'S') && (toupper(cString[1]) == 'I')) + ret = kMANUFACTURER_SIEMENS; + if ((toupper(cString[0]) == 'G') && (toupper(cString[1]) == 'E')) + ret = kMANUFACTURER_GE; + if ((toupper(cString[0]) == 'H') && (toupper(cString[1]) == 'I')) + ret = kMANUFACTURER_HITACHI; + if ((toupper(cString[0]) == 'M') && (toupper(cString[1]) == 'E')) + ret = kMANUFACTURER_MEDISO; + if ((toupper(cString[0]) == 'P') && (toupper(cString[1]) == 'H')) + ret = kMANUFACTURER_PHILIPS; + if ((toupper(cString[0]) == 'T') && (toupper(cString[1]) == 'O')) + ret = kMANUFACTURER_TOSHIBA; + //CANON_MEC + if ((toupper(cString[0]) == 'C') && (toupper(cString[1]) == 'A')) + ret = kMANUFACTURER_CANON; + if ((toupper(cString[0]) == 'U') && (toupper(cString[1]) == 'I')) + ret = kMANUFACTURER_UIH; + if ((toupper(cString[0]) == 'B') && (toupper(cString[1]) == 'R')) + ret = kMANUFACTURER_BRUKER; + if (ret == kMANUFACTURER_UNKNOWN) + printWarning("Unknown manufacturer %s\n", cString); + //#ifdef _MSC_VER + free(cString); + //#endif + return ret; +} //dcmStrManufacturer + +float csaMultiFloat(unsigned char buff[], int nItems, float Floats[], int *ItemsOK) { + //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] + //if lnItems == 1, returns first item, if lnItems > 1 returns index of final successful conversion + TCSAitem itemCSA; + *ItemsOK = 0; + if (nItems < 1) + return 0.0f; + Floats[1] = 0; + int lPos = 0; + for (int lI = 1; lI <= nItems; lI++) { + memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); + lPos += sizeof(itemCSA); + // Storage order is always little-endian, so byte-swap required values if necessary + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &itemCSA.xx2_Len); + if (itemCSA.xx2_Len > 0) { + char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len)); + memcpy(cString, &buff[lPos], itemCSA.xx2_Len); //TPX memcpy(&cString, &buff[lPos], sizeof(cString)); + lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; + //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString); + Floats[lI] = (float)atof(cString); + *ItemsOK = lI; //some sequences have store empty items + free(cString); + } + } //for each item + return Floats[1]; +} //csaMultiFloat() + +bool csaIsPhaseMap(unsigned char buff[], int nItems) { + //returns true if the tag "ImageHistory" has an item named "CC:ComplexAdd" + TCSAitem itemCSA; + if (nItems < 1) + return false; + int lPos = 0; + for (int lI = 1; lI <= nItems; lI++) { + memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); + lPos += sizeof(itemCSA); + // Storage order is always little-endian, so byte-swap required values if necessary + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &itemCSA.xx2_Len); + if (itemCSA.xx2_Len > 0) { + char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len + 1)); + memcpy(cString, &buff[lPos], sizeof(itemCSA.xx2_Len)); //TPX memcpy(&cString, &buff[lPos], sizeof(cString)); + lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; + //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString); + if (strcmp(cString, "CC:ComplexAdd") == 0) + return true; + free(cString); + } + } //for each item + return false; +} //csaIsPhaseMap() + +void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3DAcq) { + if ((is3DAcq) || (itemsOK < 1)) //we expect 3D sequences to be simultaneous + return; + if (itemsOK > kMaxEPI3D) { + printError("Please increase kMaxEPI3D and recompile\n"); + return; + } + float maxTimeValue, minTimeValue, timeValue1; + minTimeValue = CSA->sliceTiming[0]; + for (int z = 0; z < itemsOK; z++) + if (CSA->sliceTiming[z] < minTimeValue) + minTimeValue = CSA->sliceTiming[z]; + //CSA can report negative slice times + // https://neurostars.org/t/slice-timing-illegal-values-in-fmriprep/1516/8 + // Nov 1, 2018 wrote: + // If you have an interleaved dataset we can more definitively validate this formula (aka sliceTime(i) - min(sliceTimes())). + if (minTimeValue < 0) { + //printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); //if uncommented, overwhelming number of warnings (one per DICOM input), better once per series + CSA->sliceTiming[kMaxEPI3D - 1] = -2.0; //issue 271: flag for unified warning + for (int z = 0; z < itemsOK; z++) + CSA->sliceTiming[z] = CSA->sliceTiming[z] - minTimeValue; + } + CSA->multiBandFactor = 1; + timeValue1 = CSA->sliceTiming[0]; + int nTimeZero = 0; + if (CSA->sliceTiming[0] == 0) + nTimeZero++; + int minTimeIndex = 0; + int maxTimeIndex = minTimeIndex; + minTimeValue = CSA->sliceTiming[0]; + maxTimeValue = minTimeValue; + if (isVerbose > 1) + printMessage(" sliceTimes %g\t", CSA->sliceTiming[0]); + for (int z = 1; z < itemsOK; z++) { //find index and value of fastest time + if (isVerbose > 1) + printMessage("%g\t", CSA->sliceTiming[z]); + if (CSA->sliceTiming[z] == 0) + nTimeZero++; + if (CSA->sliceTiming[z] < minTimeValue) { + minTimeValue = CSA->sliceTiming[z]; + minTimeIndex = (float)z; + } + if (CSA->sliceTiming[z] > maxTimeValue) { + maxTimeValue = CSA->sliceTiming[z]; + maxTimeIndex = (float)z; + } + if (CSA->sliceTiming[z] == timeValue1) + CSA->multiBandFactor++; + } + if (isVerbose > 1) + printMessage("\n"); + CSA->slice_start = minTimeIndex; + CSA->slice_end = maxTimeIndex; + if (minTimeIndex == maxTimeIndex) { + if (isVerbose) + printMessage("No variability in slice times (3D EPI?)\n"); + } + if (nTimeZero < 2) { //not for multi-band, not 3D + if (minTimeIndex == 1) + CSA->sliceOrder = NIFTI_SLICE_ALT_INC2; // e.g. 3,1,4,2 + else if (minTimeIndex == (itemsOK - 2)) + CSA->sliceOrder = NIFTI_SLICE_ALT_DEC2; // e.g. 2,4,1,3 or 5,2,4,1,3 + else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] < CSA->sliceTiming[2])) + CSA->sliceOrder = NIFTI_SLICE_SEQ_INC; // e.g. 1,2,3,4 + else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] > CSA->sliceTiming[2])) + CSA->sliceOrder = NIFTI_SLICE_ALT_INC; //e.g. 1,3,2,4 + else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] > CSA->sliceTiming[itemsOK - 2])) + CSA->sliceOrder = NIFTI_SLICE_SEQ_DEC; //e.g. 4,3,2,1 or 5,4,3,2,1 + else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] < CSA->sliceTiming[itemsOK - 2])) + CSA->sliceOrder = NIFTI_SLICE_ALT_DEC; //e.g. 4,2,3,1 or 3,5,2,4,1 + else { + if (!is3DAcq) //we expect 3D sequences to be simultaneous + printWarning("Unable to determine slice order from CSA tag MosaicRefAcqTimes\n"); + } + } + if ((CSA->sliceOrder != NIFTI_SLICE_UNKNOWN) && (nTimeZero > 1) && (nTimeZero < itemsOK)) { + if (isVerbose) + printMessage(" Multiband x%d sequence: setting slice order as UNKNOWN (instead of %d)\n", nTimeZero, CSA->sliceOrder); + CSA->sliceOrder = NIFTI_SLICE_UNKNOWN; + } +} //checkSliceTimes() + +int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, int isVerbose, bool is3DAcq) { +//see also http://afni.nimh.nih.gov/pub/dist/src/siemens_dicom_csa.c +//printMessage("%c%c%c%c\n",buff[0],buff[1],buff[2],buff[3]); + if (lLength < 36) + return EXIT_FAILURE; + if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0')) + return EXIT_FAILURE; + int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2 + int lnTag = buff[lPos] + (buff[lPos + 1] << 8) + (buff[lPos + 2] << 16) + (buff[lPos + 3] << 24); + if (buff[lPos + 4] != 77) + return EXIT_FAILURE; + lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0 + TCSAtag tagCSA; + TCSAitem itemCSA; + int itemsOK; + float lFloats[7]; + for (int lT = 1; lT <= lnTag; lT++) { + memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag + lPos += sizeof(tagCSA); + // Storage order is always little-endian, so byte-swap required values if necessary + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &tagCSA.nitems); + if (isVerbose > 1) //extreme verbosity: show every CSA tag + printMessage(" %d CSA of %s %d\n", lPos, tagCSA.name, tagCSA.nitems); + if (tagCSA.nitems > 0) { + if (strcmp(tagCSA.name, "ImageHistory") == 0) + CSA->isPhaseMap = csaIsPhaseMap(&buff[lPos], tagCSA.nitems); + else if (strcmp(tagCSA.name, "NumberOfImagesInMosaic") == 0) + CSA->mosaicSlices = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK)); + else if (strcmp(tagCSA.name, "B_value") == 0) { + CSA->dtiV[0] = csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK); + if (CSA->dtiV[0] < 0.0) { + printWarning("(Corrupt) CSA reports negative b-value! %g\n", CSA->dtiV[0]); + CSA->dtiV[0] = 0.0; + } + CSA->numDti = 1; //triggered by b-value, as B0 images do not have DiffusionGradientDirection tag + } else if ((strcmp(tagCSA.name, "DiffusionGradientDirection") == 0) && (tagCSA.nitems > 2)) { + CSA->dtiV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); + CSA->dtiV[2] = lFloats[2]; + CSA->dtiV[3] = lFloats[3]; + if (isVerbose) + printMessage("DiffusionGradientDirection %f %f %f\n", lFloats[1], lFloats[2], lFloats[3]); + } else if ((strcmp(tagCSA.name, "SliceNormalVector") == 0) && (tagCSA.nitems > 2)) { + CSA->sliceNormV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); + CSA->sliceNormV[2] = lFloats[2]; + CSA->sliceNormV[3] = lFloats[3]; + if (isVerbose > 1) + printMessage(" SliceNormalVector %f %f %f\n", CSA->sliceNormV[1], CSA->sliceNormV[2], CSA->sliceNormV[3]); + } else if (strcmp(tagCSA.name, "SliceMeasurementDuration") == 0) + CSA->sliceMeasurementDuration = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); + else if (strcmp(tagCSA.name, "BandwidthPerPixelPhaseEncode") == 0) + CSA->bandwidthPerPixelPhaseEncode = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); + else if ((strcmp(tagCSA.name, "MosaicRefAcqTimes") == 0) && (tagCSA.nitems > 3)) { + if (itemsOK > kMaxEPI3D) { + printError("Please increase kMaxEPI3D and recompile\n"); + } else { + float *sliceTimes = (float *)malloc(sizeof(float) * (tagCSA.nitems + 1)); + csaMultiFloat(&buff[lPos], tagCSA.nitems, sliceTimes, &itemsOK); + for (int z = 0; z < kMaxEPI3D; z++) + CSA->sliceTiming[z] = -1.0; + for (int z = 0; z < itemsOK; z++) + CSA->sliceTiming[z] = sliceTimes[z + 1]; + free(sliceTimes); + checkSliceTimes(CSA, itemsOK, isVerbose, is3DAcq); + } + } else if (strcmp(tagCSA.name, "ProtocolSliceNumber") == 0) + CSA->protocolSliceNumber1 = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK)); + else if (strcmp(tagCSA.name, "PhaseEncodingDirectionPositive") == 0) + CSA->phaseEncodingDirectionPositive = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK)); + for (int lI = 1; lI <= tagCSA.nitems; lI++) { + memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); + lPos += sizeof(itemCSA); + // Storage order is always little-endian, so byte-swap required values if necessary + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &itemCSA.xx2_Len); + lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; + } + } //if at least 1 item + } // for lT 1..lnTag + if (CSA->protocolSliceNumber1 > 1) + CSA->sliceOrder = NIFTI_SLICE_UNKNOWN; + return EXIT_SUCCESS; +} // readCSAImageHeader() + +void dcmMultiShorts(int lByteLength, unsigned char lBuffer[], int lnShorts, uint16_t *lShorts, bool littleEndian) { +//read array of unsigned shorts US http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html + if ((lnShorts < 1) || (lByteLength != (lnShorts * 2))) + return; + memcpy(&lShorts[0], (uint16_t *)&lBuffer[0], lByteLength); + bool swap = (littleEndian != littleEndianPlatform()); + if (swap) + nifti_swap_2bytes(lnShorts, &lShorts[0]); +} //dcmMultiShorts() + +void dcmMultiLongs(int lByteLength, unsigned char lBuffer[], int lnLongs, uint32_t *lLongs, bool littleEndian) { +//read array of unsigned longs UL http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html + if ((lnLongs < 1) || (lByteLength != (lnLongs * 4))) + return; + memcpy(&lLongs[0], (uint32_t *)&lBuffer[0], lByteLength); + bool swap = (littleEndian != littleEndianPlatform()); + if (swap) + nifti_swap_4bytes(lnLongs, &lLongs[0]); +} //dcmMultiLongs() + +void dcmMultiFloat(int lByteLength, char lBuffer[], int lnFloats, float *lFloats) { +//warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] + if ((lnFloats < 1) || (lByteLength < 1)) + return; + char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); + memcpy(cString, (char *)&lBuffer[0], lByteLength); + cString[lByteLength] = 0; //null terminate + char *temp = (char *)malloc(lByteLength + 1); + int f = 0, lStart = 0; + bool isOK = false; + for (int i = 0; i <= lByteLength; i++) { + if ((lBuffer[i] >= '0') && (lBuffer[i] <= '9')) + isOK = true; + if ((isOK) && ((i == (lByteLength)) || (lBuffer[i] == '/') || (lBuffer[i] == ' ') || (lBuffer[i] == '\\'))) { + snprintf(temp, i - lStart + 1, "%s", &cString[lStart]); + //printMessage("dcmMultiFloat %s\n",temp); + if (f < lnFloats) { + f++; + lFloats[f] = (float)atof(temp); + isOK = false; + //printMessage("%d == %f\n", f, atof(temp)); + } //if f <= nFloats + lStart = i + 1; + } //if isOK + } //for i to length + free(temp); + free(cString); +} //dcmMultiFloat() + +float dcmStrFloat(const int lByteLength, const unsigned char lBuffer[]) { //read float stored as a string + char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); + memcpy(cString, (char *)&lBuffer[0], lByteLength); + cString[lByteLength] = 0; //null terminate + float ret = (float)atof(cString); + free(cString); + return ret; +} //dcmStrFloat() + +int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm) { + memset(h, 0, sizeof(nifti_1_header)); //zero-fill structure so unused items are consistent + for (int i = 0; i < 80; i++) + h->descrip[i] = 0; + for (int i = 0; i < 24; i++) + h->aux_file[i] = 0; + for (int i = 0; i < 18; i++) + h->db_name[i] = 0; + for (int i = 0; i < 10; i++) + h->data_type[i] = 0; + for (int i = 0; i < 16; i++) + h->intent_name[i] = 0; + if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3)) { + h->intent_code = NIFTI_INTENT_ESTIMATE; //make sure we treat this as RGBRGB...RGB + h->datatype = DT_RGB24; + } else if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 1)) + h->datatype = DT_UINT8; + else if ((d.bitsAllocated == 12) && (d.samplesPerPixel == 1)) + h->datatype = DT_INT16; + else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (d.isSigned)) + h->datatype = DT_INT16; + else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (!d.isSigned)) + h->datatype = DT_UINT16; + else if ((d.bitsAllocated == 32) && (d.isFloat)) + h->datatype = DT_FLOAT32; + else if (d.bitsAllocated == 32) + h->datatype = DT_INT32; + else if ((d.bitsAllocated == 64) && (d.isFloat)) + h->datatype = DT_FLOAT64; + else { + printMessage("Unsupported DICOM bit-depth %d with %d samples per pixel\n", d.bitsAllocated, d.samplesPerPixel); + return EXIT_FAILURE; + } + if ((h->datatype == DT_UINT16) && (d.bitsStored > 0) && (d.bitsStored < 16)) + h->datatype = DT_INT16; // DT_INT16 is more widely supported, same representation for values 0..32767 + for (int i = 0; i < 8; i++) { + h->pixdim[i] = 0.0f; + h->dim[i] = 0; + } + //next items listed as unused in NIfTI format, but zeroed for consistency across runs + h->extents = 0; + h->session_error = kSessionOK; + h->glmin = 0; //unused, but make consistent + h->glmax = 0; //unused, but make consistent + h->regular = 114; //in legacy Analyze this was always 114 + //these are important + h->scl_inter = d.intenIntercept; + h->scl_slope = d.intenScale; + h->cal_max = 0; + h->cal_min = 0; + h->magic[0] = 'n'; + h->magic[1] = '+'; + h->magic[2] = '1'; + h->magic[3] = '\0'; + h->vox_offset = (float)d.imageStart; + if (d.bitsAllocated == 12) + h->bitpix = 16 * d.samplesPerPixel; + else + h->bitpix = d.bitsAllocated * d.samplesPerPixel; + h->pixdim[1] = d.xyzMM[1]; + h->pixdim[2] = d.xyzMM[2]; + h->pixdim[3] = d.xyzMM[3]; + h->pixdim[4] = d.TR / 1000.0; //TR reported in msec, time is in sec + h->dim[1] = d.xyzDim[1]; + h->dim[2] = d.xyzDim[2]; + h->dim[3] = d.xyzDim[3]; + h->dim[4] = d.xyzDim[4]; + h->dim[5] = 1; + h->dim[6] = 1; + h->dim[7] = 1; + if (h->dim[4] < 2) + h->dim[0] = 3; + else + h->dim[0] = 4; + for (int i = 0; i <= 3; i++) { + h->srow_x[i] = 0.0f; + h->srow_y[i] = 0.0f; + h->srow_z[i] = 0.0f; + } + h->slice_start = 0; + h->slice_end = 0; + h->srow_x[0] = -1; + h->srow_y[2] = 1; + h->srow_z[1] = -1; + h->srow_x[3] = ((float)h->dim[1] / 2); + h->srow_y[3] = -((float)h->dim[3] / 2); + h->srow_z[3] = ((float)h->dim[2] / 2); + h->qform_code = NIFTI_XFORM_UNKNOWN; + h->sform_code = NIFTI_XFORM_UNKNOWN; + h->toffset = 0; + h->intent_code = NIFTI_INTENT_NONE; + h->dim_info = 0; //Freq, Phase and Slice all unknown + h->xyzt_units = NIFTI_UNITS_MM + NIFTI_UNITS_SEC; + h->slice_duration = 0; //avoid +inf/-inf, NaN + h->intent_p1 = 0; //avoid +inf/-inf, NaN + h->intent_p2 = 0; //avoid +inf/-inf, NaN + h->intent_p3 = 0; //avoid +inf/-inf, NaN + h->pixdim[0] = 1; //QFactor should be 1 or -1 + h->sizeof_hdr = 348; //used to signify header does not need to be byte-swapped + h->slice_code = d.CSA.sliceOrder; + if (isComputeSForm) + headerDcm2Nii2(d, d, h, false); + return EXIT_SUCCESS; +} // headerDcm2Nii() + +bool isFloatDiff(float a, float b) { + return (fabs(a - b) > FLT_EPSILON); +} //isFloatDiff() + +mat33 nifti_mat33_reorder_cols(mat33 m, ivec3 v) { +// matlab equivalent ret = m(:, v); where v is 1,2,3 [INDEXED FROM ONE!!!!] + mat33 ret; + for (int r = 0; r < 3; r++) { + for (int c = 0; c < 3; c++) + ret.m[r][c] = m.m[r][v.v[c] - 1]; + } + return ret; +} //nifti_mat33_reorder_cols() + +void changeExt(char *file_name, const char *ext) { + char *p_extension; + p_extension = strrchr(file_name, '.'); + if (p_extension) + strcpy(++p_extension, ext); +} //changeExt() + +void cleanStr(char *lOut) { +//e.g. strings such as image comments with special characters (e.g. "G/6/2009") can disrupt file saves + size_t lLength = strlen(lOut); + if (lLength < 1) + return; + char *cString = (char *)malloc(sizeof(char) * (lLength + 1)); + cString[lLength] = 0; + memcpy(cString, (char *)&lOut[0], lLength); + for (int i = 0; i < lLength; i++) + //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 + if (cString[i] < 1) { + unsigned char c = (unsigned char)cString[i]; + if ((c >= 192) && (c <= 198)) + cString[i] = 'A'; + if (c == 199) + cString[i] = 'C'; + if ((c >= 200) && (c <= 203)) + cString[i] = 'E'; + if ((c >= 204) && (c <= 207)) + cString[i] = 'I'; + if (c == 208) + cString[i] = 'D'; + if (c == 209) + cString[i] = 'N'; + if ((c >= 210) && (c <= 214)) + cString[i] = 'O'; + if (c == 215) + cString[i] = 'x'; + if (c == 216) + cString[i] = 'O'; + if ((c >= 217) && (c <= 220)) + cString[i] = 'O'; + if (c == 221) + cString[i] = 'Y'; + if ((c >= 224) && (c <= 230)) + cString[i] = 'a'; + if (c == 231) + cString[i] = 'c'; + if ((c >= 232) && (c <= 235)) + cString[i] = 'e'; + if ((c >= 236) && (c <= 239)) + cString[i] = 'i'; + if (c == 240) + cString[i] = 'o'; + if (c == 241) + cString[i] = 'n'; + if ((c >= 242) && (c <= 246)) + cString[i] = 'o'; + if (c == 248) + cString[i] = 'o'; + if ((c >= 249) && (c <= 252)) + cString[i] = 'u'; + if (c == 253) + cString[i] = 'y'; + if (c == 255) + cString[i] = 'y'; + } + for (int i = 0; i < lLength; i++) + if ((cString[i] < 1) || (cString[i] == ' ') || (cString[i] == ',') || (cString[i] == '/') || (cString[i] == '\\') || (cString[i] == '%') || (cString[i] == '*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) + cString[i] = '_'; //issue398 + //if ((cString[i]<1) || (cString[i]==' ') || (cString[i]==',') || (cString[i]=='^') || (cString[i]=='/') || (cString[i]=='\\') || (cString[i]=='%') || (cString[i]=='*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_'; + int len = 1; + for (int i = 1; i < lLength; i++) { //remove repeated "_" + if ((cString[i - 1] != '_') || (cString[i] != '_')) { + cString[len] = cString[i]; + len++; + } + } //for each item + if (cString[len - 1] == '_') + len--; + cString[len] = 0; //null-terminate, strlcpy does this anyway + int maxLen = kDICOMStr; + len = dcmStrLen(len, maxLen); + if (len == maxLen) { //we need space for null-termination + if (cString[len - 2] == '_') + len = len - 2; + } + memcpy(lOut, cString, len - 1); + lOut[len - 1] = 0; + free(cString); +} //cleanStr() + +int isSameFloatGE(float a, float b) { + //Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! + //return (a == b); //niave approach does not have any tolerance for rounding errors + return (fabs(a - b) <= 0.0001); +} + +struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase) { + struct TDICOMdata d = clear_dicom_data(); + dti4D->sliceOrder[0] = -1; + dti4D->volumeOnsetTime[0] = -1; + dti4D->decayFactor[0] = -1; + dti4D->frameDuration[0] = -1; + //dti4D->fragmentOffset[0] = -1; + dti4D->intenScale[0] = 0.0; + strcpy(d.protocolName, ""); //erase dummy with empty + strcpy(d.seriesDescription, ""); //erase dummy with empty + strcpy(d.sequenceName, ""); //erase dummy with empty + strcpy(d.scanningSequence, ""); + FILE *fp = fopen(parname, "r"); + if (fp == NULL) + return d; +#define LINESZ 2048 +#define kSlice 0 +#define kEcho 1 +#define kDyn 2 +#define kCardiac 3 +#define kImageType 4 +#define kSequence 5 +#define kIndex 6 +//V3 only identical for columns 1..6 +#define kBitsPerVoxel 7 //V3: not per slice: "Image pixel size [8 or 16 bits]" +#define kXdim 9 //V3: not per slice: "Recon resolution (x, y)" +#define kYdim 10 //V3: not per slice: "Recon resolution (x, y)" + int kRI = 11; //V3: 7 + int kRS = 12; //V3: 8 + int kSS = 13; //V3: 9 + int kAngulationAPs = 16; //V3: 12 + int kAngulationFHs = 17; //V3: 13 + int kAngulationRLs = 18; //V3: 14 + int kPositionAP = 19; //V3: 15 + int kPositionFH = 20; //V3: 16 + int kPositionRL = 21; //V3: 17 +#define kThickmm 22 //V3: not per slice: "Slice thickness [mm]" +#define kGapmm 23 //V3: not per slice: "Slice gap [mm]" + int kSliceOrients = 25; //V3: 19 + int kXmm = 28; //V3: 22 + int kYmm = 29; //V3: 23 + int kTEcho = 30; //V3: 24 + int kDynTime = 31; //V3: 25 + int kTriggerTime = 32; //V3: 26 + int kbval = 33;//V3: 27 +//the following do not exist in V3 +#define kInversionDelayMs 40 +#define kbvalNumber 41 +#define kGradientNumber 42 +//the following do not exist in V40 or earlier +#define kv1 47 +#define kv2 45 +#define kv3 46 +//the following do not exist in V41 or earlier +#define kASL 48 +#define kMaxImageType 4 //4 observed image types: real, imag, mag, phase (in theory also subsequent calculation such as B1) + printWarning("dcm2niix PAR is not actively supported (hint: use dicm2nii)\n"); + if (isReadPhase) + printWarning(" Reading phase images from PAR/REC\n"); + char buff[LINESZ]; + //next values: PAR V3 only + int v3BitsPerVoxel = 16; //V3: not per slice: "Image pixel size [8 or 16 bits]" + int v3Xdim = 128; //not per slice: "Recon resolution (x, y)" + int v3Ydim = 128; //V3: not per slice: "Recon resolution (x, y)" + float v3Thickmm = 2.0; //V3: not per slice: "Slice thickness [mm]" + float v3Gapmm = 0.0; //V3: not per slice: "Slice gap [mm]" + //from top of header + int maxNumberOfDiffusionValues = 1; + int maxNumberOfGradientOrients = 1; + int maxNumberOfCardiacPhases = 1; + int maxNumberOfEchoes = 1; + int maxNumberOfDynamics = 1; + int maxNumberOfMixes = 1; + int maxNumberOfLabels = 1; //Number of label types <0=no ASL> + float maxBValue = 0.0f; + float maxDynTime = 0.0f; + float minDynTime = 999999.0f; + float TE = 0.0; + int minDyn = 32767; + int maxDyn = 0; + int minSlice = 32767; + int maxSlice = 0; + bool ADCwarning = false; + bool isTypeWarning = false; + bool isType4Warning = false; + bool isSequenceWarning = false; + int numSlice2D = 0; + int prevDyn = -1; + bool dynNotAscending = false; + int parVers = 0; + int maxSeq = -1; //maximum value of Seq column + int seq1 = -1; //value of Seq volume for first slice + int maxEcho = 1; + int maxCardiac = 1; + int nCols = 26; + //int diskSlice = 0; + int num3DExpected = 0; //number of 3D volumes in the top part of the header + int num2DExpected = 0; //number of 2D slices described in the top part of the header + int maxVol = -1; + int patientPositionNumPhilips = 0; + d.isValid = false; + const int kMaxCols = 49; + float *cols = (float *)malloc(sizeof(float) * (kMaxCols + 1)); + for (int i = 0; i < kMaxCols; i++) + cols[i] = 0.0; //old versions of PAR do not fill all columns - beware of buffer overflow + char *p = fgets(buff, LINESZ, fp); + bool isIntenScaleVaries = false; + for (int i = 0; i < kMaxDTI4D; i++) { + dti4D->S[i].V[0] = -1.0; + dti4D->TE[i] = -1.0; + } + for (int i = 0; i < kMaxSlice2D; i++) + dti4D->sliceOrder[i] = -1; + while (p) { + if (strlen(buff) < 1) + continue; + if (buff[0] == '#') { //comment + char Comment[7][50]; + sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3], Comment[4], Comment[5], Comment[6]); + if ((strcmp(Comment[0], "sl") == 0) && (strcmp(Comment[1], "ec") == 0)) { + num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; + num2DExpected = d.xyzDim[3] * num3DExpected; + if ((num2DExpected) >= kMaxSlice2D) { + printError("Use dicm2nii or increase kMaxSlice2D to be more than %d\n", num2DExpected); + printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", + d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, + maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); + free(cols); + return d; + } + } + if (strcmp(Comment[1], "TRYOUT") == 0) { + //sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3],Comment[4], Comment[5],Comment[6]); + parVers = (int)round(atof(Comment[6]) * 10); //4.2 = 42 etc + if (parVers <= 29) { + printMessage("Unsupported old PAR version %0.2f (use dicm2nii)\n", parVers / 10.0); + return d; + //nCols = 26; //e.g. PAR 3.0 has 26 relevant columns + } + if (parVers < 40) { + nCols = 29; // PAR 3.0? + kRI = 7; + kRS = 8; + kSS = 9; + kAngulationAPs = 12; + kAngulationFHs = 13; + kAngulationRLs = 14; + kPositionAP = 15; + kPositionFH = 16; + kPositionRL = 17; + kSliceOrients = 19; + kXmm = 22; + kYmm = 23; + kTEcho = 24; + kDynTime = 25; + kTriggerTime = 26; + kbval = 27; + } else if (parVers < 41) + nCols = kv1; //e.g PAR 4.0 + else if (parVers < 42) + nCols = kASL; //e.g. PAR 4.1 - last column is final diffusion b-value + else + nCols = kMaxCols; //e.g. PAR 4.2 + } + //the following do not exist in V3 + p = fgets(buff, LINESZ, fp); //get next line + continue; + } //process '#' comment + if (buff[0] == '.') { //tag + char Comment[9][50]; + for (int i = 0; i < 9; i++) + strcpy(Comment[i], ""); + sscanf(buff, ". %s %s %s %s %s %s %s %s %s\n", Comment[0], Comment[1], Comment[2], Comment[3], Comment[4], Comment[5], Comment[6], Comment[7], Comment[8]); + if ((strcmp(Comment[0], "Acquisition") == 0) && (strcmp(Comment[1], "nr") == 0)) { + d.acquNum = atoi(Comment[3]); + d.seriesNum = d.acquNum; + } + if ((strcmp(Comment[0], "Recon") == 0) && (strcmp(Comment[1], "resolution") == 0)) { + v3Xdim = (int)atoi(Comment[5]); + v3Ydim = (int)atoi(Comment[6]); + //printMessage("recon %d,%d\n", v3Xdim,v3Ydim); + } + if ((strcmp(Comment[1], "pixel") == 0) && (strcmp(Comment[2], "size") == 0)) { + v3BitsPerVoxel = (int)atoi(Comment[8]); + //printMessage("bits %d\n", v3BitsPerVoxel); + } + if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "gap") == 0)) { + v3Gapmm = (float)atof(Comment[4]); + //printMessage("gap %g\n", v3Gapmm); + } + if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "thickness") == 0)) { + v3Thickmm = (float)atof(Comment[4]); + //printMessage("thick %g\n", v3Thickmm); + } + if ((strcmp(Comment[0], "Repetition") == 0) && (strcmp(Comment[1], "time") == 0)) + d.TR = (float)atof(Comment[4]); + if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "name") == 0)) { + strcpy(d.patientName, Comment[3]); + strcat(d.patientName, Comment[4]); + strcat(d.patientName, Comment[5]); + strcat(d.patientName, Comment[6]); + strcat(d.patientName, Comment[7]); + cleanStr(d.patientName); + //printMessage("%s\n",d.patientName); + } + if ((strcmp(Comment[0], "Technique") == 0) && (strcmp(Comment[1], ":") == 0)) { + strcpy(d.patientID, Comment[2]); + strcat(d.patientID, Comment[3]); + strcat(d.patientID, Comment[4]); + strcat(d.patientID, Comment[5]); + strcat(d.patientID, Comment[6]); + strcat(d.patientID, Comment[7]); + cleanStr(d.patientID); + } + if ((strcmp(Comment[0], "Protocol") == 0) && (strcmp(Comment[1], "name") == 0)) { + strcpy(d.protocolName, Comment[3]); + strcat(d.protocolName, Comment[4]); + strcat(d.protocolName, Comment[5]); + strcat(d.protocolName, Comment[6]); + strcat(d.protocolName, Comment[7]); + cleanStr(d.protocolName); + } + if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "name") == 0)) { + strcpy(d.imageComments, Comment[3]); + strcat(d.imageComments, Comment[4]); + strcat(d.imageComments, Comment[5]); + strcat(d.imageComments, Comment[6]); + strcat(d.imageComments, Comment[7]); + cleanStr(d.imageComments); + } + if ((strcmp(Comment[0], "Series") == 0) && (strcmp(Comment[1], "Type") == 0)) { + strcpy(d.seriesDescription, Comment[3]); + strcat(d.seriesDescription, Comment[4]); + strcat(d.seriesDescription, Comment[5]); + strcat(d.seriesDescription, Comment[6]); + strcat(d.seriesDescription, Comment[7]); + cleanStr(d.seriesDescription); + } + if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "date/time") == 0)) { + if ((strlen(Comment[3]) >= 10) && (strlen(Comment[5]) >= 8)) { + //DICOM date format is YYYYMMDD, but PAR stores YYYY.MM.DD 2016.03.25 + d.studyDate[0] = Comment[3][0]; + d.studyDate[1] = Comment[3][1]; + d.studyDate[2] = Comment[3][2]; + d.studyDate[3] = Comment[3][3]; + d.studyDate[4] = Comment[3][5]; + d.studyDate[5] = Comment[3][6]; + d.studyDate[6] = Comment[3][8]; + d.studyDate[7] = Comment[3][9]; + d.studyDate[8] = '\0'; + //DICOM time format is HHMMSS.FFFFFF, but PAR stores HH:MM:SS, e.g. 18:00:42 or 09:34:16 + d.studyTime[0] = Comment[5][0]; + d.studyTime[1] = Comment[5][1]; + d.studyTime[2] = Comment[5][3]; + d.studyTime[3] = Comment[5][4]; + d.studyTime[4] = Comment[5][6]; + d.studyTime[5] = Comment[5][7]; + d.studyTime[6] = '\0'; + d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime); + } + } + if ((strcmp(Comment[0], "Off") == 0) && (strcmp(Comment[1], "Centre") == 0)) { + //Off Centre midslice(ap,fh,rl) [mm] + d.stackOffcentre[2] = (float)atof(Comment[5]); + d.stackOffcentre[3] = (float)atof(Comment[6]); + d.stackOffcentre[1] = (float)atof(Comment[7]); + } + if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "position") == 0)) { + //Off Centre midslice(ap,fh,rl) [mm] + d.patientOrient[0] = toupper(Comment[3][0]); + d.patientOrient[1] = toupper(Comment[4][0]); + d.patientOrient[2] = toupper(Comment[5][0]); + d.patientOrient[3] = 0; + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "slices/locations") == 0)) { + d.xyzDim[3] = atoi(Comment[5]); + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "diffusion") == 0)) { + maxNumberOfDiffusionValues = atoi(Comment[6]); + //if (maxNumberOfDiffusionValues > 1) maxNumberOfDiffusionValues -= 1; //if two listed, one is B=0 + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "gradient") == 0)) { + maxNumberOfGradientOrients = atoi(Comment[6]); + //Warning ISOTROPIC scans may be stored that are not reported here! 32 directions plus isotropic = 33 volumes + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "cardiac") == 0)) { + maxNumberOfCardiacPhases = atoi(Comment[6]); + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "echoes") == 0)) { + maxNumberOfEchoes = atoi(Comment[5]); + if (maxNumberOfEchoes > 1) + d.isMultiEcho = true; + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "dynamics") == 0)) { + maxNumberOfDynamics = atoi(Comment[5]); + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "mixes") == 0)) { + maxNumberOfMixes = atoi(Comment[5]); + if (maxNumberOfMixes > 1) + printError("maxNumberOfMixes > 1. Please update this software to support these images\n"); + } + if ((strcmp(Comment[0], "Number") == 0) && (strcmp(Comment[2], "label") == 0)) { + maxNumberOfLabels = atoi(Comment[7]); + if (maxNumberOfLabels < 1) + maxNumberOfLabels = 1; + } + p = fgets(buff, LINESZ, fp); //get next line + continue; + } //process '.' tag + if (strlen(buff) < 24) { //empty line + p = fgets(buff, LINESZ, fp); //get next line + continue; + } + if (parVers < 20) { + printError("PAR files should have 'CLINICAL TRYOUT' line with a version from 2.0-4.2: %s\n", parname); + free(cols); + return d; + } + for (int i = 0; i <= nCols; i++) + cols[i] = strtof(p, &p); // p+1 skip comma, read a float + //printMessage("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1],(int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); + if ((int)cols[kSlice] == 0) { //line does not contain attributes + p = fgets(buff, LINESZ, fp); //get next line + continue; + } + //diskSlice ++; + bool isADC = false; + if ((maxNumberOfGradientOrients >= 2) && (cols[kbval] > 50) && isSameFloat(0.0, cols[kv1]) && isSameFloat(0.0, cols[kv2]) && isSameFloat(0.0, cols[kv3])) { + isADC = true; + ADCwarning = true; + } + if (numSlice2D < 1) { + d.xyzMM[1] = cols[kXmm]; + d.xyzMM[2] = cols[kYmm]; + if (parVers < 40) { //v3 does things differently + //cccc + d.xyzDim[1] = v3Xdim; + d.xyzDim[2] = v3Ydim; + d.xyzMM[3] = v3Thickmm + v3Gapmm; + d.bitsAllocated = v3BitsPerVoxel; + d.bitsStored = v3BitsPerVoxel; + } else { + d.xyzDim[1] = (int)cols[kXdim]; + d.xyzDim[2] = (int)cols[kYdim]; + d.xyzMM[3] = cols[kThickmm] + cols[kGapmm]; + d.bitsAllocated = (int)cols[kBitsPerVoxel]; + d.bitsStored = (int)cols[kBitsPerVoxel]; + } + d.patientPosition[1] = cols[kPositionRL]; + d.patientPosition[2] = cols[kPositionAP]; + d.patientPosition[3] = cols[kPositionFH]; + d.angulation[1] = cols[kAngulationRLs]; + d.angulation[2] = cols[kAngulationAPs]; + d.angulation[3] = cols[kAngulationFHs]; + d.sliceOrient = (int)cols[kSliceOrients]; + d.TE = cols[kTEcho]; + d.echoNum = cols[kEcho]; + d.TI = cols[kInversionDelayMs]; + d.intenIntercept = cols[kRI]; + d.intenScale = cols[kRS]; + d.intenScalePhilips = cols[kSS]; + } else { + if (parVers >= 40) { + if ((d.xyzDim[1] != cols[kXdim]) || (d.xyzDim[2] != cols[kYdim]) || (d.bitsAllocated != cols[kBitsPerVoxel])) { + printError("Slice dimensions or bit depth varies %s\n", parname); + printError("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1], (int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); + return d; + } + } + if ((d.intenScale != cols[kRS]) || (d.intenIntercept != cols[kRI])) + isIntenScaleVaries = true; + } + if (cols[kImageType] == 0) + d.isHasMagnitude = true; + if (cols[kImageType] != 0) + d.isHasPhase = true; + if (isSameFloat(cols[kImageType], 18)) { + //printWarning("Field map in Hz will be saved as the 'real' image.\n"); + //isTypeWarning = true; + d.isRealIsPhaseMapHz = true; + } else if (((cols[kImageType] < 0.0) || (cols[kImageType] > 4.0)) && (!isTypeWarning)) { + printError("Unknown type %g: not magnitude[0], real[1], imaginary[2] or phase[3].\n", cols[kImageType]); + isTypeWarning = true; + } + if (cols[kDyn] > maxDyn) + maxDyn = (int)cols[kDyn]; + if (cols[kDyn] < minDyn) + minDyn = (int)cols[kDyn]; + if (cols[kDyn] < prevDyn) + dynNotAscending = true; + prevDyn = cols[kDyn]; + if (cols[kDynTime] > maxDynTime) + maxDynTime = cols[kDynTime]; + if (cols[kDynTime] < minDynTime) + minDynTime = cols[kDynTime]; + if (cols[kEcho] > maxEcho) + maxEcho = cols[kEcho]; + if (cols[kCardiac] > maxCardiac) + maxCardiac = cols[kCardiac]; + if ((cols[kEcho] == 1) && (cols[kDyn] == 1) && (cols[kCardiac] == 1) && (cols[kGradientNumber] == 1)) { + if (cols[kSlice] == 1) { + d.patientPosition[1] = cols[kPositionRL]; + d.patientPosition[2] = cols[kPositionAP]; + d.patientPosition[3] = cols[kPositionFH]; + } + patientPositionNumPhilips++; + } + if (true) { //for every slice + int slice = (int)cols[kSlice]; + if (slice < minSlice) + minSlice = slice; + if (slice > maxSlice) { + maxSlice = slice; + d.patientPositionLast[1] = cols[kPositionRL]; + d.patientPositionLast[2] = cols[kPositionAP]; + d.patientPositionLast[3] = cols[kPositionFH]; + } + int volStep = maxNumberOfDynamics; + int vol = ((int)cols[kDyn] - 1); +#ifdef old + int gradDynVol = (int)cols[kGradientNumber] - 1; + if (gradDynVol < 0) + gradDynVol = 0; //old PAREC without cols[kGradientNumber] + vol = vol + (volStep * (gradDynVol)); + if (vol < 0) + vol = 0; + volStep = volStep * maxNumberOfGradientOrients; + int bval = (int)cols[kbvalNumber]; + if (bval > 2) //b=0 is 0, b=1000 is 1, b=2000 is 2 - b=0 does not have multiple directions + bval = bval - 1; + else + bval = 1; + //if (slice == 1) printMessage("bVal %d bVec %d isADC %d nbVal %d nGrad %d\n",(int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); + vol = vol + (volStep * (bval - 1)); + volStep = volStep * (maxNumberOfDiffusionValues - 1); + if (isADC) + vol = volStep + (bval - 1); +#else + if (maxNumberOfDiffusionValues > 1) { + int grad = (int)cols[kGradientNumber] - 1; + if (grad < 0) + grad = 0; //old v4 does not have this tag + int bval = (int)cols[kbvalNumber] - 1; + if (bval < 0) + bval = 0; //old v4 does not have this tag + if (isADC) + vol = vol + (volStep * maxNumberOfDiffusionValues * maxNumberOfGradientOrients) + bval; + else + vol = vol + (volStep * grad) + (bval * maxNumberOfGradientOrients); + + volStep = volStep * (maxNumberOfDiffusionValues + 1) * maxNumberOfGradientOrients; + //if (slice == 1) printMessage("vol %d step %d bVal %d bVec %d isADC %d nbVal %d nGrad %d\n", vol, volStep, (int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); + } +#endif + vol = vol + (volStep * ((int)cols[kEcho] - 1)); + volStep = volStep * maxNumberOfEchoes; + vol = vol + (volStep * ((int)cols[kCardiac] - 1)); + volStep = volStep * maxNumberOfCardiacPhases; + int ASL = (int)cols[kASL]; + if (ASL < 1) + ASL = 1; + vol = vol + (volStep * (ASL - 1)); + volStep = volStep * maxNumberOfLabels; + //if ((int)cols[kSequence] > 0) + int seq = (int)cols[kSequence]; + if (seq1 < 0) + seq1 = seq; + if (seq > maxSeq) + maxSeq = seq; + if (seq != seq1) { //sequence varies within this PAR file + if (!isSequenceWarning) { + isSequenceWarning = true; + printWarning("'scanning sequence' column varies within a single file. This behavior is not described at the top of the header.\n"); + } + vol = vol + (volStep * 1); + volStep = volStep * 2; + } + //if (slice == 1) printMessage("%d\t%d\t%d\t%d\t%d\n", isADC,(int)cols[kbvalNumber], (int)cols[kGradientNumber], bval, vol); + if (vol > maxVol) + maxVol = vol; + bool isReal = (cols[kImageType] == 1); + bool isImaginary = (cols[kImageType] == 2); + bool isPhase = (cols[kImageType] == 3); + if (cols[kImageType] == 18) { + isReal = true; + d.isRealIsPhaseMapHz = true; + } + if (cols[kImageType] == 4) { + if (!isType4Warning) { + printWarning("Unknown image type (4). Be aware the 'phase' image is of an unknown type.\n"); + isType4Warning = true; + } + isPhase = true; //2019 + } + if ((cols[kImageType] != 18) && ((cols[kImageType] < 0.0) || (cols[kImageType] > 3.0))) { + if (!isType4Warning) { + printWarning("Unknown image type (%g). Be aware the 'phase' image is of an unknown type.\n", round(cols[kImageType])); + isType4Warning = true; + } + isReal = true; //<- this is not correct, kludge for bug in ROGERS_20180526_WIP_B0_NS_8_1.PAR + } + if (isReal) + vol += num3DExpected; + if (isImaginary) + vol += (2 * num3DExpected); + if (isPhase) + vol += (3 * num3DExpected); + if (vol >= kMaxDTI4D) { + printError("Use dicm2nii or increase kMaxDTI4D (currently %d)to be more than %d\n", kMaxDTI4D, kMaxImageType * num2DExpected); + printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", + d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, + maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); + free(cols); + return d; + } + // dti4D->S[vol].V[0] = cols[kbval]; + //dti4D->gradDynVol[vol] = gradDynVol; + dti4D->TE[vol] = cols[kTEcho]; + if (isSameFloatGE(cols[kTEcho], 0)) + dti4D->TE[vol] = TE; //kludge for cols[kImageType]==18 where TE set as 0 + else + TE = cols[kTEcho]; + dti4D->triggerDelayTime[vol] = cols[kTriggerTime]; + if (dti4D->TE[vol] < 0) + dti4D->TE[vol] = 0; //used to detect sparse volumes + //dti4D->intenIntercept[vol] = cols[kRI]; + //dti4D->intenScale[vol] = cols[kRS]; + //dti4D->intenScalePhilips[vol] = cols[kSS]; + dti4D->isReal[vol] = isReal; + dti4D->isImaginary[vol] = isImaginary; + dti4D->isPhase[vol] = isPhase; + if ((maxNumberOfGradientOrients > 1) && (parVers > 40)) { + dti4D->S[vol].V[0] = cols[kbval]; + dti4D->S[vol].V[1] = cols[kv1]; + dti4D->S[vol].V[2] = cols[kv2]; + dti4D->S[vol].V[3] = cols[kv3]; + if ((vol + 1) > d.CSA.numDti) + d.CSA.numDti = vol + 1; + } + if (numSlice2D < kMaxDTI4D) { //issue 363: intensity can vary with each 2D slice of 4D volume + dti4D->intenIntercept[numSlice2D] = cols[kRI]; + dti4D->intenScale[numSlice2D] = cols[kRS]; + dti4D->intenScalePhilips[numSlice2D] = cols[kSS]; + } + //if (slice == 1) printWarning("%d\n", (int)cols[kEcho]); + slice = slice + (vol * d.xyzDim[3]); + //offset images by type: mag+0,real+1, imag+2,phase+3 + //if (cols[kImageType] != 0) //yikes - phase maps! + // slice = slice + numExpected; + //printWarning("%d\t%d\n", slice -1, numSlice2D); + if ((slice >= 0) && (slice < kMaxSlice2D) && (numSlice2D < kMaxSlice2D) && (numSlice2D >= 0)) { + dti4D->sliceOrder[slice - 1] = numSlice2D; + //printMessage("%d\t%d\t%d\n", numSlice2D, slice, (int)cols[kSlice],(int)vol); + } + numSlice2D++; + } + //printMessage("%f %f %lu\n",cols[9],cols[kGradientNumber], strlen(buff)) + p = fgets(buff, LINESZ, fp); //get next line + } + free(cols); + fclose(fp); + if ((parVers <= 0) || (numSlice2D < 1)) { + printError("Invalid PAR format header (unable to detect version or slices) %s\n", parname); + return d; + } + if (numSlice2D > kMaxSlice2D) { //check again after reading, as top portion of header does not report image types or isotropics + printError("Increase kMaxSlice2D from %d to at least %d (or use dicm2nii).\n", kMaxSlice2D, numSlice2D); + return d; + } + if (numSlice2D > kMaxDTI4D) { //since issue460, kMaxSlice2D == kMaxSlice4D, so we should never get here + printError("Increase kMaxDTI4D from %d to at least %d (or use dicm2nii).\n", kMaxDTI4D, numSlice2D); + return d; + } + d.manufacturer = kMANUFACTURER_PHILIPS; + d.isValid = true; + d.isSigned = true; + //remove unused volumes - this will happen if unless we have all 4 image types: real, imag, mag, phase + maxVol = 0; + for (int i = 0; i < kMaxDTI4D; i++) { + if (dti4D->TE[i] > -1.0) { + dti4D->TE[maxVol] = dti4D->TE[i]; + dti4D->triggerDelayTime[maxVol] = dti4D->triggerDelayTime[i]; + //dti4D->intenIntercept[maxVol] = dti4D->intenIntercept[i]; + //dti4D->intenScale[maxVol] = dti4D->intenScale[i]; + //dti4D->intenScalePhilips[maxVol] = dti4D->intenScalePhilips[i]; + dti4D->isReal[maxVol] = dti4D->isReal[i]; + dti4D->isImaginary[maxVol] = dti4D->isImaginary[i]; + dti4D->isPhase[maxVol] = dti4D->isPhase[i]; + dti4D->S[maxVol].V[0] = dti4D->S[i].V[0]; + dti4D->S[maxVol].V[1] = dti4D->S[i].V[1]; + dti4D->S[maxVol].V[2] = dti4D->S[i].V[2]; + dti4D->S[maxVol].V[3] = dti4D->S[i].V[3]; + maxVol = maxVol + 1; + } + } + if (d.CSA.numDti > 0) + d.CSA.numDti = maxVol; //e.g. gradient 2 can skip B=0 but include isotropic + //remove unused slices - this will happen if unless we have all 4 image types: real, imag, mag, phase + int slice = 0; + for (int i = 0; i < kMaxSlice2D; i++) { + if (dti4D->sliceOrder[i] > -1) { //this slice was populated + dti4D->sliceOrder[slice] = dti4D->sliceOrder[i]; + slice = slice + 1; + } + } + if (slice != numSlice2D) { + printError("Catastrophic error: found %d but expected %d slices. %s\n", slice, numSlice2D, parname); + printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n", + d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, + maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); + d.isValid = false; + } + for (int i = 0; i < numSlice2D; i++) { //issue363 + if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) + d.isScaleVariesEnh = true; + if (dti4D->intenScale[i] != dti4D->intenScale[0]) + d.isScaleVariesEnh = true; + if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) + d.isScaleVariesEnh = true; + //printf("%g --> %g\n", dti4D->intenIntercept[i], dti4D->intenScale[i]); + } + if (d.isScaleVariesEnh) { //juggle to sorted order, required for subsequent rescaling + printWarning("PAR/REC intensity scaling varies between slices (please validate output).\n"); + TDTI4D tmp; + for (int i = 0; i < numSlice2D; i++) { //issue363 + tmp.intenIntercept[i] = dti4D->intenIntercept[i]; + tmp.intenScale[i] = dti4D->intenScale[i]; + tmp.intenScalePhilips[i] = dti4D->intenScalePhilips[i]; + } + for (int i = 0; i < numSlice2D; i++) { + int j = dti4D->sliceOrder[i]; + dti4D->intenIntercept[i] = tmp.intenIntercept[j]; + dti4D->intenScale[i] = tmp.intenScale[j]; + dti4D->intenScalePhilips[i] = tmp.intenScalePhilips[j]; + } + } + d.isScaleOrTEVaries = true; + if (numSlice2D > kMaxSlice2D) { + printError("Overloaded slice re-ordering. Number of slices (%d) exceeds kMaxSlice2D (%d)\n", numSlice2D, kMaxSlice2D); + dti4D->sliceOrder[0] = -1; + dti4D->intenScale[0] = 0.0; + } + if ((maxSlice - minSlice + 1) != d.xyzDim[3]) { + int numSlice = (maxSlice - minSlice) + 1; + printWarning("Expected %d slices, but found %d (%d..%d). %s\n", d.xyzDim[3], numSlice, minSlice, maxSlice, parname); + if (numSlice <= 0) + d.isValid = false; + d.xyzDim[3] = numSlice; + num2DExpected = d.xyzDim[3] * num3DExpected; + } + if ((maxBValue <= 0.0f) && (maxDyn > minDyn) && (maxDynTime > minDynTime)) { //use max vs min Dyn instead of && (d.CSA.numDti > 1) + int numDyn = (maxDyn - minDyn) + 1; + if (numDyn != maxNumberOfDynamics) { + printWarning("Expected %d dynamics, but found %d (%d..%d).\n", maxNumberOfDynamics, numDyn, minDyn, maxDyn); + maxNumberOfDynamics = numDyn; + num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; + num2DExpected = d.xyzDim[3] * num3DExpected; + } + float TRms = 1000.0f * (maxDynTime - minDynTime) / (float)(numDyn - 1); //-1 for fence post + if (fabs(TRms - d.TR) > 0.005f) + printWarning("Reported TR=%gms, measured TR=%gms (prospect. motion corr.?)\n", d.TR, TRms); + d.TR = TRms; + } + if ((isTypeWarning) && ((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0)) { + num2DExpected = numSlice2D; + } + if (((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0)) { + num2DExpected = d.xyzDim[3] * (int)(numSlice2D / d.xyzDim[3]); + if (!ADCwarning) + printWarning("More volumes than described in header (ADC or isotropic?)\n"); + } + if ((numSlice2D % num2DExpected) != 0) { + printMessage("Found %d slices, but expected divisible by %d: slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d %s\n", numSlice2D, num2DExpected, + d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, + maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels, parname); + d.isValid = false; + } + if (dynNotAscending) { + printWarning("PAR file volumes not saved in ascending temporal order (please check re-ordering)\n"); + } + if ((slice % d.xyzDim[3]) != 0) { + printError("Total number of slices (%d) not divisible by slices per 3D volume (%d) [acquisition aborted]. Try dicm2nii or R2AGUI: %s\n", slice, d.xyzDim[3], parname); + d.isValid = false; + return d; + } + d.xyzDim[4] = slice / d.xyzDim[3]; + d.locationsInAcquisition = d.xyzDim[3]; + if (ADCwarning) + printWarning("PAR/REC dataset includes derived (isotropic, ADC, etc) map(s) that could disrupt analysis. Please remove volume and ensure vectors are reported correctly\n"); + if (isIntenScaleVaries) + printWarning("Intensity slope/intercept varies between slices! [check resulting images]\n"); + if ((isVerbose) && (d.isValid)) { + printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n", + d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, + maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); + } + if ((d.xyzDim[3] > 1) && (minSlice == 1) && (maxSlice > minSlice)) { //issue 273 + float dx[4]; + dx[1] = (d.patientPosition[1] - d.patientPositionLast[1]); + dx[2] = (d.patientPosition[2] - d.patientPositionLast[2]); + dx[3] = (d.patientPosition[3] - d.patientPositionLast[3]); + //compute error using 3D pythagorean theorm + float sliceMM = sqrt(pow(dx[1], 2) + pow(dx[2], 2) + pow(dx[3], 2)); + sliceMM = sliceMM / (maxSlice - minSlice); + if (!(isSameFloatGE(sliceMM, d.xyzMM[3]))) { + //if (d.xyzMM[3] > 0.0) + printWarning("Distance between slices reported by slice gap+thick does not match estimate from slice positions (issue 273).\n"); + d.xyzMM[3] = sliceMM; + } + } //issue 273 + printMessage("Done reading PAR header version %.1f, with %d slices\n", (float)parVers / 10, numSlice2D); + //see Xiangrui Li 's dicm2nii (also BSD license) + // http://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter + // Rotation order and signs are figured out by trial and error, not 100% sure + float d2r = (float)(M_PI / 180.0); + vec3 ca = setVec3(cos(d.angulation[1] * d2r), cos(d.angulation[2] * d2r), cos(d.angulation[3] * d2r)); + vec3 sa = setVec3(sin(d.angulation[1] * d2r), sin(d.angulation[2] * d2r), sin(d.angulation[3] * d2r)); + mat33 rx, ry, rz; + LOAD_MAT33(rx, 1.0f, 0.0f, 0.0f, 0.0f, ca.v[0], -sa.v[0], 0.0f, sa.v[0], ca.v[0]); + LOAD_MAT33(ry, ca.v[1], 0.0f, sa.v[1], 0.0f, 1.0f, 0.0f, -sa.v[1], 0.0f, ca.v[1]); + LOAD_MAT33(rz, ca.v[2], -sa.v[2], 0.0f, sa.v[2], ca.v[2], 0.0f, 0.0f, 0.0f, 1.0f); + mat33 R = nifti_mat33_mul(rx, ry); + R = nifti_mat33_mul(R, rz); + ivec3 ixyz = setiVec3(1, 2, 3); + if (d.sliceOrient == kSliceOrientSag) { + ixyz = setiVec3(2, 3, 1); + for (int r = 0; r < 3; r++) + for (int c = 0; c < 3; c++) + if (c != 1) + R.m[r][c] = -R.m[r][c]; //invert first and final columns + } else if (d.sliceOrient == kSliceOrientCor) { + ixyz = setiVec3(1, 3, 2); + for (int r = 0; r < 3; r++) + R.m[r][2] = -R.m[r][2]; //invert rows of final column + } + R = nifti_mat33_reorder_cols(R, ixyz); //dicom rotation matrix + d.orient[1] = R.m[0][0]; + d.orient[2] = R.m[1][0]; + d.orient[3] = R.m[2][0]; + d.orient[4] = R.m[0][1]; + d.orient[5] = R.m[1][1]; + d.orient[6] = R.m[2][1]; + mat33 diag; + LOAD_MAT33(diag, d.xyzMM[1], 0.0f, 0.0f, 0.0f, d.xyzMM[2], 0.0f, 0.0f, 0.0f, d.xyzMM[3]); + R = nifti_mat33_mul(R, diag); + mat44 R44; + LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.stackOffcentre[1], + R.m[1][0], R.m[1][1], R.m[1][2], d.stackOffcentre[2], + R.m[2][0], R.m[2][1], R.m[2][2], d.stackOffcentre[3]); + vec3 x; + if (parVers > 40) //guess + x = setVec3(((float)d.xyzDim[1] - 1) / 2, ((float)d.xyzDim[2] - 1) / 2, ((float)d.xyzDim[3] - 1) / 2); + else + x = setVec3((float)d.xyzDim[1] / 2, (float)d.xyzDim[2] / 2, ((float)d.xyzDim[3] - 1) / 2); + mat44 eye; + LOAD_MAT44(eye, 1.0f, 0.0f, 0.0f, x.v[0], + 0.0f, 1.0f, 0.0f, x.v[1], + 0.0f, 0.0f, 1.0f, x.v[2]); + eye = nifti_mat44_inverse(eye); //we wish to compute R/eye, so compute invEye and calculate R*invEye + R44 = nifti_mat44_mul(R44, eye); + vec4 y; + y.v[0] = 0.0f; + y.v[1] = 0.0f; + y.v[2] = (float)d.xyzDim[3] - 1.0f; + y.v[3] = 1.0f; + y = nifti_vect44mat44_mul(y, R44); + int iOri = 2; //for axial, slices are 3rd dimenson (indexed from 0) (k) + if (d.sliceOrient == kSliceOrientSag) + iOri = 0; //for sagittal, slices are 1st dimension (i) + if (d.sliceOrient == kSliceOrientCor) + iOri = 1; //for coronal, slices are 2nd dimension (j) + if (d.xyzDim[3] > 1) { //detect and fix Philips Bug + //Est: assuming "image offcentre (ap,fh,rl in mm )" is correct + float stackOffcentreEst[4]; + stackOffcentreEst[1] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5; + stackOffcentreEst[2] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5; + stackOffcentreEst[3] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5; + //compute error using 3D pythagorean theorm + stackOffcentreEst[0] = sqrt(pow(stackOffcentreEst[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreEst[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreEst[3] - d.stackOffcentre[3], 2)); + //Est: assuming "image offcentre (ap,fh,rl in mm )" is stored in order rl,ap,fh + float stackOffcentreRev[4]; + stackOffcentreRev[1] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5; + stackOffcentreRev[2] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5; + stackOffcentreRev[3] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5; + //compute error using 3D pythagorean theorm + stackOffcentreRev[0] = sqrt(pow(stackOffcentreRev[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreRev[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreRev[3] - d.stackOffcentre[3], 2)); + //detect, report and fix error + if ((stackOffcentreEst[0] > 1.0) && (stackOffcentreRev[0] < stackOffcentreEst[0])) { + //error detected: the ">1.0" handles the low precision of the "Off Centre" values + printMessage("Order of 'image offcentre (ap,fh,rl in mm )' appears incorrect (assuming rl,ap,fh)\n"); + printMessage(" err[ap,fh,rl]= %g (%g %g %g) \n", stackOffcentreEst[0], stackOffcentreEst[1], stackOffcentreEst[2], stackOffcentreEst[3]); + printMessage(" err[rl,ap,fh]= %g (%g %g %g) \n", stackOffcentreRev[0], stackOffcentreRev[1], stackOffcentreRev[2], stackOffcentreRev[3]); + printMessage(" orient\t%d\tOffCentre 1st->mid->nth\t%g\t%g\t%g\t->\t%g\t%g\t%g\t->\t%g\t%g\t%g\t=\t%g\t%s\n", iOri, + d.patientPosition[1], d.patientPosition[2], d.patientPosition[3], + d.stackOffcentre[1], d.stackOffcentre[2], d.stackOffcentre[3], + d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3], (d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]), parname); + //correct patientPosition + for (int i = 1; i < 4; i++) + stackOffcentreRev[i] = d.patientPosition[i]; + d.patientPosition[1] = stackOffcentreRev[2]; + d.patientPosition[2] = stackOffcentreRev[3]; + d.patientPosition[3] = stackOffcentreRev[1]; + //correct patientPositionLast + for (int i = 1; i < 4; i++) + stackOffcentreRev[i] = d.patientPositionLast[i]; + d.patientPositionLast[1] = stackOffcentreRev[2]; + d.patientPositionLast[2] = stackOffcentreRev[3]; + d.patientPositionLast[3] = stackOffcentreRev[1]; + } //if bug: report and fix + } //if 3D data + bool flip = false; + //assume head first supine + if ((iOri == 0) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) > 0))) + flip = true; //6/2018 : TODO, not sure if this is >= or > + if ((iOri == 1) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0))) + flip = true; //<= not <, leslie_dti_6_1.PAR + if ((iOri == 2) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0))) + flip = true; //<= not <, see leslie_dti_3_1.PAR + if (flip) { + //if ((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) < 0) { + //if (( (y.v[iOri]-R44.m[iOri][3])>0 ) == ( (y.v[iOri]-d.stackOffcentre[iOri+1])>0 ) ) { + d.patientPosition[1] = R44.m[0][3]; + d.patientPosition[2] = R44.m[1][3]; + d.patientPosition[3] = R44.m[2][3]; + d.patientPositionLast[1] = y.v[0]; + d.patientPositionLast[2] = y.v[1]; + d.patientPositionLast[3] = y.v[2]; + //printWarning(" Flipping slice order: please verify %s\n", parname); + } else { + //printWarning(" NOT Flipping slice order: please verify %s\n", parname); + d.patientPosition[1] = y.v[0]; + d.patientPosition[2] = y.v[1]; + d.patientPosition[3] = y.v[2]; + d.patientPositionLast[1] = R44.m[0][3]; + d.patientPositionLast[2] = R44.m[1][3]; + d.patientPositionLast[3] = R44.m[2][3]; + } + //finish up + changeExt(parname, "REC"); +#ifndef _MSC_VER //Linux is case sensitive, #include + if (access(parname, F_OK) != 0) + changeExt(parname, "rec"); +#endif + d.locationsInAcquisition = d.xyzDim[3]; + d.imageStart = 0; + if (d.CSA.numDti >= kMaxDTI4D) { + printError("Unable to convert DTI [increase kMaxDTI4D] found %d directions\n", d.CSA.numDti); + printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); + d.CSA.numDti = 0; + }; + //check if dimensions vary + if (maxVol > 0) { //maxVol indexed from 0 + for (int i = 1; i <= maxVol; i++) { + //if (dti4D->gradDynVol[i] > d.maxGradDynVol) d.maxGradDynVol = dti4D->gradDynVol[i]; + //issue363 slope/intercept can vary for each 2D slice, not only between 3D volumes in a 4D time series + //if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleOrTEVaries = true; + //if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleOrTEVaries = true; + //if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleOrTEVaries = true; + if (dti4D->isPhase[i] != dti4D->isPhase[0]) + d.isScaleOrTEVaries = true; + if (dti4D->isReal[i] != dti4D->isReal[0]) + d.isScaleOrTEVaries = true; + if (dti4D->isImaginary[i] != dti4D->isImaginary[0]) + d.isScaleOrTEVaries = true; + if (dti4D->triggerDelayTime[i] != dti4D->triggerDelayTime[0]) + d.isScaleOrTEVaries = true; + } + //if (d.isScaleOrTEVaries) + // printWarning("Varying dimensions (echoes, phase maps, intensity scaling) will require volumes to be saved separately (hint: you may prefer dicm2nii output)\n"); + } + //if (d.CSA.numDti > 1) + // for (int i = 0; i < d.CSA.numDti; i++) + // printMessage("%d\tb=\t%g\tv=\t%g\t%g\t%g\n",i,dti4D->S[i].V[0],dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]); + //check DTI makes sense + if (d.CSA.numDti > 1) { + bool v1varies = false; + bool v2varies = false; + bool v3varies = false; + for (int i = 1; i < d.CSA.numDti; i++) { + if (dti4D->S[0].V[1] != dti4D->S[i].V[1]) + v1varies = true; + if (dti4D->S[0].V[2] != dti4D->S[i].V[2]) + v2varies = true; + if (dti4D->S[0].V[3] != dti4D->S[i].V[3]) + v3varies = true; + } + if ((!v1varies) || (!v2varies) || (!v3varies)) + printError("Bizarre b-vectors %s\n", parname); + } + if ((maxEcho > 1) || (maxCardiac > 1)) + printWarning("Multiple Echo (%d) or Cardiac (%d). Carefully inspect output\n", maxEcho, maxCardiac); + if ((maxEcho > 1) || (maxCardiac > 1)) + d.isScaleOrTEVaries = true; + return d; +} //nii_readParRec() + +size_t nii_SliceBytes(struct nifti_1_header hdr) { + //size of 2D slice + size_t imgsz = hdr.bitpix / 8; + for (int i = 1; i < 3; i++) + if (hdr.dim[i] > 1) + imgsz = imgsz * hdr.dim[i]; + return imgsz; +} //nii_SliceBytes() + +size_t nii_ImgBytes(struct nifti_1_header hdr) { + size_t imgsz = hdr.bitpix / 8; + for (int i = 1; i < 8; i++) + if (hdr.dim[i] > 1) + imgsz = imgsz * hdr.dim[i]; + return imgsz; +} //nii_ImgBytes() + +//unsigned char * nii_demosaic(unsigned char* inImg, struct nifti_1_header *hdr, int nMosaicSlices, int ProtocolSliceNumber1) { +unsigned char *nii_demosaic(unsigned char *inImg, struct nifti_1_header *hdr, int nMosaicSlices, bool isUIH) { + //demosaic http://nipy.org/nibabel/dicom/dicom_mosaic.html + if (nMosaicSlices < 2) + return inImg; + //Byte inImg[ [img length] ]; + //[img getBytes:&inImg length:[img length]]; + int nCol = (int)ceil(sqrt((double)nMosaicSlices)); + int nRow = nCol; + //n.b. Siemens store 20 images as 5x5 grid, UIH as 5rows, 4 Col https://github.com/rordenlab/dcm2niix/issues/225 + if (isUIH) + nRow = ceil((float)nMosaicSlices / (float)nCol); + //printf("%d = %dx%d\n", nMosaicSlices, nCol, nRow); + int colBytes = hdr->dim[1] / nCol * hdr->bitpix / 8; + int lineBytes = hdr->dim[1] * hdr->bitpix / 8; + int rowBytes = hdr->dim[1] * hdr->dim[2] / nRow * hdr->bitpix / 8; + int col = 0; + int row = 0; + int lOutPos = 0; + hdr->dim[1] = hdr->dim[1] / nCol; + hdr->dim[2] = hdr->dim[2] / nRow; + hdr->dim[3] = nMosaicSlices; + size_t imgsz = nii_ImgBytes(*hdr); + unsigned char *outImg = (unsigned char *)malloc(imgsz); + for (int m = 1; m <= nMosaicSlices; m++) { + int lPos = (row * rowBytes) + (col * colBytes); + for (int y = 0; y < hdr->dim[2]; y++) { + memcpy(&outImg[lOutPos], &inImg[lPos], colBytes); // dest, src, bytes + lPos += lineBytes; + lOutPos += colBytes; + } + col++; + if (col >= nCol) { + row++; + col = 0; + } //start new column + } //for m = each mosaic slice + free(inImg); + return outImg; +} // nii_demosaic() + +unsigned char *nii_flipImgY(unsigned char *bImg, struct nifti_1_header *hdr) { + //DICOM row order opposite from NIfTI + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + size_t lineBytes = hdr->dim[1] * hdr->bitpix / 8; + if ((hdr->datatype == DT_RGB24) && (hdr->bitpix == 24) && (hdr->intent_code == NIFTI_INTENT_NONE)) { + //we use the intent code to indicate planar vs triplet... + lineBytes = hdr->dim[1]; + dim3to7 = dim3to7 * 3; + } //rgb data saved planar (RRR..RGGGG..GBBB..B + unsigned char *line = (unsigned char *)malloc(sizeof(unsigned char) * (lineBytes)); + size_t sliceBytes = hdr->dim[2] * lineBytes; + int halfY = hdr->dim[2] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns + for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice + size_t slBottom = (size_t)sl * sliceBytes; + size_t slTop = (((size_t)sl + 1) * sliceBytes) - lineBytes; + for (int y = 0; y < halfY; y++) { + //swap order of lines + memcpy(line, &bImg[slBottom], lineBytes); //memcpy(&line, &bImg[slBottom], lineBytes); + memcpy(&bImg[slBottom], &bImg[slTop], lineBytes); + memcpy(&bImg[slTop], line, lineBytes); //tpx memcpy(&bImg[slTop], &line, lineBytes); + slTop -= lineBytes; + slBottom += lineBytes; + } //for y + } //for each slice + free(line); + return bImg; +} // nii_flipImgY() + +unsigned char *nii_flipImgZ(unsigned char *bImg, struct nifti_1_header *hdr) { + //DICOM row order opposite from NIfTI + int halfZ = hdr->dim[3] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns + if (halfZ < 1) + return bImg; + int dim4to7 = 1; + for (int i = 4; i < 8; i++) + if (hdr->dim[i] > 1) + dim4to7 = dim4to7 * hdr->dim[i]; + size_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8; + size_t volBytes = sliceBytes * hdr->dim[3]; + unsigned char *slice = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes)); + for (int vol = 0; vol < dim4to7; vol++) { //for each 2D slice + size_t slBottom = vol * volBytes; + size_t slTop = ((vol + 1) * volBytes) - sliceBytes; + for (int z = 0; z < halfZ; z++) { + //swap order of lines + memcpy(slice, &bImg[slBottom], sliceBytes); //TPX memcpy(&slice, &bImg[slBottom], sliceBytes); + memcpy(&bImg[slBottom], &bImg[slTop], sliceBytes); + memcpy(&bImg[slTop], slice, sliceBytes); //TPX + slTop -= sliceBytes; + slBottom += sliceBytes; + } //for Z + } //for each volume + free(slice); + return bImg; +} // nii_flipImgZ() + +unsigned char *nii_flipZ(unsigned char *bImg, struct nifti_1_header *h) { + //flip slice order + if (h->dim[3] < 2) + return bImg; + mat33 s; + mat44 Q44; + LOAD_MAT33(s, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_y[0], h->srow_y[1], h->srow_y[2], + h->srow_z[0], h->srow_z[1], h->srow_z[2]); + LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3], + h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3], + h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]); + vec4 v = setVec4(0.0f, 0.0f, (float)h->dim[3] - 1.0f); + v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin + mat33 mFlipZ; + LOAD_MAT33(mFlipZ, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f); + s = nifti_mat33_mul(s, mFlipZ); + LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0], + s.m[1][0], s.m[1][1], s.m[1][2], v.v[1], + s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]); + //printMessage(" ----------> %f %f %f\n",v.v[0],v.v[1],v.v[2]); + setQSForm(h, Q44, true); + //printMessage("nii_flipImgY dims %dx%dx%d %d \n",h->dim[1],h->dim[2], dim3to7,h->bitpix/8); + return nii_flipImgZ(bImg, h); +} // nii_flipZ() + +unsigned char *nii_flipY(unsigned char *bImg, struct nifti_1_header *h) { + mat33 s; + mat44 Q44; + LOAD_MAT33(s, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_y[0], h->srow_y[1], h->srow_y[2], + h->srow_z[0], h->srow_z[1], h->srow_z[2]); + LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3], + h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3], + h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]); + vec4 v = setVec4(0, (float)h->dim[2] - 1, 0); + v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin + mat33 mFlipY; + LOAD_MAT33(mFlipY, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f); + s = nifti_mat33_mul(s, mFlipY); + LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0], + s.m[1][0], s.m[1][1], s.m[1][2], v.v[1], + s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]); + setQSForm(h, Q44, true); + //printMessage("nii_flipImgY dims %dx%d %d \n",h->dim[1],h->dim[2], h->bitpix/8); + return nii_flipImgY(bImg, h); +} // nii_flipY() + +void conv12bit16bit(unsigned char *img, struct nifti_1_header hdr) { + //convert 12-bit allocated data to 16-bit + // works for MR-MONO2-12-angio-an1 from http://www.barre.nom.fr/medical/samples/ + // looks wrong: this sample toggles between big and little endian stores + printWarning("Support for images that allocate 12 bits is experimental\n"); + int nVox = (int)nii_ImgBytes(hdr) / (hdr.bitpix / 8); + for (int i = (nVox - 1); i >= 0; i--) { + int i16 = i * 2; + int i12 = floor(i * 1.5); + uint16_t val; + if ((i % 2) != 1) { + val = img[i12 + 1] + (img[i12 + 0] << 8); + val = val >> 4; + } else { + val = img[i12 + 0] + (img[i12 + 1] << 8); + } + img[i16 + 0] = val & 0xFF; + img[i16 + 1] = (val >> 8) & 0xFF; + } +} //conv12bit16bit() + +unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bitsAllocated, int imageStart32) { + size_t imgsz = nii_ImgBytes(hdr); + size_t imgszRead = imgsz; + size_t imageStart = imageStart32; + if (bitsAllocated == 12) + imgszRead = round(imgsz * 0.75); + FILE *file = fopen(imgname, "rb"); + if (!file) { + printError("Unable to open '%s'\n", imgname); + return NULL; + } + fseek(file, 0, SEEK_END); + long fileLen = ftell(file); + if (fileLen < (imgszRead + imageStart)) { + //note hdr.vox_offset is a float: issue507 + //https://www.nitrc.org/forum/message.php?msg_id=27155 + printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu+%zu) \n", fileLen, imgszRead, imageStart); + printWarning("File not large enough to store image data: %s\n", imgname); + return NULL; + } + fseek(file, (long)imageStart, SEEK_SET); + unsigned char *bImg = (unsigned char *)malloc(imgsz); + //int i = 0; + //while (bImg[i] == 0) i++; + //printMessage("%d %d<\n",i,bImg[i]); + size_t sz = fread(bImg, 1, imgszRead, file); + fclose(file); + if (sz < imgszRead) { + printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname); + return NULL; + } + if (bitsAllocated == 12) + conv12bit16bit(bImg, hdr); + return bImg; +} //nii_loadImgCore() + +unsigned char *nii_planar2rgb(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar) { + //DICOM data saved in triples RGBRGBRGB, NIfTI RGB saved in planes RRR..RGGG..GBBBB..B + if (bImg == NULL) + return NULL; + if (hdr->datatype != DT_RGB24) + return bImg; + if (isPlanar == 0) + return bImg; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int sliceBytes8 = hdr->dim[1] * hdr->dim[2]; + int sliceBytes24 = sliceBytes8 * 3; + unsigned char *slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); + int sliceOffsetRGB = 0; + int sliceOffsetR = 0; + int sliceOffsetG = sliceOffsetR + sliceBytes8; + int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8; + //printMessage("planar->rgb %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); + int i = 0; + for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice + memcpy(slice24, &bImg[sliceOffsetRGB], sliceBytes24); + for (int rgb = 0; rgb < sliceBytes8; rgb++) { + bImg[i++] = slice24[sliceOffsetR + rgb]; + bImg[i++] = slice24[sliceOffsetG + rgb]; + bImg[i++] = slice24[sliceOffsetB + rgb]; + } + sliceOffsetRGB += sliceBytes24; + } //for each slice + free(slice24); + return bImg; +} //nii_planar2rgb() + +unsigned char *nii_rgb2planar(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar) { + //DICOM data saved in triples RGBRGBRGB, Analyze RGB saved in planes RRR..RGGG..GBBBB..B + if (bImg == NULL) + return NULL; + if (hdr->datatype != DT_RGB24) + return bImg; + if (isPlanar == 1) + return bImg; //return nii_bgr2rgb(bImg,hdr); + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int sliceBytes8 = hdr->dim[1] * hdr->dim[2]; + int sliceBytes24 = sliceBytes8 * 3; + unsigned char *slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); + //printMessage("rgb->planar %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); + int sliceOffsetR = 0; + for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice + memcpy(slice24, &bImg[sliceOffsetR], sliceBytes24); //TPX memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24); + int sliceOffsetG = sliceOffsetR + sliceBytes8; + int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8; + int i = 0; + int j = 0; + for (int rgb = 0; rgb < sliceBytes8; rgb++) { + bImg[sliceOffsetR + j] = slice24[i++]; + bImg[sliceOffsetG + j] = slice24[i++]; + bImg[sliceOffsetB + j] = slice24[i++]; + j++; + } + sliceOffsetR += sliceBytes24; + } //for each slice + free(slice24); + return bImg; +} //nii_rgb2Planar() + +unsigned char *nii_iVaries(unsigned char *img, struct nifti_1_header *hdr, struct TDTI4D *dti4D) { + //each DICOM image can have its own intensity scaling, whereas NIfTI requires the same scaling for all images in a file + //WARNING: do this BEFORE nii_check16bitUnsigned!!!! + //if (hdr->datatype != DT_INT16) return img; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return img; + float *img32 = (float *)malloc(nVox * sizeof(float)); + if (hdr->datatype == DT_UINT8) { + uint8_t *img8i = (uint8_t *)img; + for (int i = 0; i < nVox; i++) + img32[i] = img8i[i]; + } else if (hdr->datatype == DT_UINT16) { + uint16_t *img16ui = (uint16_t *)img; + for (int i = 0; i < nVox; i++) + img32[i] = img16ui[i]; + } else if (hdr->datatype == DT_INT16) { + int16_t *img16i = (int16_t *)img; + for (int i = 0; i < nVox; i++) + img32[i] = img16i[i]; + } else if (hdr->datatype == DT_INT32) { + int32_t *img32i = (int32_t *)img; + for (int i = 0; i < nVox; i++) + img32[i] = (float)img32i[i]; + } + free(img); //release previous image + if ((dti4D != NULL) && (dti4D->intenScale[0] != 0.0)) { //enhanced dataset, intensity varies across slices of a single file + if (dti4D->RWVScale[0] != 0.0) + printWarning("Intensity scale/slope using 0028,1053 and 0028,1052"); //to do: real-world values and precise values + int dim1to2 = hdr->dim[1] * hdr->dim[2]; + int slice = -1; + //(0028,1052) SS = scale slope (2005,100E) RealWorldIntercept = (0040,9224) Real World Slope = (0040,9225) + //printf("vol\tRS(0028,1053)\tRI(0028,1052)\tSS(2005,100E)\trwS(0040,9225)\trwI(0040,9224)\n"); + for (int i = 0; i < nVox; i++) { //issue 363 + if ((i % dim1to2) == 0) { + slice++; + //printf("%d\t%g\t%g\t%g\t%g\t%g\n", slice, dti4D->intenScale[slice], dti4D->intenIntercept[slice],dti4D->intenScalePhilips[slice], dti4D->RWVScale[slice], dti4D->RWVIntercept[slice]); + } + img32[i] = (img32[i] * dti4D->intenScale[slice]) + dti4D->intenIntercept[slice]; + } + } else { // + for (int i = 0; i < nVox; i++) + img32[i] = (img32[i] * hdr->scl_slope) + hdr->scl_inter; + } + hdr->scl_slope = 1; + hdr->scl_inter = 0; + hdr->datatype = DT_FLOAT; + hdr->bitpix = 32; + return (unsigned char *)img32; +} //nii_iVaries() + +unsigned char *nii_reorderSlicesX(unsigned char *bImg, struct nifti_1_header *hdr, struct TDTI4D *dti4D) { + //Philips can save slices in any random order... rearrange all of them + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + if (dim3to7 < 2) + return bImg; + if (dim3to7 > kMaxSlice2D) + return bImg; + uint64_t imgSz = nii_ImgBytes(*hdr); + uint64_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8; + unsigned char *outImg = (unsigned char *)malloc(imgSz); + memcpy(&outImg[0], &bImg[0], imgSz); + for (int i = 0; i < dim3to7; i++) { //for each volume + int fromSlice = dti4D->sliceOrder[i]; + //if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice); + //printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", fromSlice, i); + if ((i < 0) || (fromSlice >= dim3to7)) { + printError("Re-ordered slice out-of-volume %d\n", fromSlice); + } else if (i != fromSlice) { + uint64_t inPos = fromSlice * sliceBytes; + uint64_t outPos = i * sliceBytes; + memcpy(&bImg[outPos], &outImg[inPos], sliceBytes); + } + } + free(outImg); + return bImg; +} + +unsigned char *nii_byteswap(unsigned char *img, struct nifti_1_header *hdr) { + if (hdr->bitpix < 9) + return img; + uint64_t nvox = nii_ImgBytes(*hdr) / (hdr->bitpix / 8); + void *ar = (void *)img; + if (hdr->bitpix == 16) + nifti_swap_2bytes(nvox, ar); + if (hdr->bitpix == 32) + nifti_swap_4bytes(nvox, ar); + if (hdr->bitpix == 64) + nifti_swap_8bytes(nvox, ar); + return img; +} //nii_byteswap() + +#ifdef myEnableJasper +unsigned char *nii_loadImgCoreJasper(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { + if (jas_init()) { + return NULL; + } + jas_stream_t *in; + jas_image_t *image; + jas_setdbglevel(0); + if (!(in = jas_stream_fopen(imgname, "rb"))) { + printError("Cannot open input image file %s\n", imgname); + return NULL; + } + //int isSeekable = jas_stream_isseekable(in); + jas_stream_seek(in, dcm.imageStart, 0); + int infmt = jas_image_getfmt(in); + if (infmt < 0) { + printError("Input image has unknown format %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); + return NULL; + } + char opt[] = "\0"; + char *inopts = opt; + if (!(image = jas_image_decode(in, infmt, inopts))) { + printError("Cannot decode image data %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); + return NULL; + } + int numcmpts; + int cmpts[4]; + switch (jas_clrspc_fam(jas_image_clrspc(image))) { + case JAS_CLRSPC_FAM_RGB: + if (jas_image_clrspc(image) != JAS_CLRSPC_SRGB) + printWarning("Inaccurate color\n"); + numcmpts = 3; + if ((cmpts[0] = jas_image_getcmptbytype(image, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R))) < 0 || + (cmpts[1] = jas_image_getcmptbytype(image, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G))) < 0 || + (cmpts[2] = jas_image_getcmptbytype(image, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B))) < 0) { + printError("Missing color component\n"); + return NULL; + } + break; + case JAS_CLRSPC_FAM_GRAY: + if (jas_image_clrspc(image) != JAS_CLRSPC_SGRAY) + printWarning("Inaccurate color\n"); + numcmpts = 1; + if ((cmpts[0] = jas_image_getcmptbytype(image, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y))) < 0) { + printError("Missing color component\n"); + return NULL; + } + break; + default: + printError("Unsupported color space\n"); + return NULL; + break; + } + int width = jas_image_cmptwidth(image, cmpts[0]); + int height = jas_image_cmptheight(image, cmpts[0]); + int prec = jas_image_cmptprec(image, cmpts[0]); + int sgnd = jas_image_cmptsgnd(image, cmpts[0]); +#ifdef MY_DEBUG + printMessage("offset %d w*h %d*%d bpp %d sgnd %d components %d '%s' Jasper=%s\n", dcm.imageStart, width, height, prec, sgnd, numcmpts, imgname, jas_getversion()); +#endif + for (int cmptno = 0; cmptno < numcmpts; ++cmptno) { + if (jas_image_cmptwidth(image, cmpts[cmptno]) != width || + jas_image_cmptheight(image, cmpts[cmptno]) != height || + jas_image_cmptprec(image, cmpts[cmptno]) != prec || + jas_image_cmptsgnd(image, cmpts[cmptno]) != sgnd || + jas_image_cmpthstep(image, cmpts[cmptno]) != jas_image_cmpthstep(image, 0) || + jas_image_cmptvstep(image, cmpts[cmptno]) != jas_image_cmptvstep(image, 0) || + jas_image_cmpttlx(image, cmpts[cmptno]) != jas_image_cmpttlx(image, 0) || + jas_image_cmpttly(image, cmpts[cmptno]) != jas_image_cmpttly(image, 0)) { + printMessage("The NIfTI format cannot be used to represent an image with this geometry.\n"); + return NULL; + } + } + //extract the data + int bpp = (prec + 7) >> 3; //e.g. 12 bits requires 2 bytes + int imgbytes = bpp * width * height * numcmpts; + if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { + printError("Catastrophic decompression error\n"); + return NULL; + } + jas_seqent_t v; + unsigned char *img = (unsigned char *)malloc(imgbytes); + uint16_t *img16ui = (uint16_t *)img; //unsigned 16-bit + int16_t *img16i = (int16_t *)img; //signed 16-bit + if (sgnd) + bpp = -bpp; + if (bpp == -1) { + printError("Signed 8-bit DICOM?\n"); + return NULL; + } + jas_matrix_t *data; + jas_seqent_t *d; + data = 0; + int cmptno, y, x; + int pix = 0; + for (cmptno = 0; cmptno < numcmpts; ++cmptno) { + if (!(data = jas_matrix_create(1, width))) { + free(img); + return NULL; + } + } + //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB + for (cmptno = 0; cmptno < numcmpts; ++cmptno) { + for (y = 0; y < height; ++y) { + if (jas_image_readcmpt(image, cmpts[cmptno], 0, y, width, 1, data)) { + free(img); + return NULL; + } + d = jas_matrix_getref(data, 0, 0); + for (x = 0; x < width; ++x) { + v = *d; + switch (bpp) { + case 1: + img[pix] = v; + break; + case 2: + img16ui[pix] = v; + break; + case -2: + img16i[pix] = v; + break; + } + pix++; + ++d; + } //for x + } //for y + } //for each component + jas_matrix_destroy(data); + jas_image_destroy(image); + jas_image_clearfmts(); + return img; +} //nii_loadImgCoreJasper() +#endif + +struct TJPEG { + long offset; + long size; +}; + +TJPEG *decode_JPEG_SOF_0XC3_stack(const char *fn, int skipBytes, int isVerbose, int frames, bool isLittleEndian) { +#define abortGoto() free(lOffsetRA); return NULL; + TJPEG *lOffsetRA = (TJPEG *)malloc(frames * sizeof(TJPEG)); + FILE *reader = fopen(fn, "rb"); + fseek(reader, 0, SEEK_END); + long lRawSz = ftell(reader) - skipBytes; + if (lRawSz <= 8) { + printError("Unable to open %s\n", fn); + abortGoto(); //read failure + } + fseek(reader, skipBytes, SEEK_SET); + unsigned char *lRawRA = (unsigned char *)malloc(lRawSz); + size_t lSz = fread(lRawRA, 1, lRawSz, reader); + fclose(reader); + if (lSz < (size_t)lRawSz) { + printError("Unable to read %s\n", fn); + abortGoto(); //read failure + } + long lRawPos = 0; //starting position + int frame = 0; + while ((frame < frames) && ((lRawPos + 10) < lRawSz)) { + int tag = dcmInt(4, &lRawRA[lRawPos], isLittleEndian); + lRawPos += 4; //read tag + int tagLength = dcmInt(4, &lRawRA[lRawPos], isLittleEndian); + long tagEnd = lRawPos + tagLength + 4; + if (isVerbose) + printMessage("Frame %d Tag %#x length %d end at %ld\n", frame + 1, tag, tagLength, tagEnd + skipBytes); + lRawPos += 4; //read tag length + if ((lRawRA[lRawPos] != 0xFF) || (lRawRA[lRawPos + 1] != 0xD8) || (lRawRA[lRawPos + 2] != 0xFF)) { + if (isVerbose) + printWarning("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn); + } else { + lOffsetRA[frame].offset = lRawPos + skipBytes; + lOffsetRA[frame].size = tagLength; + frame++; + } + lRawPos = tagEnd; + } + free(lRawRA); + if (frame < frames) { + printMessage("Only found %d of %d JPEG fragments. Please use dcmdjpeg or gdcmconv to uncompress data.\n", frame, frames); + abortGoto(); + } + return lOffsetRA; +} + +unsigned char *nii_loadImgJPEGC3(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int isVerbose) { + //arcane and inefficient lossless compression method popularized by dcmcjpeg, examples at http://www.osirix-viewer.com/resources/dicom-image-library/ + int dimX, dimY, bits, frames; + //clock_t start = clock(); + // https://github.com/rii-mango/JPEGLosslessDecoderJS/blob/master/tests/data/jpeg_lossless_sel1-8bit.dcm + //N.B. this current code can not extract a 2D image that is saved as multiple fragments, for example see the JPLL files at + // ftp://medical.nema.org/MEDICAL/Dicom/DataSets/WG04/ + //Live javascript code that can handle these is at + // https://github.com/chafey/cornerstoneWADOImageLoader + //I have never seen these segmented images in the wild, so we will simply warn the user if we encounter such a file + //int Sz = JPEG_SOF_0XC3_sz (imgname, (dcm.imageStart - 4), dcm.isLittleEndian); + //printMessage("Sz %d %d\n", Sz, dcm.imageBytes ); + //This behavior is legal but appears extremely rare + //ftp://medical.nema.org/medical/dicom/final/cp900_ft.pdf + if (65536 == dcm.imageBytes) + printError("One frame may span multiple fragments. SOFxC3 lossless JPEG. Please extract with dcmdjpeg or gdcmconv.\n"); + unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, dcm.imageStart, isVerbose, &dimX, &dimY, &bits, &frames, 0); + if (ret == NULL) { + printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n"); + return NULL; + } + if (hdr.dim[3] != frames) { //multi-slice image saved as multiple image fragments rather than a single image + //printMessage("Unable to decode all slices (%d/%d). Please use dcmdjpeg to uncompress data.\n", frames, hdr.dim[3]); + if (ret != NULL) + free(ret); + TJPEG *offsetRA = decode_JPEG_SOF_0XC3_stack(imgname, dcm.imageStart - 8, isVerbose, hdr.dim[3], dcm.isLittleEndian); + if (offsetRA == NULL) + return NULL; + size_t slicesz = nii_SliceBytes(hdr); + size_t imgsz = slicesz * hdr.dim[3]; + size_t pos = 0; + unsigned char *bImg = (unsigned char *)malloc(imgsz); + for (int frame = 0; frame < hdr.dim[3]; frame++) { + if (isVerbose) + printMessage("JPEG frame %d has %ld bytes @ %ld\n", frame, offsetRA[frame].size, offsetRA[frame].offset); + unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, (int)offsetRA[frame].offset, false, &dimX, &dimY, &bits, &frames, (int)offsetRA[frame].size); + if (ret == NULL) { + printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n"); + free(bImg); + return NULL; + } + memcpy(&bImg[pos], ret, slicesz); //dest, src, size + free(ret); + pos += slicesz; + } + free(offsetRA); + return bImg; + } + return ret; +} + +#ifndef F_OK +#define F_OK 0 /* existence check */ +#endif + +#ifndef myDisableClassicJPEG + +#ifdef myTurboJPEG //if turboJPEG instead of nanoJPEG for classic JPEG decompression + +//unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { +unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) { + //decode classic JPEG using nanoJPEG + //printMessage("50 offset %d\n", dcm.imageStart); + if ((dcm.samplesPerPixel != 1) && (dcm.samplesPerPixel != 3)) { + printError("%d components (expected 1 or 3) in a JPEG image '%s'\n", dcm.samplesPerPixel, imgname); + return NULL; + } + if (access(imgname, F_OK) == -1) { + printError("Unable to find '%s'\n", imgname); + return NULL; + } + //load compressed data + FILE *f = fopen(imgname, "rb"); + fseek(f, 0, SEEK_END); + long unsigned int _jpegSize = (long unsigned int)ftell(f); + _jpegSize = _jpegSize - dcm.imageStart; + if (_jpegSize < 8) { + printError("File too small\n"); + fclose(f); + return NULL; + } + unsigned char *_compressedImage = (unsigned char *)malloc(_jpegSize); + fseek(f, dcm.imageStart, SEEK_SET); + _jpegSize = (long unsigned int)fread(_compressedImage, 1, _jpegSize, f); + fclose(f); + int jpegSubsamp, width, height; + //printMessage("Decoding with turboJPEG\n"); + tjhandle _jpegDecompressor = tjInitDecompress(); + tjDecompressHeader2(_jpegDecompressor, _compressedImage, _jpegSize, &width, &height, &jpegSubsamp); + int COLOR_COMPONENTS = dcm.samplesPerPixel; + //printMessage("turboJPEG h*w %d*%d sampling %d components %d\n", width, height, jpegSubsamp, COLOR_COMPONENTS); + if ((jpegSubsamp == TJSAMP_GRAY) && (COLOR_COMPONENTS != 1)) { + printError("Grayscale jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); + } + if ((jpegSubsamp != TJSAMP_GRAY) && (COLOR_COMPONENTS != 3)) { + printError("Color jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); + } + //unsigned char bImg[width*height*COLOR_COMPONENTS]; //!< will contain the decompressed image + unsigned char *bImg = (unsigned char *)malloc(width * height * COLOR_COMPONENTS); + if (COLOR_COMPONENTS == 1) //TJPF_GRAY + tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_GRAY, TJFLAG_FASTDCT); + else + tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_RGB, TJFLAG_FASTDCT); + //printMessage("turboJPEG h*w %d*%d (sampling %d)\n", width, height, jpegSubsamp); + tjDestroy(_jpegDecompressor); + return bImg; +} + +#else //if turboJPEG else use nanojpeg... + +//unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { +unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) { + //decode classic JPEG using nanoJPEG + //printMessage("50 offset %d\n", dcm.imageStart); + if (access(imgname, F_OK) == -1) { + printError("Unable to find '%s'\n", imgname); + return NULL; + } + //load compressed data + FILE *f = fopen(imgname, "rb"); + fseek(f, 0, SEEK_END); + int size = (int)ftell(f); + size = size - dcm.imageStart; + if (size < 8) { + printError("File too small '%s'\n", imgname); + fclose(f); + return NULL; + } + char *buf = (char *)malloc(size); + fseek(f, dcm.imageStart, SEEK_SET); + size = (int)fread(buf, 1, size, f); + fclose(f); + //decode + njInit(); + if (njDecode(buf, size)) { + printError("Unable to decode JPEG image.\n"); + return NULL; + } + free(buf); + unsigned char *bImg = (unsigned char *)malloc(njGetImageSize()); + memcpy(bImg, njGetImage(), njGetImageSize()); //dest, src, size + njDone(); + return bImg; +} +#endif +#endif + +uint32_t rleInt(int lIndex, unsigned char lBuffer[], bool swap) { //read binary 32-bit integer + uint32_t retVal = 0; + memcpy(&retVal, (char *)&lBuffer[lIndex * 4], 4); + if (!swap) + return retVal; + uint32_t swapVal; + char *inInt = (char *)&retVal; + char *outInt = (char *)&swapVal; + outInt[0] = inInt[3]; + outInt[1] = inInt[2]; + outInt[2] = inInt[1]; + outInt[3] = inInt[0]; + return swapVal; +} //rleInt() + +unsigned char *nii_loadImgPMSCT_RLE1(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { + //Transfer Syntax 1.3.46.670589.33.1.4.1 also handled by TomoVision and GDCM's rle2img + //https://github.com/malaterre/GDCM/blob/a923f206060e85e8d81add565ae1b9dd7b210481/Examples/Cxx/rle2img.cxx + //see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1 + if (dcm.imageBytes < 66) { //64 for header+ 2 byte minimum image + printError("%d is not enough bytes for PMSCT_RLE1 compression '%s'\n", dcm.imageBytes, imgname); + return NULL; + } + int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8); + if (bytesPerSample != 2) { //there is an RGB variation of this format, but we have not seen it in the wild + printError("PMSCT_RLE1 should be 16-bits per sample (please report on Github and use pmsct_rgb1).\n"); + return NULL; + } + FILE *file = fopen(imgname, "rb"); + if (!file) { + printError("Unable to open %s\n", imgname); + return NULL; + } + fseek(file, 0, SEEK_END); + long fileLen = ftell(file); + if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) { + printMessage("File not large enough to store image data: %s\n", imgname); + fclose(file); + return NULL; + } + fseek(file, (long)dcm.imageStart, SEEK_SET); + size_t imgsz = nii_ImgBytes(hdr); + char *cImg = (char *)malloc(dcm.imageBytes); //compressed input + size_t sz = fread(cImg, 1, dcm.imageBytes, file); + fclose(file); + if (sz < (size_t)dcm.imageBytes) { + printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); + free(cImg); + return NULL; + } + if (imgsz == dcm.imageBytes) { // Handle special case that data is not compressed: + return (unsigned char *)cImg; + } + unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output + // RLE pass: compressed -> temp (bImg -> tImg) + char *tImg = (char *)malloc(imgsz); //temp output + int o = 0; + for (size_t i = 0; i < dcm.imageBytes; ++i) { + if (cImg[i] == (char)0xa5) { + int repeat = (unsigned char)cImg[i + 1] + 1; + char value = cImg[i + 2]; + while (repeat) { + tImg[o] = value; + o++; + --repeat; + } + i += 2; + } else { + tImg[o] = cImg[i]; + o++; + } + } //for i + free(cImg); + int tempsize = o; + //Looks like this RLE is pretty ineffective... + // printMessage("RLE %d -> %d\n", dcm.imageBytes, o); + //Delta encoding pass: temp -> output (tImg -> bImg) + unsigned short delta = 0; + o = 0; + int n16 = (int)imgsz >> 1; + unsigned short *bImg16 = (unsigned short *)bImg; + for (size_t i = 0; i < tempsize; ++i) { + if (tImg[i] == (unsigned char)0x5a) { + unsigned char v1 = (unsigned char)tImg[i + 1]; + unsigned char v2 = (unsigned char)tImg[i + 2]; + unsigned short value = (unsigned short)(v2 * 256 + v1); + if (o < n16) + bImg16[o] = value; + o++; + delta = value; + i += 2; + } else { + unsigned short value = (unsigned short)(tImg[i] + delta); + if (o < n16) + bImg16[o] = value; + o++; + delta = value; + } + } //for i + //printMessage("Delta %d -> %d (of %d)\n", tempsize, 2*(o-1), imgsz); + free(tImg); + return bImg; +} // nii_loadImgPMSCT_RLE1() + +unsigned char *nii_loadImgRLE(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { + //decompress PackBits run-length encoding https://en.wikipedia.org/wiki/PackBits + if (dcm.imageBytes < 66) { //64 for header+ 2 byte minimum image + printError("%d is not enough bytes for RLE compression '%s'\n", dcm.imageBytes, imgname); + return NULL; + } + FILE *file = fopen(imgname, "rb"); + if (!file) { + printError("Unable to open %s\n", imgname); + return NULL; + } + fseek(file, 0, SEEK_END); + long fileLen = ftell(file); + if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) { + printMessage("File not large enough to store image data: %s\n", imgname); + fclose(file); + return NULL; + } + fseek(file, (long)dcm.imageStart, SEEK_SET); + size_t imgsz = nii_ImgBytes(hdr); + unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input + size_t sz = fread(cImg, 1, dcm.imageBytes, file); + fclose(file); + if (sz < (size_t)dcm.imageBytes) { + printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); + free(cImg); + return NULL; + } + //read header http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html + bool swap = (dcm.isLittleEndian != littleEndianPlatform()); + int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8); + uint32_t bytesPerSampleRLE = rleInt(0, cImg, swap); + if ((bytesPerSample < 0) || (bytesPerSampleRLE != (uint32_t)bytesPerSample)) { + printError("RLE header corrupted %d != %d\n", bytesPerSampleRLE, bytesPerSample); + free(cImg); + return NULL; + } + unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output + for (size_t i = 0; i < imgsz; i++) + bImg[i] = 0; + for (int i = 0; i < bytesPerSample; i++) { + uint32_t offset = rleInt(i + 1, cImg, swap); + if ((dcm.imageBytes < 0) || (offset > (uint32_t)dcm.imageBytes)) { + printError("RLE header error\n"); + free(cImg); + free(bImg); + return NULL; + } + //save in platform's endian: + // The first Segment is generated by stripping off the most significant byte of each Padded Composite Pixel Code... + size_t vx = i; + if ((dcm.samplesPerPixel == 1) && (littleEndianPlatform())) //endian, except for RGB + vx = (bytesPerSample - 1) - i; + while (vx < imgsz) { + int8_t n = (int8_t)cImg[offset]; + offset++; + //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html + //if ((n >= 0) && (n <= 127)) { //not needed: int8_t always <=127 + if (n >= 0) { //literal bytes + int reps = 1 + (int)n; + for (int r = 0; r < reps; r++) { + int8_t v = cImg[offset]; + offset++; + if (vx >= imgsz) + ; //printMessage("literal overflow %d %d\n", r, reps); + else + bImg[vx] = v; + vx = vx + bytesPerSample; + } + } else if ((n <= -1) && (n >= -127)) { //repeated run + int8_t v = cImg[offset]; + offset++; + int reps = -(int)n + 1; + for (int r = 0; r < reps; r++) { + if (vx >= imgsz) + ; //printMessage("repeat overflow %d\n", reps); + else + bImg[vx] = v; + vx = vx + bytesPerSample; + } + }; //n.b. we ignore -128! + } //while vx < imgsz + } //for i < bytesPerSample + free(cImg); + return bImg; +} // nii_loadImgRLE() + +#ifdef myDisableOpenJPEG +#ifndef myEnableJasper +//avoid compiler warning, see https://stackoverflow.com/questions/3599160/unused-parameter-warnings-in-c +#define UNUSED(x) (void)(x) +#endif +#endif + +#if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) //Support for JPEG-LS +//JPEG-LS: Transfer Syntaxes 1.2.840.10008.1.2.4.80 1.2.840.10008.1.2.4.81 + +#ifdef myEnableJPEGLS1 //use CharLS v1.* requires c++03 +//-std=c++03 -DmyEnableJPEGLS1 charls1/header.cpp charls1/jpegls.cpp charls1/jpegmarkersegment.cpp charls1/interface.cpp charls1/jpegstreamwriter.cpp +#include "charls1/interface.h" +#else //use latest release of CharLS: CharLS 2.x requires c++14 +//-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp +#include "charls/charls.h" +#endif +#include "charls/publictypes.h" + +unsigned char *nii_loadImgJPEGLS(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { + //load compressed data + FILE *file = fopen(imgname, "rb"); + if (!file) { + printError("Unable to open %s\n", imgname); + return NULL; + } + fseek(file, 0, SEEK_END); + long fileLen = ftell(file); + if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) { + printMessage("File not large enough to store JPEG-LS data: %s\n", imgname); + fclose(file); + return NULL; + } + fseek(file, (long)dcm.imageStart, SEEK_SET); + unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input + size_t sz = fread(cImg, 1, dcm.imageBytes, file); + fclose(file); + if (sz < (size_t)dcm.imageBytes) { + printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); + free(cImg); + return NULL; + } + //create buffer for uncompressed data + size_t imgsz = nii_ImgBytes(hdr); + unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output + JlsParameters params = {}; +#ifdef myEnableJPEGLS1 + if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms) != OK) { +#else + using namespace charls; + if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { +#endif + printMessage("CharLS failed to read header.\n"); + return NULL; + } +#ifdef myEnableJPEGLS1 + if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms) != OK) { +#else + if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { +#endif + free(bImg); + printMessage("CharLS failed to read image.\n"); + return NULL; + } + return (bImg); +} +#endif + +unsigned char *nii_loadImgXL(char *imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D) { + //provided with a filename (imgname) and DICOM header (dcm), creates NIfTI header (hdr) and img + if (headerDcm2Nii(dcm, hdr, true) == EXIT_FAILURE) + return NULL; //TOFU + unsigned char *img; + if (dcm.compressionScheme == kCompress50) { +#ifdef myDisableClassicJPEG + printMessage("Software not compiled to decompress classic JPEG DICOM images\n"); + return NULL; +#else + //img = nii_loadImgJPEG50(imgname, *hdr, dcm); + img = nii_loadImgJPEG50(imgname, dcm); + if (hdr->datatype == DT_RGB24) //convert to planar + img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped +#endif + } else if (dcm.compressionScheme == kCompressJPEGLS) { +#if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) + img = nii_loadImgJPEGLS(imgname, *hdr, dcm); + if (hdr->datatype == DT_RGB24) //convert to planar + img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped +#else + printMessage("Software not compiled to decompress JPEG-LS DICOM images\n"); + return NULL; +#endif + } else if (dcm.compressionScheme == kCompressPMSCT_RLE1) { + img = nii_loadImgPMSCT_RLE1(imgname, *hdr, dcm); + } else if (dcm.compressionScheme == kCompressRLE) { + img = nii_loadImgRLE(imgname, *hdr, dcm); + if (hdr->datatype == DT_RGB24) //convert to planar + img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped + } else if (dcm.compressionScheme == kCompressC3) + img = nii_loadImgJPEGC3(imgname, *hdr, dcm, isVerbose); + else +#ifndef myDisableOpenJPEG + if (((dcm.compressionScheme == kCompress50) || (dcm.compressionScheme == kCompressYes)) && (compressFlag != kCompressNone)) + img = nii_loadImgCoreOpenJPEG(imgname, *hdr, dcm, compressFlag); + else +#else +#ifdef myEnableJasper + if ((dcm.compressionScheme == kCompressYes) && (compressFlag != kCompressNone)) + img = nii_loadImgCoreJasper(imgname, *hdr, dcm, compressFlag); + else +#endif +#endif + if (dcm.compressionScheme == kCompressYes) { + printMessage("Software not set up to decompress DICOM\n"); + return NULL; + } else + img = nii_loadImgCore(imgname, *hdr, dcm.bitsAllocated, dcm.imageStart); + if (img == NULL) + return img; + if ((dcm.compressionScheme == kCompressNone) && (dcm.isLittleEndian != littleEndianPlatform()) && (hdr->bitpix > 8)) + img = nii_byteswap(img, hdr); + if ((dcm.compressionScheme == kCompressNone) && (hdr->datatype == DT_RGB24)) + img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped + dcm.isPlanarRGB = true; + if (dcm.CSA.mosaicSlices > 1) { + img = nii_demosaic(img, hdr, dcm.CSA.mosaicSlices, (dcm.manufacturer == kMANUFACTURER_UIH)); //, dcm.CSA.protocolSliceNumber1); + } + if ((dti4D == NULL) && (!dcm.isFloat) && (iVaries)) //must do afte + img = nii_iVaries(img, hdr, NULL); + int nAcq = dcm.locationsInAcquisition; + if ((nAcq > 1) && (hdr->dim[0] < 4) && ((hdr->dim[3] % nAcq) == 0) && (hdr->dim[3] > nAcq)) { + hdr->dim[4] = hdr->dim[3] / nAcq; + hdr->dim[3] = nAcq; + hdr->dim[0] = 4; + } + if ((dti4D != NULL) && (dti4D->sliceOrder[0] >= 0)) + img = nii_reorderSlicesX(img, hdr, dti4D); + if ((dti4D != NULL) && (!dcm.isFloat) && (iVaries)) + img = nii_iVaries(img, hdr, dti4D); + headerDcm2NiiSForm(dcm, dcm, hdr, false); + return img; +} //nii_loadImgXL() + +int isSQ(uint32_t groupElement) { //Detect sequence VR ("SQ") for implicit tags + static const int array_size = 35; + uint32_t array[array_size] = {0x2005 + (uint32_t(0x140F) << 16), 0x0008 + (uint32_t(0x1111) << 16), 0x0008 + (uint32_t(0x1115) << 16), 0x0008 + (uint32_t(0x1140) << 16), 0x0008 + (uint32_t(0x1199) << 16), 0x0008 + (uint32_t(0x2218) << 16), 0x0008 + (uint32_t(0x9092) << 16), 0x0018 + (uint32_t(0x9006) << 16), 0x0018 + (uint32_t(0x9042) << 16), 0x0018 + (uint32_t(0x9045) << 16), 0x0018 + (uint32_t(0x9049) << 16), 0x0018 + (uint32_t(0x9112) << 16), 0x0018 + (uint32_t(0x9114) << 16), 0x0018 + (uint32_t(0x9115) << 16), 0x0018 + (uint32_t(0x9117) << 16), 0x0018 + (uint32_t(0x9119) << 16), 0x0018 + (uint32_t(0x9125) << 16), 0x0018 + (uint32_t(0x9152) << 16), 0x0018 + (uint32_t(0x9176) << 16), 0x0018 + (uint32_t(0x9226) << 16), 0x0018 + (uint32_t(0x9239) << 16), 0x0020 + (uint32_t(0x9071) << 16), 0x0020 + (uint32_t(0x9111) << 16), 0x0020 + (uint32_t(0x9113) << 16), 0x0020 + (uint32_t(0x9116) << 16), 0x0020 + (uint32_t(0x9221) << 16), 0x0020 + (uint32_t(0x9222) << 16), 0x0028 + (uint32_t(0x9110) << 16), 0x0028 + (uint32_t(0x9132) << 16), 0x0028 + (uint32_t(0x9145) << 16), 0x0040 + (uint32_t(0x0260) << 16), 0x0040 + (uint32_t(0x0555) << 16), 0x0040 + (uint32_t(0xa170) << 16), 0x5200 + (uint32_t(0x9229) << 16), 0x5200 + (uint32_t(0x9230) << 16)}; + for (int i = 0; i < array_size; i++) { + //if (array[i] == groupElement) printMessage(" implicitSQ %04x,%04x\n", groupElement & 65535,groupElement>>16); + if (array[i] == groupElement) + return 1; + } + return 0; +} //isSQ() + +int isDICOMfile(const char *fname) { //0=NotDICOM, 1=DICOM, 2=Maybe(not Part 10 compliant) + //Someday: it might be worthwhile to detect "IMGF" at offset 3228 to warn user if they attempt to convert Signa data + FILE *fp = fopen(fname, "rb"); + if (!fp) + return 0; + fseek(fp, 0, SEEK_END); + long fileLen = ftell(fp); + if (fileLen < 256) { + fclose(fp); + return 0; + } + fseek(fp, 0, SEEK_SET); + unsigned char buffer[256]; + size_t sz = fread(buffer, 1, 256, fp); + fclose(fp); + if (sz < 256) + return 0; + if ((buffer[128] == 'D') && (buffer[129] == 'I') && (buffer[130] == 'C') && (buffer[131] == 'M')) + return 1; //valid DICOM + if ((buffer[0] == 8) && (buffer[1] == 0) && (buffer[3] == 0)) + return 2; //not valid Part 10 file, perhaps DICOM object + return 0; +} //isDICOMfile() + +//START RIR 12/2017 Robert I. Reid + +// Gathering spot for all the info needed to get the b value and direction +// for a volume. +struct TVolumeDiffusion { + struct TDICOMdata *pdd; // The multivolume + struct TDTI4D *pdti4D; // permanent records. + uint8_t manufacturer; // kMANUFACTURER_UNKNOWN, kMANUFACTURER_SIEMENS, etc. + + //void set_manufacturer(const uint8_t m) {manufacturer = m; update();} // unnecessary + + // Everything after this in the structure would be private if it were a C++ + // class, but it has been rewritten as a struct for C compatibility. I am + // using _ as a hint of that, although _ for privacy is not really a + // universal convention in C. Privacy is desired because immediately + // any of these are updated _update_tvd() should be called. + + bool _isAtFirstPatientPosition; // Limit b vals and vecs to 1 per volume. + + //float bVal0018_9087; // kDiffusion_b_value, always present in Philips/Siemens. + //float bVal2001_1003; // kDiffusionBFactor + // float dirRL2005_10b0; // kDiffusionDirectionRL + // float dirAP2005_10b1; // kDiffusionDirectionAP + // float dirFH2005_10b2; // kDiffusionDirectionFH + // Philips diffusion scans tend to have a "trace" (average of the diffusion + // weighted volumes) volume tacked on, usually but not always at the end, + // so b is > 0, but the direction is meaningless. Most software versions + // explicitly set the direction to 0, but version 3 does not, making (0x18, + // 0x9075) necessary. + bool _isPhilipsNonDirectional; + //char _directionality0018_9075[16]; // DiffusionDirectionality, not in Philips 2.6. + // float _orientation0018_9089[3]; // kDiffusionOrientation, always present in Philips/Siemens for volumes with a direction. + //char _seq0018_9117[64]; // MRDiffusionSequence, not in Philips 2.6. + float _dtiV[4]; + double _symBMatrix[6]; + //uint16_t numDti; +}; +struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D); +void clear_volume(struct TVolumeDiffusion *ptvd); // Blank the volume-specific members or set them to impossible values. +void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf); +void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian); +void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, bool iafpp); +int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf); +void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis); +void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, int axis); +void set_bVal(struct TVolumeDiffusion *ptvd, float b); +void set_bMatrix(struct TVolumeDiffusion *ptvd, float b, int component); +void _update_tvd(struct TVolumeDiffusion *ptvd); + +struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D) { + struct TVolumeDiffusion tvd; + tvd.pdd = ptdd; + tvd.pdti4D = dti4D; + clear_volume(&tvd); + return tvd; +} //initTVolumeDiffusion() + +void clear_volume(struct TVolumeDiffusion *ptvd) { + ptvd->_isAtFirstPatientPosition = false; + ptvd->manufacturer = kMANUFACTURER_UNKNOWN; + //bVal0018_9087 = -1; + //ptvd->_directionality0018_9075[0] = 0; + //ptvd->seq0018_9117[0] = 0; + //bVal2001_1003 = -1; + // dirRL2005_10b0 = 2; + // dirAP2005_10b1 = 2; + // dirFH2005_10b2 = 2; + ptvd->_isPhilipsNonDirectional = false; + ptvd->_dtiV[0] = -1; + for (int i = 1; i < 4; ++i) + ptvd->_dtiV[i] = 2; + for (int i = 1; i < 6; ++i) + ptvd->_symBMatrix[i] = NAN; + //numDti = 0; +} //clear_volume() + +void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf) { + //if(!strncmp(( char*)(inbuf), "BMATRIX", 4)) printf("FOUND BMATRIX----%s\n",inbuf ); + //n.b. strncmp returns 0 if the contents of both strings are equal, for boolean 0 = false! + // elsewhere we use strstr() which returns 0/null if match is not present + if (strncmp((char *)(inbuf), "DIRECTIONAL", 11) && // strncmp = 0 for ==. + //strncmp(( char*)(inbuf), "NONE", 4) && //issue 256 + strncmp((char *)(inbuf), "BMATRIX", 7)) { // Siemens XA10 + ptvd->_isPhilipsNonDirectional = true; + // Explicitly set the direction to 0 now, because there may + // not be a 0018,9089 for this frame. + for (int i = 1; i < 4; ++i) // 1-3 is intentional. + ptvd->_dtiV[i] = 0.0; + } else { + ptvd->_isPhilipsNonDirectional = false; + // Wait for 0018,9089 to get the direction. + } + _update_tvd(ptvd); +} //set_directionality0018_9075() + +int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf) { + //see Series 16 https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/ge-mr750-dwi-b-vals#table b750 = 1000000750\8\0\0 b1500 = 1000001500\8\0\0 + int bVal = dcmStrInt(lLength, inbuf); + bVal = (bVal % 10000); + ptvd->_dtiV[0] = bVal; + //printf("(0043,1039) '%s' Slop_int_6 -->%d \n", inbuf, bVal); + //dd.CSA.numDti = 1; // Always true for GE. + _update_tvd(ptvd); + return bVal; +} //set_bValGE() + +// axis: 0 -> x, 1 -> y , 2 -> z +void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis) { + ptvd->_dtiV[axis + 1] = vec; + //printf("(2005,10b0..2) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]); + _update_tvd(ptvd); +} //set_diffusion_directionPhilips() + +// axis: 0 -> x, 1 -> y , 2 -> z +void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, const int axis) { + ptvd->_dtiV[axis + 1] = dcmStrFloat(lLength, inbuf); + //printf("(0019,10bb..d) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]); + _update_tvd(ptvd); +} //set_diffusion_directionGE() + +void dcmMultiFloatDouble(size_t lByteLength, unsigned char lBuffer[], size_t lnFloats, float *lFloats, bool isLittleEndian) { + size_t floatlen = lByteLength / lnFloats; + for (size_t i = 0; i < lnFloats; ++i) + lFloats[i] = dcmFloatDouble((int)floatlen, lBuffer + i * floatlen, isLittleEndian); +} //dcmMultiFloatDouble() + +void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian) { + if (ptvd->_isPhilipsNonDirectional) { + for (int i = 1; i < 4; ++i) // Deliberately ignore inbuf; it might be nonsense. + ptvd->_dtiV[i] = 0.0; + } else + dcmMultiFloatDouble(lLength, inbuf, 3, ptvd->_dtiV + 1, isLittleEndian); + _update_tvd(ptvd); +} //set_orientation0018_9089() + +void set_bVal(struct TVolumeDiffusion *ptvd, const float b) { + ptvd->_dtiV[0] = b; + _update_tvd(ptvd); +} //set_bVal() + +void set_bMatrix(struct TVolumeDiffusion *ptvd, double b, int idx) { + if ((idx < 0) || (idx > 5)) + return; + ptvd->_symBMatrix[idx] = b; + _update_tvd(ptvd); +} + +void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, const bool iafpp) { + ptvd->_isAtFirstPatientPosition = iafpp; + _update_tvd(ptvd); +} //set_isAtFirstPatientPosition_tvd() + +// Update the diffusion info in dd and *pdti4D for a volume once all the +// diffusion info for that volume has been read into pvd. +// +// Note that depending on the scanner software the diffusion info can arrive in +// different tags, in different orders (because of enclosing sequence tags), +// and the values in some tags may be invalid, or may be essential, depending +// on the presence of other tags. Thus it is best to gather all the diffusion +// info for a volume (frame) before taking action on it. +// +// On the other hand, dd and *pdti4D need to be updated as soon as the +// diffusion info is ready, before diffusion info for the next volume is read +// in. +void _update_tvd(struct TVolumeDiffusion *ptvd) { + // Figure out if we have both the b value and direction (if any) for this + // volume, and if isFirstPosition. + // // GE (software version 27) is liable to NOT include kDiffusion_b_value for the + // // slice if it is 0, but should still have kDiffusionBFactor, which comes + // // after PatientPosition. + // if(isAtFirstPatientPosition && manufacturer == kMANUFACTURER_GE && dtiV[0] < 0) + // dtiV[0] = 0; // Implied 0. + bool isReady = (ptvd->_isAtFirstPatientPosition && (ptvd->_dtiV[0] >= 0)); + if (!isReady) + return; //no B=0 + if (isReady) { + for (int i = 1; i < 4; ++i) { + if (ptvd->_dtiV[i] > 1) { + isReady = false; + break; + } + } + } + if (!isReady) { //bvecs NOT filled: see if symBMatrix filled + isReady = true; + for (int i = 1; i < 6; ++i) + if (isnan(ptvd->_symBMatrix[i])) + isReady = false; + if (!isReady) + return; // symBMatrix not filled + //START BRUKER KLUDGE + //see issue 265: Bruker stores xx,xy,xz,yx,yy,yz instead of xx,xy,xz,yy,yz,zz + // we can recover since xx+yy+zz = bval + // since any value squared is positive, a negative diagonal reveals fault + double xx = ptvd->_symBMatrix[0]; //x*x + double xy = ptvd->_symBMatrix[1]; //x*y + double xz = ptvd->_symBMatrix[2]; //x*z + double yy = ptvd->_symBMatrix[3]; //y*y + double yz = ptvd->_symBMatrix[4]; //y*z + double zz = ptvd->_symBMatrix[5]; //z*z + bool isBrukerBug = false; + if ((xx < 0.0) || (yy < 0.0) || (zz < 0.0)) + isBrukerBug = true; + double sumDiag = ptvd->_symBMatrix[0] + ptvd->_symBMatrix[3] + ptvd->_symBMatrix[5]; //if correct xx+yy+zz = bval + double bVecError = fabs(sumDiag - ptvd->pdd->CSA.dtiV[0]); + if (bVecError > 0.5) + isBrukerBug = true; + //next: check diagonals + double x = sqrt(xx); + double y = sqrt(yy); + double z = sqrt(zz); + if ((fabs((x * y) - xy)) > 0.5) + isBrukerBug = true; + if ((fabs((x * z) - xz)) > 0.5) + isBrukerBug = true; + if ((fabs((y * z) - yz)) > 0.5) + isBrukerBug = true; + if (isBrukerBug) + printWarning("Fixing corrupt bmat (issue 265). [%g %g %g %g %g %g]\n", xx, xy, xz, yy, yz, zz); + if (isBrukerBug) { + ptvd->_symBMatrix[3] = ptvd->_symBMatrix[4]; + ptvd->_symBMatrix[4] = ptvd->_symBMatrix[5]; + //next: solve for zz given bvalue, xx, and yy + ptvd->_symBMatrix[5] = ptvd->_dtiV[0] - ptvd->_symBMatrix[0] - ptvd->_symBMatrix[3]; + if ((ptvd->_symBMatrix[0] < 0.0) || (ptvd->_symBMatrix[5] < 0.0)) + printError("DICOM BMatrix corrupt.\n"); + } + //END BRUKER_KLUDGE + vec3 bVec = nifti_mat33_eig3(ptvd->_symBMatrix[0], ptvd->_symBMatrix[1], ptvd->_symBMatrix[2], ptvd->_symBMatrix[3], ptvd->_symBMatrix[4], ptvd->_symBMatrix[5]); + ptvd->_dtiV[1] = bVec.v[0]; + ptvd->_dtiV[2] = bVec.v[1]; + ptvd->_dtiV[3] = bVec.v[2]; + //printf("bmat=[%g %g %g %g %g %g %g %g %g]\n", ptvd->_symBMatrix[0],ptvd->_symBMatrix[1],ptvd->_symBMatrix[2], ptvd->_symBMatrix[1],ptvd->_symBMatrix[3],ptvd->_symBMatrix[4], ptvd->_symBMatrix[2],ptvd->_symBMatrix[4],ptvd->_symBMatrix[5]); + //printf("bmats=[%g %g %g %g %g %g];\n", ptvd->_symBMatrix[0],ptvd->_symBMatrix[1],ptvd->_symBMatrix[2],ptvd->_symBMatrix[3],ptvd->_symBMatrix[4],ptvd->_symBMatrix[5]); + //printf("bvec=[%g %g %g];\n", ptvd->_dtiV[1], ptvd->_dtiV[2], ptvd->_dtiV[3]); + //printf("bval=%g;\n\n", ptvd->_dtiV[0]); + } + if (!isReady) + return; + // If still here, update dd and *pdti4D. + ptvd->pdd->CSA.numDti++; + if (ptvd->pdd->CSA.numDti == 2) { // First time we know that this is a 4D DTI dataset; + for (int i = 0; i < 4; ++i) // Start *pdti4D before ptvd->pdd->CSA.dtiV + ptvd->pdti4D->S[0].V[i] = ptvd->pdd->CSA.dtiV[i]; // is updated. + } + for (int i = 0; i < 4; ++i) // Update pdd + ptvd->pdd->CSA.dtiV[i] = ptvd->_dtiV[i]; + if ((ptvd->pdd->CSA.numDti > 1) && (ptvd->pdd->CSA.numDti < kMaxDTI4D)) { // Update *pdti4D + //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI)); + for (int i = 0; i < 4; ++i) + ptvd->pdti4D->S[ptvd->pdd->CSA.numDti - 1].V[i] = ptvd->_dtiV[i]; + } + clear_volume(ptvd); // clear the slate for the next volume. +} //_update_tvd() +//END RIR + +struct TDCMdim { //DimensionIndexValues + uint32_t dimIdx[MAX_NUMBER_OF_DIMENSIONS]; + uint32_t diskPos; + float triggerDelayTime, TE, intenScale, intenIntercept, intenScalePhilips, RWVScale, RWVIntercept; + float V[4]; + bool isPhase; + bool isReal; + bool isImaginary; +}; + +void getFileNameX(char *pathParent, const char *path, int maxLen) { //if path is c:\d1\d2 then filename is 'd2' + const char *filename = strrchr(path, '/'); //UNIX + const char *filenamew = strrchr(path, '\\'); //Windows + if (filename == NULL) + filename = filenamew; + //if ((filename != NULL) && (filenamew != NULL)) filename = std::max(filename, filenamew); + if ((filename != NULL) && (filenamew != NULL) && (filenamew > filename)) + filename = filenamew; //for mixed file separators, e.g. "C:/dir\filenane.tmp" + if (filename == NULL) { //no path separator + strcpy(pathParent, path); + return; + } + filename++; + strncpy(pathParent, filename, maxLen - 1); +} + +void getFileName(char *pathParent, const char *path) { //if path is c:\d1\d2 then filename is 'd2' + getFileNameX(pathParent, path, kDICOMStr); +} + +struct fidx { + float value; + int index; +}; + +int fcmp(const void *a, const void *b) { + struct fidx *a1 = (struct fidx *)a; + struct fidx *a2 = (struct fidx *)b; + if ((*a1).value > (*a2).value) + return 1; + else if ((*a1).value < (*a2).value) + return -1; + else + return 0; +} + +#ifdef USING_R + +// True iff dcm1 sorts *before* dcm2 +bool compareTDCMdim(const TDCMdim &dcm1, const TDCMdim &dcm2) { + for (int i = MAX_NUMBER_OF_DIMENSIONS - 1; i >= 0; i--) { + if (dcm1.dimIdx[i] < dcm2.dimIdx[i]) + return true; + else if (dcm1.dimIdx[i] > dcm2.dimIdx[i]) + return false; + } + return false; +} //compareTDCMdim() + +bool compareTDCMdimRev(const TDCMdim &dcm1, const TDCMdim &dcm2) { + for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) { + if (dcm1.dimIdx[i] < dcm2.dimIdx[i]) + return true; + else if (dcm1.dimIdx[i] > dcm2.dimIdx[i]) + return false; + } + return false; +} //compareTDCMdimRev() + +#else + +int compareTDCMdim(void const *item1, void const *item2) { + struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1; + struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2; + //for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) { + for (int i = MAX_NUMBER_OF_DIMENSIONS - 1; i >= 0; i--) { + if (dcm1->dimIdx[i] < dcm2->dimIdx[i]) + return -1; + else if (dcm1->dimIdx[i] > dcm2->dimIdx[i]) + return 1; + } + return 0; +} //compareTDCMdim() + +int compareTDCMdimRev(void const *item1, void const *item2) { + struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1; + struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2; + for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) { + if (dcm1->dimIdx[i] < dcm2->dimIdx[i]) + return -1; + else if (dcm1->dimIdx[i] > dcm2->dimIdx[i]) + return 1; + } + return 0; +} //compareTDCMdimRev() + +#endif // USING_R + +struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D *dti4D) { + //struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { + int isVerbose = prefs->isVerbose; + int compressFlag = prefs->compressFlag; + struct TDICOMdata d = clear_dicom_data(); + d.imageNum = 0; //not set + strcpy(d.protocolName, ""); //erase dummy with empty + strcpy(d.protocolName, ""); //erase dummy with empty + strcpy(d.seriesDescription, ""); //erase dummy with empty + strcpy(d.sequenceName, ""); //erase dummy with empty + //do not read folders - code specific to GCC (LLVM/Clang seems to recognize a small file size) + dti4D->sliceOrder[0] = -1; + dti4D->volumeOnsetTime[0] = -1; + dti4D->decayFactor[0] = -1; + dti4D->frameDuration[0] = -1; + //dti4D->fragmentOffset[0] = -1; + dti4D->intenScale[0] = 0.0; + struct TVolumeDiffusion volDiffusion = initTVolumeDiffusion(&d, dti4D); + struct stat s; + if (stat(fname, &s) == 0) { + if (!(s.st_mode & S_IFREG)) { + printMessage("DICOM read fail: not a valid file (perhaps a directory) %s\n", fname); + return d; + } + } + bool isPart10prefix = true; + int isOK = isDICOMfile(fname); + if (isOK == 0) + return d; + if (isOK == 2) { + d.isExplicitVR = false; + isPart10prefix = false; + } + FILE *file = fopen(fname, "rb"); + if (!file) { + printMessage("Unable to open file %s\n", fname); + return d; + } + fseek(file, 0, SEEK_END); + long fileLen = ftell(file); //Get file length + if (fileLen < 256) { + printMessage("File too small to be a DICOM image %s\n", fname); + return d; + } +//Since size of DICOM header is unknown, we will load it in 1mb segments +//This uses less RAM and makes is faster for computers with slow disk access +//Benefit is largest for 4D images. +//To disable caching and load entire file to RAM, compile with "-dmyLoadWholeFileToReadHeader" +//To implement the segments, we define these variables: +// fileLen = size of file in bytes +// MaxBufferSz = maximum size of buffer in bytes +// Buffer = array with n elements, where n is smaller of fileLen or MaxBufferSz +// lPos = position in Buffer (indexed from 0), 0..(n-1) +// lFileOffset = offset of Buffer in file: true file position is lOffset+lPos (initially 0) +#ifdef myLoadWholeFileToReadHeader + size_t MaxBufferSz = fileLen; +#else + size_t MaxBufferSz = 1000000; //ideally size of DICOM header, but this varies from 2D to 4D files +#endif + if (MaxBufferSz > (size_t)fileLen) + MaxBufferSz = fileLen; + //printf("%d -> %d\n", MaxBufferSz, fileLen); + long lFileOffset = 0; + fseek(file, 0, SEEK_SET); + //Allocate memory + unsigned char *buffer = (unsigned char *)malloc(MaxBufferSz + 1); + if (!buffer) { + printError("Memory exhausted!"); + fclose(file); + return d; + } + //Read file contents into buffer + size_t sz = fread(buffer, 1, MaxBufferSz, file); + if (sz < MaxBufferSz) { + printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); + fclose(file); + return d; + } +#ifdef myLoadWholeFileToReadHeader + fclose(file); +#endif + //DEFINE DICOM TAGS +#define kUnused 0x0001 + (0x0001 << 16) +#define kStart 0x0002 + (0x0000 << 16) +#define kMediaStorageSOPClassUID 0x0002 + (0x0002 << 16) +#define kMediaStorageSOPInstanceUID 0x0002 + (0x0003 << 16) +#define kTransferSyntax 0x0002 + (0x0010 << 16) +#define kImplementationVersionName 0x0002 + (0x0013 << 16) +#define kSourceApplicationEntityTitle 0x0002 + (0x0016 << 16) +#define kDirectoryRecordSequence 0x0004 + (0x1220 << 16) +//#define kSpecificCharacterSet 0x0008+(0x0005 << 16 ) //someday we should handle foreign characters... +#define kImageTypeTag 0x0008 + (0x0008 << 16) +//#define kSOPInstanceUID 0x0008+(0x0018 << 16 ) //Philips inserts time as last item, e.g. ?.?.?.YYYYMMDDHHmmSS.SSSS +// not reliable https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/21 +#define kStudyDate 0x0008 + (0x0020 << 16) +#define kAcquisitionDate 0x0008 + (0x0022 << 16) +#define kAcquisitionDateTime 0x0008 + (0x002A << 16) +#define kStudyTime 0x0008 + (0x0030 << 16) +#define kSeriesTime 0x0008 + (0x0031 << 16) +#define kAcquisitionTime 0x0008 + (0x0032 << 16) //TM +//#define kContentTime 0x0008+(0x0033 << 16 ) //TM +#define kModality 0x0008 + (0x0060 << 16) //CS +#define kManufacturer 0x0008 + (0x0070 << 16) +#define kInstitutionName 0x0008 + (0x0080 << 16) +#define kInstitutionAddress 0x0008 + (0x0081 << 16) +#define kReferringPhysicianName 0x0008 + (0x0090 << 16) +#define kStationName 0x0008 + (0x1010 << 16) +#define kSeriesDescription 0x0008 + (0x103E << 16) // '0008' '103E' 'LO' 'SeriesDescription' +#define kInstitutionalDepartmentName 0x0008 + (0x1040 << 16) +#define kManufacturersModelName 0x0008 + (0x1090 << 16) +#define kDerivationDescription 0x0008 + (0x2111 << 16) +#define kComplexImageComponent (uint32_t)0x0008 + (0x9208 << 16) //'0008' '9208' 'CS' 'ComplexImageComponent' +#define kAcquisitionContrast (uint32_t)0x0008 + (0x9209 << 16) //'0008' '9209' 'CS' 'AcquisitionContrast' +#define kPatientName 0x0010 + (0x0010 << 16) +#define kPatientID 0x0010 + (0x0020 << 16) +#define kAccessionNumber 0x0008 + (0x0050 << 16) +#define kPatientBirthDate 0x0010 + (0x0030 << 16) +#define kPatientSex 0x0010 + (0x0040 << 16) +#define kPatientAge 0x0010 + (0x1010 << 16) +#define kPatientWeight 0x0010 + (0x1030 << 16) +#define kAnatomicalOrientationType 0x0010 + (0x2210 << 16) +#define kDeidentificationMethod 0x0012 + (0x0063 << 16) //[DICOMANON, issue 383 +#define kBodyPartExamined 0x0018 + (0x0015 << 16) +#define kBodyPartExamined 0x0018 + (0x0015 << 16) +#define kScanningSequence 0x0018 + (0x0020 << 16) +#define kSequenceVariant 0x0018 + (0x0021 << 16) +#define kScanOptions 0x0018 + (0x0022 << 16) +#define kMRAcquisitionType 0x0018 + (0x0023 << 16) +#define kSequenceName 0x0018 + (0x0024 << 16) +#define kRadiopharmaceutical 0x0018 + (0x0031 << 16) //LO +#define kZThick 0x0018 + (0x0050 << 16) +#define kTR 0x0018 + (0x0080 << 16) +#define kTE 0x0018 + (0x0081 << 16) +#define kTI 0x0018 + (0x0082 << 16) // Inversion time +#define kNumberOfAverages 0x0018 + (0x0083 << 16) //DS +#define kImagingFrequency 0x0018 + (0x0084 << 16) //DS +//#define kEffectiveTE 0x0018+(0x9082 << 16 ) +#define kEchoNum 0x0018 + (0x0086 << 16) //IS +#define kMagneticFieldStrength 0x0018 + (0x0087 << 16) //DS +#define kZSpacing 0x0018 + (0x0088 << 16) //'DS' 'SpacingBetweenSlices' +#define kPhaseEncodingSteps 0x0018 + (0x0089 << 16) //'IS' +#define kEchoTrainLength 0x0018 + (0x0091 << 16) //IS +#define kPercentSampling 0x0018 + (0x0093 << 16) //'DS' +#define kPhaseFieldofView 0x0018 + (0x0094 << 16) //'DS' +#define kPixelBandwidth 0x0018 + (0x0095 << 16) //'DS' 'PixelBandwidth' +#define kDeviceSerialNumber 0x0018 + (0x1000 << 16) //LO +#define kSoftwareVersions 0x0018 + (0x1020 << 16) //LO +#define kProtocolName 0x0018 + (0x1030 << 16) +#define kTriggerTime 0x0018 + (0x1060 << 16) //DS +#define kRadionuclideTotalDose 0x0018 + (0x1074 << 16) +#define kRadionuclideHalfLife 0x0018 + (0x1075 << 16) +#define kRadionuclidePositronFraction 0x0018 + (0x1076 << 16) +#define kGantryTilt 0x0018 + (0x1120 << 16) +#define kXRayTimeMS 0x0018 + (0x1150 << 16) //IS +#define kXRayTubeCurrent 0x0018 + (0x1151 << 16) //IS +#define kXRayExposure 0x0018 + (0x1152 << 16) +#define kConvolutionKernel 0x0018 + (0x1210 << 16) //SH +#define kFrameDuration 0x0018 + (0x1242 << 16) //IS +#define kReceiveCoilName 0x0018 + (0x1250 << 16) // SH +//#define kTransmitCoilName 0x0018 + (0x1251 << 16) // SH issue527 +#define kAcquisitionMatrix 0x0018 + (0x1310 << 16) //US +#define kFlipAngle 0x0018 + (0x1314 << 16) +#define kInPlanePhaseEncodingDirection 0x0018 + (0x1312 << 16) //CS +#define kSAR 0x0018 + (0x1316 << 16) //'DS' 'SAR' +#define kPatientOrient 0x0018 + (0x5100 << 16) //0018,5100. patient orientation - 'HFS' +#define kInversionRecovery 0x0018 + uint32_t(0x9009 << 16) //'CS' 'YES'/'NO' +#define kSpoiling 0x0018 + uint32_t(0x9016 << 16) //'CS' +#define kEchoPlanarPulseSequence 0x0018 + uint32_t(0x9018 << 16) //'CS' 'YES'/'NO' +#define kMagnetizationTransferAttribute 0x0018 + uint32_t(0x9020 << 16) //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' +#define kRectilinearPhaseEncodeReordering 0x0018 + uint32_t(0x9034 << 16) //'CS' 'REVERSE_LINEAR'/'LINEAR' +#define kPartialFourierDirection 0x0018 + uint32_t(0x9036 << 16) //'CS' +#define kCardiacSynchronizationTechnique 0x0018 + uint32_t(0x9037 << 16) //'CS' +#define kParallelReductionFactorInPlane 0x0018 + uint32_t(0x9069 << 16) //FD +#define kAcquisitionDuration 0x0018 + uint32_t(0x9073 << 16) //FD +//#define kFrameAcquisitionDateTime 0x0018+uint32_t(0x9074<< 16 ) //DT "20181019212528.232500" +#define kDiffusionDirectionality 0x0018 + uint32_t(0x9075 << 16) // NONE, ISOTROPIC, or DIRECTIONAL +#define kParallelAcquisitionTechnique 0x0018 + uint32_t(0x9078 << 16) //CS: SENSE, SMASH +#define kInversionTimes 0x0018 + uint32_t(0x9079 << 16) //FD +#define kPartialFourier 0x0018 + uint32_t(0x9081 << 16) //CS +const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); +//#define kDiffusionBFactorSiemens 0x0019+(0x100C<< 16 ) // 0019;000C;SIEMENS MR HEADER;B_value +#define kDiffusion_bValue 0x0018 + uint32_t(0x9087 << 16) // FD +#define kDiffusionOrientation 0x0018 + uint32_t(0x9089 << 16) // FD, seen in enhanced DICOM from Philips 5.* and Siemens XA10. +#define kImagingFrequency2 0x0018 + uint32_t(0x9098 << 16) //FD +#define kParallelReductionFactorOutOfPlane 0x0018 + uint32_t(0x9155 << 16) //FD +//#define kFrameAcquisitionDuration 0x0018+uint32_t(0x9220 << 16 ) //FD +#define kDiffusionBValueXX 0x0018 + uint32_t(0x9602 << 16) //FD +#define kDiffusionBValueXY 0x0018 + uint32_t(0x9603 << 16) //FD +#define kDiffusionBValueXZ 0x0018 + uint32_t(0x9604 << 16) //FD +#define kDiffusionBValueYY 0x0018 + uint32_t(0x9605 << 16) //FD +#define kDiffusionBValueYZ 0x0018 + uint32_t(0x9606 << 16) //FD +#define kDiffusionBValueZZ 0x0018 + uint32_t(0x9607 << 16) //FD +#define kMREchoSequence 0x0018 + uint32_t(0x9114 << 16) //SQ +#define kMRAcquisitionPhaseEncodingStepsInPlane 0x0018 + uint32_t(0x9231 << 16) //US +#define kNumberOfImagesInMosaic 0x0019 + (0x100A << 16) //US NumberOfImagesInMosaic +//https://nmrimaging.wordpress.com/2011/12/20/when-we-process/ +// https://nciphub.org/groups/qindicom/wiki/DiffusionrelatedDICOMtags:experienceacrosssites?action=pdf +#define kDiffusion_bValueSiemens 0x0019 + (0x100C << 16) //IS +#define kDiffusionGradientDirectionSiemens 0x0019 + (0x100E << 16) //FD +#define kSeriesPlaneGE 0x0019 + (0x1017 << 16) //SS +#define kDwellTime 0x0019 + (0x1018 << 16) //IS in NSec, see https://github.com/rordenlab/dcm2niix/issues/127 +#define kLastScanLoc 0x0019 + (0x101B << 16) +#define kBandwidthPerPixelPhaseEncode 0x0019 + (0x1028 << 16) //FD +#define kSliceTimeSiemens 0x0019 + (0x1029 << 16) ///FD +#define kPulseSequenceNameGE 0x0019 + (0x109C << 16) //LO 'epiRT' or 'epi' +#define kInternalPulseSequenceNameGE 0x0019 + (0x109E << 16) //LO 'EPI' or 'EPI2' +#define kRawDataRunNumberGE 0x0019 + (0x10A2 << 16)//SL +#define kMaxEchoNumGE 0x0019 + (0x10A9 << 16) //DS +#define kUserData12GE 0x0019 + (0x10B3 << 16) //DS phase diffusion direction +#define kDiffusionDirectionGEX 0x0019 + (0x10BB << 16) //DS phase diffusion direction +#define kDiffusionDirectionGEY 0x0019 + (0x10BC << 16) //DS frequency diffusion direction +#define kDiffusionDirectionGEZ 0x0019 + (0x10BD << 16) //DS slice diffusion direction +#define kNumberOfDiffusionDirectionGE 0x0019 + (0x10E0 << 16) ///DS NumberOfDiffusionDirection:UserData24 +#define kStudyID 0x0020 + (0x0010 << 16) +#define kSeriesNum 0x0020 + (0x0011 << 16) +#define kAcquNum 0x0020 + (0x0012 << 16) +#define kImageNum 0x0020 + (0x0013 << 16) +#define kStudyInstanceUID 0x0020 + (0x000D << 16) +#define kSeriesInstanceUID 0x0020 + (0x000E << 16) +#define kImagePositionPatient 0x0020 + (0x0032 << 16) // Actually ! +#define kOrientationACR 0x0020 + (0x0035 << 16) +#define kOrientation 0x0020 + (0x0037 << 16) +#define kTemporalPosition 0x0020+(0x0100 << 16 ) //IS +//#define kNumberOfTemporalPositions 0x0020+(0x0105 << 16 ) //IS public tag for NumberOfDynamicScans +#define kTemporalResolution 0x0020 + (0x0110 << 16) //DS +#define kImagesInAcquisition 0x0020 + (0x1002 << 16) //IS +//#define kSliceLocation 0x0020+(0x1041 << 16 ) //DS would be useful if not optional type 3 +#define kImageComments 0x0020 + (0x4000 << 16) // '0020' '4000' 'LT' 'ImageComments' +#define kFrameContentSequence 0x0020 + uint32_t(0x9111 << 16) //SQ +#define kTriggerDelayTime 0x0020 + uint32_t(0x9153 << 16) //FD +#define kDimensionIndexValues 0x0020 + uint32_t(0x9157 << 16) // UL n-dimensional index of frame. +#define kInStackPositionNumber 0x0020 + uint32_t(0x9057 << 16) // UL can help determine slices in volume +#define kTemporalPositionIndex 0x0020 + uint32_t(0x9128 << 16) // UL +#define kDimensionIndexPointer 0x0020 + uint32_t(0x9165 << 16) +//Private Group 21 as Used by Siemens: +#define kSequenceVariant21 0x0021 + (0x105B << 16) //CS +#define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText +#define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS +#define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 16) //IS +//#define kRealDwellTime 0x0021+(0x1142<< 16 )//IS +#define kBandwidthPerPixelPhaseEncode21 0x0021 + (0x1153 << 16) //FD +#define kCoilElements 0x0021 + (0x114F << 16) //LO +#define kAcquisitionMatrixText21 0x0021 + (0x1158 << 16) //SH +//Private Group 21 as used by GE: +#define kLocationsInAcquisitionGE 0x0021 + (0x104F << 16) //SS 'LocationsInAcquisitionGE' +#define kRTIA_timer 0x0021 + (0x105E << 16) //DS +#define kProtocolDataBlockGE 0x0025 + (0x101B << 16) //OB +#define kNumberOfPointsPerArm 0x0027 + (0x1060 << 16) //FL +#define kNumberOfArms 0x0027 + (0x1061 << 16) //FL +#define kNumberOfExcitations 0x0027 + (0x1062 << 16) //FL +#define kSamplesPerPixel 0x0028 + (0x0002 << 16) +#define kPhotometricInterpretation 0x0028 + (0x0004 << 16) +#define kPlanarRGB 0x0028 + (0x0006 << 16) +#define kDim3 0x0028 + (0x0008 << 16) //number of frames - for Philips this is Dim3*Dim4 +#define kDim2 0x0028 + (0x0010 << 16) +#define kDim1 0x0028 + (0x0011 << 16) +#define kXYSpacing 0x0028 + (0x0030 << 16) //DS 'PixelSpacing' +#define kBitsAllocated 0x0028 + (0x0100 << 16) +#define kBitsStored 0x0028 + (0x0101 << 16) //US 'BitsStored' +#define kIsSigned 0x0028 + (0x0103 << 16) //PixelRepresentation +#define kPixelPaddingValue 0x0028 + (0x0120 << 16) // https://github.com/rordenlab/dcm2niix/issues/262 +#define kFloatPixelPaddingValue 0x0028 + (0x0122 << 16) // https://github.com/rordenlab/dcm2niix/issues/262 +#define kIntercept 0x0028 + (0x1052 << 16) +#define kSlope 0x0028 + (0x1053 << 16) +//#define kRescaleType 0x0028+(0x1053 << 16 ) //LO e.g. for Philips Fieldmap: [Hz] +//#define kSpectroscopyDataPointColumns 0x0028+(0x9002 << 16 ) //IS +#define kGeiisFlag 0x0029 + (0x0010 << 16) //warn user if dreaded GEIIS was used to process image +#define kCSAImageHeaderInfo 0x0029 + (0x1010 << 16) +#define kCSASeriesHeaderInfo 0x0029 + (0x1020 << 16) +#define kStudyComments 0x0032 + (0x4000 << 16) //LT StudyComments +//#define kObjectGraphics 0x0029+(0x1210 << 16 ) //0029,1210 syngoPlatformOOGInfo Object Oriented Graphics +#define kProcedureStepDescription 0x0040 + (0x0254 << 16) +#define kRealWorldIntercept 0x0040 + uint32_t(0x9224 << 16) //IS dicm2nii's SlopInt_6_9 +#define kRealWorldSlope 0x0040 + uint32_t(0x9225 << 16) //IS dicm2nii's SlopInt_6_9 +#define kUserDefineDataGE 0x0043 + (0x102A << 16) //OB +#define kEffectiveEchoSpacingGE 0x0043 + (0x102C << 16) //SS +#define kImageTypeGE 0x0043 + (0x102F << 16) //SS 0/1/2/3 for magnitude/phase/real/imaginary +#define kDiffusion_bValueGE 0x0043 + (0x1039 << 16) //IS dicm2nii's SlopInt_6_9 +#define kEpiRTGroupDelayGE 0x0043 + (0x107C << 16) //FL +#define kAssetRFactorsGE 0x0043 + (0x1083 << 16) //DS +#define kASLContrastTechniqueGE 0x0043 + (0x10A3 << 16) //CS +#define kASLLabelingTechniqueGE 0x0043 + (0x10A4 << 16) //LO +#define kDurationLabelPulseGE 0x0043 + (0x10A5 << 16) //IS +#define kMultiBandGE 0x0043 + (0x10B6 << 16) //LO +#define kAcquisitionMatrixText 0x0051 + (0x100B << 16) //LO +#define kImageOrientationText 0x0051 + (0x100E << 16) // +#define kCoilSiemens 0x0051 + (0x100F << 16) +#define kImaPATModeText 0x0051 + (0x1011 << 16) +#define kLocationsInAcquisition 0x0054 + (0x0081 << 16) +#define kUnitsPT 0x0054 + (0x1001 << 16) //CS +#define kAttenuationCorrectionMethod 0x0054 + (0x1101 << 16) //LO +#define kDecayCorrection 0x0054 + (0x1102 << 16) //CS +#define kReconstructionMethod 0x0054 + (0x1103 << 16) //LO +#define kDecayFactor 0x0054 + (0x1321 << 16) //LO +//ftp://dicom.nema.org/MEDICAL/dicom/2014c/output/chtml/part03/sect_C.8.9.4.html +//If ImageType is REPROJECTION we slice direction is reversed - need example to test +// #define kSeriesType 0x0054+(0x1000 << 16 ) +#define kDoseCalibrationFactor 0x0054 + (0x1322 << 16) +#define kPETImageIndex 0x0054 + (0x1330 << 16) +#define kPEDirectionDisplayedUIH 0x0065 + (0x1005 << 16) //SH +#define kDiffusion_bValueUIH 0x0065 + (0x1009 << 16) //FD +#define kParallelInformationUIH 0x0065 + (0x100D << 16) //SH +#define kNumberOfImagesInGridUIH 0x0065 + (0x1050 << 16) //DS +#define kDiffusionGradientDirectionUIH 0x0065 + (0x1037 << 16) //FD +//#define kMRVFrameSequenceUIH 0x0065+(0x1050<< 16 ) //SQ +#define kPhaseEncodingDirectionPositiveUIH 0x0065 + (0x1058 << 16) //IS issue410 +#define kIconImageSequence 0x0088 + (0x0200 << 16) +#define kElscintIcon 0x07a3 + (0x10ce << 16) //see kGeiisFlag and https://github.com/rordenlab/dcm2niix/issues/239 +#define kPMSCT_RLE1 0x07a1 + (0x100a << 16) //Elscint/Philips compression +#define kPrivateCreator 0x2001 + (0x0010 << 16) // LO (Private creator is any tag where group is odd and element is x0010-x00FF +#define kDiffusion_bValuePhilips 0x2001 + (0x1003 << 16) // FL +#define kPhaseNumber 0x2001 + (0x1008 << 16) //IS +#define kCardiacSync 0x2001 + (0x1010 << 16) //CS +//#define kDiffusionDirectionPhilips 0x2001+(0x1004 << 16 )//CS Diffusion Direction +#define kSliceNumberMrPhilips 0x2001 + (0x100A << 16) //IS Slice_Number_MR +#define kSliceOrient 0x2001 + (0x100B << 16) //2001,100B Philips slice orientation (TRANSVERSAL, AXIAL, SAGITTAL) +#define kEPIFactorPhilips 0x2001 + (0x1013 << 16) //SL +#define kPrepulseDelay 0x2001 + (0x101B << 16) //FL +#define kPrepulseType 0x2001 + (0x101C << 16) //CS +#define kRespirationSync 0x2001 + (0x101F << 16) //CS +#define kNumberOfSlicesMrPhilips 0x2001 + (0x1018 << 16) //SL 0x2001, 0x1018 ), "Number_of_Slices_MR" +#define kPartialMatrixScannedPhilips 0x2001 + (0x1019 << 16) // CS +#define kWaterFatShiftPhilips 0x2001 + (0x1022 << 16) //FL +//#define kMRSeriesAcquisitionNumber 0x2001+(0x107B << 16 ) //IS +//#define kNumberOfLocationsPhilips 0x2001+(0x1015 << 16 ) //SS +//#define kStackSliceNumber 0x2001+(0x1035 << 16 )//? Potential way to determine slice order for Philips? +#define kNumberOfDynamicScans 0x2001 + (0x1081 << 16) //'2001' '1081' 'IS' 'NumberOfDynamicScans' +#define kMRfMRIStatusIndicationPhilips 0x2005 + (0x1063 << 16) +#define kMRAcquisitionTypePhilips 0x2005 + (0x106F << 16) //SS +#define kAngulationAP 0x2005 + (0x1071 << 16) //'2005' '1071' 'FL' 'MRStackAngulationAP' +#define kAngulationFH 0x2005 + (0x1072 << 16) //'2005' '1072' 'FL' 'MRStackAngulationFH' +#define kAngulationRL 0x2005 + (0x1073 << 16) //'2005' '1073' 'FL' 'MRStackAngulationRL' +#define kMRStackOffcentreAP 0x2005 + (0x1078 << 16) +#define kMRStackOffcentreFH 0x2005 + (0x1079 << 16) +#define kMRStackOffcentreRL 0x2005 + (0x107A << 16) +#define kPhilipsSlope 0x2005 + (0x100E << 16) +#define kMRImageDynamicScanBeginTime 0x2005 + (0x10a0 << 16) //FL +#define kDiffusionDirectionRL 0x2005 + (0x10B0 << 16) +#define kDiffusionDirectionAP 0x2005 + (0x10B1 << 16) +#define kDiffusionDirectionFH 0x2005 + (0x10B2 << 16) +#define kPrivatePerFrameSq 0x2005 + (0x140F << 16) +#define kMRImageDiffBValueNumber 0x2005 + (0x1412 << 16) //IS +#define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS +#define kMRImageLabelType 0x2005 + (0x1429 << 16) //CS ASL LBL_CTL https://github.com/physimals/dcm_convert_phillips/ +#define kMRImageDiffVolumeNumber 0x2005+(0x1596 << 16) //IS +#define kSharedFunctionalGroupsSequence 0x5200 + uint32_t(0x9229 << 16) // SQ +#define kPerFrameFunctionalGroupsSequence 0x5200 + uint32_t(0x9230 << 16) // SQ +#define kWaveformSq 0x5400 + (0x0100 << 16) +#define kSpectroscopyData 0x5600 + (0x0020 << 16) //OF +#define kImageStart 0x7FE0 + (0x0010 << 16) +#define kImageStartFloat 0x7FE0 + (0x0008 << 16) +#define kImageStartDouble 0x7FE0 + (0x0009 << 16) + uint32_t kItemTag = 0xFFFE + (0xE000 << 16); + uint32_t kItemDelimitationTag = 0xFFFE + (0xE00D << 16); + uint32_t kSequenceDelimitationItemTag = 0xFFFE + (0xE0DD << 16); +#define salvageAgfa +#ifdef salvageAgfa //issue435 +// handle PrivateCreator renaming e.g. 0021,10xx -> 0021,11xx +// https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl +// https://github.com/neurolabusc/dcm_qa_agfa +// http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.8.html +#define kMaxRemaps 16 //no vendor uses more than 5 private creator groups + //we need to keep track of multiple remappings, e.g. issue 437 2005,0014->2005,0012; 2005,0015->2005,0011 + int nRemaps = 0; + uint32_t privateCreatorMasks[kMaxRemaps]; //0 -> none + uint32_t privateCreatorRemaps[kMaxRemaps]; //0 -> none +#endif + double TE = 0.0; //most recent echo time recorded + float temporalResolutionMS = 0.0; + float MRImageDynamicScanBeginTime = 0.0; + bool is2005140FSQ = false; + bool overlayOK = true; + int userData12GE = 0; + int overlayRows = 0; + int overlayCols = 0; + bool isNeologica = false; + bool isTriggerSynced = false; + bool isProspectiveSynced = false; + bool isDICOMANON = false; //issue383 + bool isMATLAB = false; //issue383 + bool isASL = false; + //double contentTime = 0.0; + int echoTrainLengthPhil = 0; + int philMRImageDiffBValueNumber = 0; + int philMRImageDiffVolumeNumber = -1; + int sqDepth = 0; + int acquisitionTimesGE_UIH = 0; + int sqDepth00189114 = -1; + bool hasDwiDirectionality = false; + //float sliceLocation = INFINITY; //useless since this tag is optional + //int numFirstPatientPosition = 0; + int nDimIndxVal = -1; //tracks Philips kDimensionIndexValues + int locationsInAcquisitionGE = 0; + int PETImageIndex = 0; + int inStackPositionNumber = 0; + bool isKludgeIssue533 = false; + uint32_t dimensionIndexPointer[MAX_NUMBER_OF_DIMENSIONS]; + size_t dimensionIndexPointerCounter = 0; + int maxInStackPositionNumber = 0; + int temporalPositionIndex = 0; + int maxTemporalPositionIndex = 0; + //int temporalPositionIdentifier = 0; + int locationsInAcquisitionPhilips = 0; + int imagesInAcquisition = 0; + //int sumSliceNumberMrPhilips = 0; + int sliceNumberMrPhilips = 0; + int volumeNumber = -1; + int gradientOrientationNumberPhilips = -1; + int numberOfFrames = 0; + //int MRImageGradientOrientationNumber = 0; + //int minGradNum = kMaxDTI4D + 1; + //int maxGradNum = -1; + int numberOfDynamicScans = 0; + //int mRSeriesAcquisitionNumber = 0; + uint32_t lLength; + uint32_t groupElement; + long lPos = 0; + bool isPhilipsDerived = false; + //bool isPhilipsDiffusion = false; + if (isPart10prefix) { //for part 10 files, skip preamble and prefix + lPos = 128 + 4; //4-byte signature starts at 128 + groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + if (groupElement != kStart) + printMessage("DICOM appears corrupt: first group:element should be 0x0002:0x0000 '%s'\n", fname); + } else { //no isPart10prefix - need to work out if this is explicit VR! + if (isVerbose > 1) + printMessage("DICOM preamble and prefix missing: this is not a valid DICOM image.\n"); + //See Toshiba Aquilion images from https://www.aliza-dicom-viewer.com/download/datasets + lLength = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24); + if (lLength > fileLen) { + if (isVerbose > 1) + printMessage("Guessing this is an explicit VR image.\n"); + d.isExplicitVR = true; + } + } + char vr[2]; + //float intenScalePhilips = 0.0; + char seriesTimeTxt[kDICOMStr] = ""; + char acquisitionDateTimeTxt[kDICOMStr] = ""; + char imageType1st[kDICOMStr] = ""; + bool isEncapsulatedData = false; + int multiBandFactor = 0; + int frequencyRows = 0; + int numberOfImagesInMosaic = 0; + int encapsulatedDataFragments = 0; + int encapsulatedDataFragmentStart = 0; //position of first FFFE,E000 for compressed images + int encapsulatedDataImageStart = 0; //position of 7FE0,0010 for compressed images (where actual image start should be start of first fragment) + bool isOrient = false; + //bool isDcm4Che = false; + bool isMoCo = false; + bool isPaletteColor = false; + bool isInterpolated = false; + bool isIconImageSequence = false; + int sqDepthIcon = -1; + bool isSwitchToImplicitVR = false; + bool isSwitchToBigEndian = false; + bool isAtFirstPatientPosition = false; //for 3d and 4d files: flag is true for slices at same position as first slice + bool isMosaic = false; + bool isGEfieldMap = false; //issue501 + int patientPositionNum = 0; + float B0Philips = -1.0; + float vRLPhilips = 0.0; + float vAPPhilips = 0.0; + float vFHPhilips = 0.0; + bool isPhase = false; + bool isReal = false; + bool isImaginary = false; + bool isMagnitude = false; + d.seriesNum = -1; + //start issue 372: + vec3 sliceV; //cross-product of kOrientation 0020,0037 + sliceV.v[0] = NAN; + float sliceMM[kMaxSlice2D]; + int nSliceMM = 0; + float minSliceMM = INFINITY; + float maxSliceMM = -INFINITY; + float minDynamicScanBeginTime = INFINITY; + float maxDynamicScanBeginTime = -INFINITY; + float minPatientPosition[4] = {NAN, NAN, NAN, NAN}; + float maxPatientPosition[4] = {NAN, NAN, NAN, NAN}; + //end issue 372 + //float frameAcquisitionDuration = 0.0; //issue369 + float patientPositionPrivate[4] = {NAN, NAN, NAN, NAN}; + float patientPosition[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D + //float patientPositionPublic[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D + float patientPositionEndPhilips[4] = {NAN, NAN, NAN, NAN}; + float patientPositionStartPhilips[4] = {NAN, NAN, NAN, NAN}; + //struct TDTI philDTI[kMaxDTI4D]; + //for (int i = 0; i < kMaxDTI4D; i++) + // philDTI[i].V[0] = -1; + //array for storing DimensionIndexValues + int numDimensionIndexValues = 0; +#ifdef USING_R + // Allocating a large array on the stack, as below, vexes valgrind and may cause overflow + std::vector dcmDim(kMaxSlice2D); +#else + TDCMdim dcmDim[kMaxSlice2D]; +#endif + for (int i = 0; i < kMaxSlice2D; i++) { + dcmDim[i].diskPos = i; + for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) + dcmDim[i].dimIdx[j] = 0; + } +//http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html +//The array nestPos tracks explicit lengths for Data Element Tag of Value (FFFE,E000) +//a delimiter (fffe,e000) can have an explicit length, in which case there is no delimiter (fffe,e00d) +// fffe,e000 can provide explicit lengths, to demonstrate ./dcmconv +ti ex.DCM im.DCM +#define kMaxNestPost 128 + int nNestPos = 0; + size_t nestPos[kMaxNestPost]; + while ((d.imageStart == 0) && ((lPos + 8 + lFileOffset) < fileLen)) { +#ifndef myLoadWholeFileToReadHeader //read one segment at a time + if ((size_t)(lPos + 128) > MaxBufferSz) { //avoid overreading the file + lFileOffset = lFileOffset + lPos; + if ((lFileOffset + MaxBufferSz) > (size_t)fileLen) + MaxBufferSz = fileLen - lFileOffset; + fseek(file, lFileOffset, SEEK_SET); + size_t sz = fread(buffer, 1, MaxBufferSz, file); + if (sz < MaxBufferSz) { + printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); + fclose(file); + return d; + } + lPos = 0; + } +#endif + if (d.isLittleEndian) + groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + else + groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24); + if ((isSwitchToBigEndian) && ((groupElement & 0xFFFF) != 2)) { + isSwitchToBigEndian = false; + d.isLittleEndian = false; + groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24); + } //transfer syntax requests switching endian after group 0002 + if ((isSwitchToImplicitVR) && ((groupElement & 0xFFFF) != 2)) { + isSwitchToImplicitVR = false; + d.isExplicitVR = false; + } //transfer syntax requests switching VR after group 0001 + //uint32_t group = (groupElement & 0xFFFF); + lPos += 4; + //issue409 - icons can have their own sub-sections... keep reading until we get to the icon image? + //if ((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) isIconImageSequence = false; + //if (groupElement == kItemTag) sqDepth++; + bool unNest = false; + while ((nNestPos > 0) && (nestPos[nNestPos] <= (lFileOffset + lPos))) { + nNestPos--; + sqDepth--; + unNest = true; + if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415 + sqDepthIcon = -1; + isIconImageSequence = false; + } + } + if (groupElement == kItemDelimitationTag) { //end of item with undefined length + sqDepth--; + unNest = true; + if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415 + sqDepthIcon = -1; + isIconImageSequence = false; + } + } + if (unNest) { + is2005140FSQ = false; + if (sqDepth < 0) + sqDepth = 0; //should not happen, but protect for faulty anonymization + //if we leave the folder MREchoSequence 0018,9114 + if ((nDimIndxVal > 0) && ((d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_BRUKER) || (d.manufacturer == kMANUFACTURER_PHILIPS)) && (sqDepth00189114 >= sqDepth)) { + sqDepth00189114 = -1; //triggered + //printf("slice %d---> 0020,9157 = %d %d %d\n", inStackPositionNumber, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2]); + // d.aslFlags = kASL_FLAG_PHILIPS_LABEL; kASL_FLAG_PHILIPS_LABEL + if ((nDimIndxVal > 1) && (volumeNumber > 0) && (inStackPositionNumber > 0) && (d.aslFlags == kASL_FLAG_PHILIPS_LABEL) || (d.aslFlags == kASL_FLAG_PHILIPS_CONTROL)) { + isKludgeIssue533 = true; + for (int i = 0; i < nDimIndxVal; i++) + d.dimensionIndexValues[i] = 0; + int phase = d.phaseNumber; + if (d.phaseNumber < 0) phase = 0; //if not set: we are saving as UINT + d.dimensionIndexValues[0] = inStackPositionNumber; //dim[3] slice changes fastest + d.dimensionIndexValues[1] = phase; //dim[4] successive volumes are phase + d.dimensionIndexValues[2] = d.aslFlags == kASL_FLAG_PHILIPS_LABEL; //dim[5] Control/Label + d.dimensionIndexValues[3] = volumeNumber; //dim[6] Repeat changes slowest + nDimIndxVal = 4; //slice < phase < control/label < volume + //printf("slice %d phase %d control/label %d repeat %d\n", inStackPositionNumber, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL, volumeNumber); + } + int ndim = nDimIndxVal; + if (inStackPositionNumber > 0) { + //for images without SliceNumberMrPhilips (2001,100A) + int sliceNumber = inStackPositionNumber; + //printf("slice %d \n", sliceNumber); + if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) { + for (int k = 0; k < 4; k++) + patientPositionStartPhilips[k] = patientPosition[k]; + } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) { + for (int k = 0; k < 4; k++) + patientPositionStartPhilips[k] = patientPositionPrivate[k]; + } + if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPosition[1]))) { + for (int k = 0; k < 4; k++) + patientPositionEndPhilips[k] = patientPosition[k]; + } else if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPositionPrivate[1]))) { + for (int k = 0; k < 4; k++) + patientPositionEndPhilips[k] = patientPositionPrivate[k]; + } + patientPosition[1] = NAN; + patientPositionPrivate[1] = NAN; + } + inStackPositionNumber = 0; + if (numDimensionIndexValues >= kMaxSlice2D) { + printError("Too many slices to track dimensions. Only up to %d are supported\n", kMaxSlice2D); + break; + } + uint32_t dimensionIndexOrder[MAX_NUMBER_OF_DIMENSIONS]; + for (size_t i = 0; i < nDimIndxVal; i++) + dimensionIndexOrder[i] = i; + // Bruker Enhanced MR IOD: reorder dimensions to ensure InStackPositionNumber corresponds to the first one + // This will ensure correct ordering of slices in 4D datasets + //Canon and Bruker reverse dimensionIndexItem order relative to Philips: new versions introduce compareTDCMdimRev + //printf("%d: %d %d %d %d\n", ndim, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2], d.dimensionIndexValues[3]); + if ((philMRImageDiffVolumeNumber > 0) && (sliceNumberMrPhilips > 0)) { //issue546: 2005,1596 provides temporal order + dcmDim[numDimensionIndexValues].dimIdx[0] = 1; + dcmDim[numDimensionIndexValues].dimIdx[1] = sliceNumberMrPhilips; + dcmDim[numDimensionIndexValues].dimIdx[2] = philMRImageDiffVolumeNumber; + } else { + for (int i = 0; i < ndim; i++) + dcmDim[numDimensionIndexValues].dimIdx[i] = d.dimensionIndexValues[dimensionIndexOrder[i]]; + } + dcmDim[numDimensionIndexValues].TE = TE; + dcmDim[numDimensionIndexValues].intenScale = d.intenScale; + dcmDim[numDimensionIndexValues].intenIntercept = d.intenIntercept; + dcmDim[numDimensionIndexValues].isPhase = isPhase; + dcmDim[numDimensionIndexValues].isReal = isReal; + dcmDim[numDimensionIndexValues].isImaginary = isImaginary; + dcmDim[numDimensionIndexValues].intenScalePhilips = d.intenScalePhilips; + dcmDim[numDimensionIndexValues].RWVScale = d.RWVScale; + dcmDim[numDimensionIndexValues].RWVIntercept = d.RWVIntercept; + //printf("%d %d %g????\n", isTriggerSynced, isProspectiveSynced, d.triggerDelayTime); + //TODO533: isKludgeIssue533 alias Philips ASL as FrameDuration? + //if ((d.triggerDelayTime > 0.0) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.aslFlags != kASL_FLAG_NONE)) + //printf(">>>%g\n", d.triggerDelayTime); + //if ((isASL) || (d.aslFlags != kASL_FLAG_NONE)) d.triggerDelayTime = 0.0; //see dcm_qa_philips_asl + //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408 + // d.triggerDelayTime = 0.0; + if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime) ) + dcmDim[numDimensionIndexValues].triggerDelayTime = 0.0; //issue395 + else + dcmDim[numDimensionIndexValues].triggerDelayTime = d.triggerDelayTime; + dcmDim[numDimensionIndexValues].V[0] = -1.0; +#ifdef MY_DEBUG + if (numDimensionIndexValues < 19) { + printMessage("dimensionIndexValues0020x9157[%d] = [", numDimensionIndexValues); + for (int i = 0; i < ndim; i++) + printMessage("%d ", d.dimensionIndexValues[i]); + printMessage("]\n"); + //printMessage("B0= %g num=%d\n", B0Philips, gradNum); + } else + return d; +#endif + //next: add diffusion if reported + if (B0Philips >= 0.0) { //diffusion parameters + // Philips does not always provide 2005,1413 (MRImageGradientOrientationNumber) and sometimes after dimensionIndexValues + /*int gradNum = 0; + for (int i = 0; i < ndim; i++) + if (d.dimensionIndexValues[i] > 0) gradNum = d.dimensionIndexValues[i]; + if (gradNum <= 0) break; + With Philips 51.0 both ADC and B=0 are saved as same direction, though they vary in another dimension + (0018,9075) CS [ISOTROPIC] + (0020,9157) UL 1\2\1\33 << ADC MAP + (0018,9075) CS [NONE] + (0020,9157) UL 1\1\2\33 + next two lines attempt to skip ADC maps + we could also increment gradNum for ADC if we wanted... + */ + if (isPhilipsDerived) { + //gradNum ++; + B0Philips = 2000.0; + vRLPhilips = 0.0; + vAPPhilips = 0.0; + vFHPhilips = 0.0; + } + if (B0Philips == 0.0) { + //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); + vRLPhilips = 0.0; + vAPPhilips = 0.0; + vFHPhilips = 0.0; + } + //if ((MRImageGradientOrientationNumber > 0) && ((gradNum != MRImageGradientOrientationNumber)) break; + /*if (gradNum < minGradNum) minGradNum = gradNum; + if (gradNum >= maxGradNum) maxGradNum = gradNum; + if (gradNum >= kMaxDTI4D) { + printError("Number of DTI gradients exceeds 'kMaxDTI4D (%d).\n", kMaxDTI4D); + } else { + gradNum = gradNum - 1; //index from 0 + philDTI[gradNum].V[0] = B0Philips; + philDTI[gradNum].V[1] = vRLPhilips; + philDTI[gradNum].V[2] = vAPPhilips; + philDTI[gradNum].V[3] = vFHPhilips; + }*/ + dcmDim[numDimensionIndexValues].V[0] = B0Philips; + dcmDim[numDimensionIndexValues].V[1] = vRLPhilips; + dcmDim[numDimensionIndexValues].V[2] = vAPPhilips; + dcmDim[numDimensionIndexValues].V[3] = vFHPhilips; + isPhilipsDerived = false; + //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); + //!!! 16032018 : next line as well as definition of B0Philips may need to be set to zero if Philips omits DiffusionBValue tag for B=0 + B0Philips = -1.0; //Philips may skip reporting B-values for B=0 volumes, so zero these + vRLPhilips = 0.0; + vAPPhilips = 0.0; + vFHPhilips = 0.0; + //MRImageGradientOrientationNumber = 0; + } //diffusion parameters + numDimensionIndexValues++; + nDimIndxVal = -1; //we need DimensionIndexValues + } //record dimensionIndexValues slice information + } //groupElement == kItemDelimitationTag : delimit item exits folder + if (groupElement == kItemTag) { + uint32_t slen = dcmInt(4, &buffer[lPos], d.isLittleEndian); + uint32_t kUndefinedLen = 0xFFFFFFFF; + if (slen != kUndefinedLen) { + nNestPos++; + if (nNestPos >= kMaxNestPost) + nNestPos = kMaxNestPost - 1; + nestPos[nNestPos] = slen + lFileOffset + lPos; + } + lLength = 4; + sqDepth++; + //return d; + } else if (((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) && (!isEncapsulatedData)) { + vr[0] = 'N'; + vr[1] = 'A'; + lLength = 4; + } else if (d.isExplicitVR) { + vr[0] = buffer[lPos]; + vr[1] = buffer[lPos + 1]; + if (buffer[lPos + 1] < 'A') { //implicit vr with 32-bit length + if (d.isLittleEndian) + lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + else + lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24); + lPos += 4; + } else if (((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'N')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'C')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'R')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'T')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'V')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'B')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'D')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'F')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'L')) | ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'V')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'W')) || ((buffer[lPos] == 'S') && (buffer[lPos + 1] == 'V'))) { //VR= UN, OB, OW, SQ || ((buffer[lPos] == 'S') && (buffer[lPos+1] == 'Q')) + //for example of UC/UR/UV/OD/OF/OL/OV/SV see VR conformance test https://www.aliza-dicom-viewer.com/download/datasets + lPos = lPos + 4; //skip 2 byte VR string and 2 reserved bytes = 4 bytes + if (d.isLittleEndian) + lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + else + lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24); + lPos = lPos + 4; //skip 4 byte length + } else if ((buffer[lPos] == 'S') && (buffer[lPos + 1] == 'Q')) { + lLength = 8; //Sequence Tag + //printMessage(" !!!SQ\t%04x,%04x\n", groupElement & 65535,groupElement>>16); + } else { //explicit VR with 16-bit length + if ((d.isLittleEndian)) + lLength = buffer[lPos + 2] | (buffer[lPos + 3] << 8); + else + lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8); + lPos += 4; //skip 2 byte VR string and 2 length bytes = 4 bytes + } + } else { //implicit VR + vr[0] = 'U'; + vr[1] = 'N'; + if (d.isLittleEndian) + lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + else + lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24); + lPos += 4; //we have loaded the 32-bit length + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144 + vr[0] = 'S'; + vr[1] = 'Q'; + lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced + } + if ((d.manufacturer != kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144 + vr[0] = 'S'; + vr[1] = 'Q'; + lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced + } + } //if explicit else implicit VR + if (lLength == 0xFFFFFFFF) { + lLength = 8; //SQ (Sequences) use 0xFFFFFFFF [4294967295] to denote unknown length + //09032018 - do not count these as SQs: Horos does not count even groups + //uint32_t special = dcmInt(4,&buffer[lPos],d.isLittleEndian); + //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html + //if (special != ksqDelim) { + vr[0] = 'S'; + vr[1] = 'Q'; + //} + } + if ((groupElement == kItemTag) && (isEncapsulatedData)) { //use this to find image fragment for compressed datasets, e.g. JPEG transfer syntax + d.imageBytes = dcmInt(4, &buffer[lPos], d.isLittleEndian); + lPos = lPos + 4; + lLength = d.imageBytes; + if (d.imageBytes > 128) { + /*if (encapsulatedDataFragments < kMaxDTI4D) { + dti4D->fragmentOffset[encapsulatedDataFragments] = (int)lPos + (int)lFileOffset; + dti4D->fragmentLength[encapsulatedDataFragments] = lLength; + }*/ + encapsulatedDataFragments++; + if (encapsulatedDataFragmentStart == 0) + encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; + } + } + if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028)) + groupElement = kUnused; //ignore icon dimensions +#ifdef salvageAgfa //issue435 + //Handle remapping using integers, and slower but simpler approach is with strings: + // https://github.com/pydicom/pydicom/blob/master/pydicom/_private_dict.py + if (((groupElement & 65535) % 2) == 0) + goto skipRemap; //remap odd (private) groups + //printf("tag %04x,%04x\n", groupElement & 65535, groupElement >> 16); + if (((groupElement >> 16) >= 0x10) && ((groupElement >> 16) <= 0xFF)) { //tags (gggg,0010-00FF) may define new remapping + //if remapping tag + //first: see if this remapping overwrites existing tag + uint32_t privateCreatorMask = 0; //0 -> none + uint32_t privateCreatorRemap = 0; //0 -> none + privateCreatorMask = (groupElement & 65535) + ((groupElement & 0xFFFF0000) << 8); + if (nRemaps > 0) { + int j = 0; + for (int i = 0; i < nRemaps; i++) //remove duplicate remapping + //copy all remaps except exact match + if (privateCreatorMasks[i] != privateCreatorMask) { + privateCreatorMasks[j] = privateCreatorMasks[i]; + privateCreatorRemaps[j] = privateCreatorRemaps[i]; + j++; + } + nRemaps = j; + } + //see if this is known private vendor tag + privateCreatorRemap = 0; + char privateCreator[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], privateCreator); + //next lines determine remapping, append as needed + //Siemens https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl + if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL) + privateCreatorRemap = 0x0019 + (0x1000 << 16); + if (strstr(privateCreator, "SIEMENS MR SDS 01") != NULL) + privateCreatorRemap = 0x0021 + (0x1000 << 16); + if (strstr(privateCreator, "SIEMENS MR SDI 02") != NULL) + privateCreatorRemap = 0x0021 + (0x1100 << 16); + if (strstr(privateCreator, "SIEMENS CSA HEADER") != NULL) + privateCreatorRemap = 0x0029 + (0x1000 << 16); + if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL) + privateCreatorRemap = 0x0051 + (0x1000 << 16); + //GE https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/gems.tpl + if (strstr(privateCreator, "GEMS_ACQU_01") != NULL) + privateCreatorRemap = 0x0019 + (0x1000 << 16); + if (strstr(privateCreator, "GEMS_RELA_01") != NULL) + privateCreatorRemap = 0x0021 + (0x1000 << 16); + if (strstr(privateCreator, "GEMS_SERS_01") != NULL) + privateCreatorRemap = 0x0025 + (0x1000 << 16); + if (strstr(privateCreator, "GEMS_PARM_01") != NULL) + privateCreatorRemap = 0x0043 + (0x1000 << 16); + //ELSCINT https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/elscint.tpl + int grp = (groupElement & 65535); + if ((grp == 0x07a1) && (strstr(privateCreator, "ELSCINT1") != NULL)) + privateCreatorRemap = 0x07a1 + (0x1000 << 16); + if ((grp == 0x07a3) && (strstr(privateCreator, "ELSCINT1") != NULL)) + privateCreatorRemap = 0x07a3 + (0x1000 << 16); + //Philips https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/philips.tpl + if (strstr(privateCreator, "PHILIPS IMAGING DD 001") != NULL) + privateCreatorRemap = 0x2001 + (0x1000 << 16); + if (strstr(privateCreator, "Philips Imaging DD 001") != NULL) + privateCreatorRemap = 0x2001 + (0x1000 << 16); + if (strstr(privateCreator, "PHILIPS MR IMAGING DD 001") != NULL) + privateCreatorRemap = 0x2005 + (0x1000 << 16); + if (strstr(privateCreator, "Philips MR Imaging DD 001") != NULL) + privateCreatorRemap = 0x2005 + (0x1000 << 16); + if (strstr(privateCreator, "PHILIPS MR IMAGING DD 005") != NULL) + privateCreatorRemap = 0x2005 + (0x1400 << 16); + if (strstr(privateCreator, "Philips MR Imaging DD 005") != NULL) + privateCreatorRemap = 0x2005 + (0x1400 << 16); + //UIH https://github.com/neurolabusc/dcm_qa_uih + if (strstr(privateCreator, "Image Private Header") != NULL) + privateCreatorRemap = 0x0065 + (0x1000 << 16); + //sanity check: group should match + if (grp != (privateCreatorRemap & 65535)) + privateCreatorRemap = 0; + if (privateCreatorRemap == 0) + goto skipRemap; //this is not a known private group + if (privateCreatorRemap == privateCreatorMask) + goto skipRemap; //the remapping and mask are identical 2005,1000 -> 2005,1000 + if ((nRemaps + 1) >= kMaxRemaps) + goto skipRemap; //all slots full (should never happen) + //add new remapping + privateCreatorMasks[nRemaps] = privateCreatorMask; + privateCreatorRemaps[nRemaps] = privateCreatorRemap; + //printf("new remapping %04x,%04x -> %04x,%04x\n", privateCreatorMask & 65535, privateCreatorMask >> 16, privateCreatorRemap & 65535, privateCreatorRemap >> 16); + if (isVerbose > 1) + printMessage("new remapping (%d) %04x,%02xxy -> %04x,%02xxy\n", nRemaps, privateCreatorMask & 65535, privateCreatorMask >> 24, privateCreatorRemap & 65535, privateCreatorRemap >> 24); + nRemaps += 1; + //for (int i = 0; i < nRemaps; i++) + // printf(" %d = %04x,%02xxy -> %04x,%02xxy\n", i, privateCreatorMasks[i] & 65535, privateCreatorMasks[i] >> 24, privateCreatorRemaps[i] & 65535, privateCreatorRemaps[i] >> 24); + goto skipRemap; + } + if (nRemaps < 1) + goto skipRemap; + { + uint32_t remappedGroupElement = 0; + for (int i = 0; i < nRemaps; i++) + if ((groupElement & 0xFF00FFFF) == (privateCreatorMasks[i] & 0xFF00FFFF)) + remappedGroupElement = privateCreatorRemaps[i] + (groupElement & 0x00FF0000); + if (remappedGroupElement == 0) + goto skipRemap; + if (isVerbose > 1) + printMessage("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16); + groupElement = remappedGroupElement; + } + skipRemap: +#endif // salvageAgfa + if ((lLength % 2) != 0) { //https://www.nitrc.org/forum/forum.php?thread_id=11827&forum_id=4703 + printMessage("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535, groupElement >> 16, lLength, fname); + //proper to return here, but we can carry on as a hail mary + // d.isValid = false; + //return d; + } + switch (groupElement) { + case kMediaStorageSOPClassUID: { + char mediaUID[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], mediaUID); + //Philips "XX_" files + //see https://github.com/rordenlab/dcm2niix/issues/328 + if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66") != NULL) + d.isRawDataStorage = true; + if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.1") != NULL) + d.isRawDataStorage = true; //Private MR Spectrum Storage + if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.2") != NULL) + d.isRawDataStorage = true; //Private MR Series Data Storage + if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.4") != NULL) + d.isRawDataStorage = true; //Private MR Examcard Storage + if (d.isRawDataStorage) + d.isDerived = true; + if (d.isRawDataStorage) + printMessage("Skipping non-image DICOM: %s\n", fname); + //Philips "PS_" files + if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.11.1") != NULL) + d.isGrayscaleSoftcopyPresentationState = true; + if (d.isGrayscaleSoftcopyPresentationState) + d.isDerived = true; + break; + } + case kMediaStorageSOPInstanceUID: { // 0002, 0003 + //char SOPInstanceUID[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], d.instanceUID); + //printMessage(">>%s\n", d.seriesInstanceUID); + d.instanceUidCrc = mz_crc32X((unsigned char *)&d.instanceUID, strlen(d.instanceUID)); + break; + } + case kTransferSyntax: { + char transferSyntax[kDICOMStr]; + strcpy(transferSyntax, ""); + dcmStr(lLength, &buffer[lPos], transferSyntax); + if (strcmp(transferSyntax, "1.2.840.10008.1.2.1") == 0) + ; //default isExplicitVR=true; //d.isLittleEndian=true + else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.50") == 0) { + d.compressionScheme = kCompress50; + //printMessage("Lossy JPEG: please decompress with Osirix or dcmdjpg. %s\n", transferSyntax); + //d.imageStart = 1; //abort as invalid (imageStart MUST be >128) + } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.51") == 0) { + d.compressionScheme = kCompress50; + } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.57") == 0) { + //d.isCompressed = true; + //https://www.medicalconnections.co.uk/kb/Transfer_Syntax should be SOF = 0xC3 + d.compressionScheme = kCompressC3; + //printMessage("Ancient JPEG-lossless (SOF type 0xc3): please check conversion\n"); + } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.70") == 0) { + d.compressionScheme = kCompressC3; + } else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.4.80") == 0) || (strcmp(transferSyntax, "1.2.840.10008.1.2.4.81") == 0)) { +#if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) + d.compressionScheme = kCompressJPEGLS; +#else + printWarning("Unsupported transfer syntax '%s' (decode with 'dcmdjpls jpg.dcm raw.dcm' or 'gdcmconv -w jpg.dcm raw.dcm', or recompile dcm2niix with JPEGLS support)\n", transferSyntax); + d.imageStart = 1; //abort as invalid (imageStart MUST be >128) +#endif + } else if (strcmp(transferSyntax, "1.3.46.670589.33.1.4.1") == 0) { + d.compressionScheme = kCompressPMSCT_RLE1; + //printMessage("Unsupported transfer syntax '%s' (decode with rle2img)\n",transferSyntax); + //d.imageStart = 1; //abort as invalid (imageStart MUST be >128) + } else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.90") == 0)) { + d.compressionScheme = kCompressYes; + //printMessage("JPEG2000 Lossless support is new: please validate conversion\n"); + } else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.1.99") == 0)) { + //n.b. Deflate compression applied applies to the encoding of the **entire** DICOM Data Set, not just image data + // see https://www.medicalconnections.co.uk/kb/Transfer-Syntax/ + //#ifndef myDisableZLib + //d.compressionScheme = kCompressDeflate; + //#else + printWarning("Unsupported transfer syntax '%s' (inflate files with 'dcmconv +te gz.dcm raw.dcm' or 'gdcmconv -w gz.dcm raw.dcm)'\n", transferSyntax); + d.imageStart = 1; //abort as invalid (imageStart MUST be >128) + //#endif + } else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.91") == 0)) { + d.compressionScheme = kCompressYes; + //printMessage("JPEG2000 support is new: please validate conversion\n"); + } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.5") == 0) + d.compressionScheme = kCompressRLE; //run length + else if (strcmp(transferSyntax, "1.2.840.10008.1.2.2") == 0) + isSwitchToBigEndian = true; //isExplicitVR=true; + else if (strcmp(transferSyntax, "1.2.840.10008.1.2") == 0) + isSwitchToImplicitVR = true; //d.isLittleEndian=true + else { + if (lLength < 1) //"1.2.840.10008.1.2" + printWarning("Missing transfer syntax: assuming default (1.2.840.10008.1.2)\n"); + else { + printWarning("Unsupported transfer syntax '%s' (see www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage)\n", transferSyntax); + d.imageStart = 1; //abort as invalid (imageStart MUST be >128) + } + } + break; + } //{} provide scope for variable 'transferSyntax + case kImplementationVersionName: { + char impTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], impTxt); + int slen = (int)strlen(impTxt); + if ((slen > 5) && (strstr(impTxt, "MATLAB") != NULL)) + isMATLAB = true; + if ((slen < 5) || (strstr(impTxt, "XA10A") == NULL)) + break; + d.isXA10A = true; + break; + } + case kSourceApplicationEntityTitle: { + char saeTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], saeTxt); + int slen = (int)strlen(saeTxt); + if ((slen < 5) || (strstr(saeTxt, "oasis") == NULL)) + break; + d.isSegamiOasis = true; + break; + } + case kDirectoryRecordSequence: { + d.isRawDataStorage = true; + break; + } + case kImageTypeTag: { + bool is1st = strlen(d.imageType) == 0; + dcmStr(lLength, &buffer[lPos], d.imageType, false); //<-distinguish spaces from pathdelim: [ORIGINAL\PHASE MAP\FFE] should return "PHASE MAP" not "PHASE_MAP" + int slen; + slen = (int)strlen(d.imageType); + if (slen > 1) { + for (int i = 0; i < slen; i++) + if (d.imageType[i] == '\\') + d.imageType[i] = '_'; + } + if (is1st) + strcpy(imageType1st, d.imageType); + if ((slen > 5) && strstr(d.imageType, "_MOCO_")) { + //d.isDerived = true; //this would have 'i- y' skip MoCo images + isMoCo = true; + } + if ((slen > 5) && strstr(d.imageType, "B0") && strstr(d.imageType, "MAP")) + d.isRealIsPhaseMapHz = true; + if ((slen > 5) && strstr(d.imageType, "_ADC_")) + d.isDerived = true; + if ((slen > 5) && strstr(d.imageType, "_TRACEW_")) + d.isDerived = true; + if ((slen > 5) && strstr(d.imageType, "_TRACE_")) + d.isDerived = true; + if ((slen > 5) && strstr(d.imageType, "_FA_")) + d.isDerived = true; + if ((slen > 12) && strstr(d.imageType, "_DIFFUSION_")) + d.isDiffusion = true; + //if (strcmp(transferSyntax, "ORIGINAL_PRIMARY_M_ND_MOSAIC") == 0) + if ((slen > 5) && !strcmp(d.imageType + slen - 6, "MOSAIC")) + isMosaic = true; + //const char* prefix = "MOSAIC"; + const char *pos = strstr(d.imageType, "MOSAIC"); + //const char p = (const char *) d.imageType; + //p = (const char) strstr(d.imageType, "MOSAIC"); + //const char* p = strstr(d.imageType, "MOSAIC"); + if (pos != NULL) + isMosaic = true; + //isNonImage 0008,0008 = DERIVED,CSAPARALLEL,POSDISP + // sometime ComplexImageComponent 0008,9208 is missing - see ADNI data + // attempt to detect non-images, see https://github.com/scitran/data/blob/a516fdc39d75a6e4ac75d0e179e18f3a5fc3c0af/scitran/data/medimg/dcm/mr/siemens.py + //For Philips combinations see Table 3-28 Table 3-28: Valid combinations of Image Type applied values + // http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/5144982/DICOM_Conformance_Statement_Intera_R7%2c_R8_and_R9.pdf%3fnodeid%3d5147977%26vernum%3d-2 + if ((slen > 3) && (strstr(d.imageType, "_R_") != NULL)) { + d.isHasReal = true; + isReal = true; + } + if ((slen > 3) && (strstr(d.imageType, "_M_") != NULL)) { + d.isHasMagnitude = true; + isMagnitude = true; + } + if ((slen > 3) && (strstr(d.imageType, "_I_") != NULL)) { + d.isHasImaginary = true; + isImaginary = true; + } + if ((slen > 3) && (strstr(d.imageType, "_P_") != NULL)) { + d.isHasPhase = true; + isPhase = true; + } + if ((slen > 6) && (strstr(d.imageType, "_REAL_") != NULL)) { + d.isHasReal = true; + isReal = true; + } + if ((slen > 11) && (strstr(d.imageType, "_MAGNITUDE_") != NULL)) { + d.isHasMagnitude = true; + isMagnitude = true; + } + if ((slen > 11) && (strstr(d.imageType, "_IMAGINARY_") != NULL)) { + d.isHasImaginary = true; + isImaginary = true; + } + if ((slen > 6) && (strstr(d.imageType, "PHASE") != NULL)) { + d.isHasPhase = true; + isPhase = true; + } + if ((slen > 6) && (strstr(d.imageType, "DERIVED") != NULL)) + d.isDerived = true; + //if((slen > 4) && (strstr(typestr, "DIS2D") != NULL) ) + // d.isNonImage = true; + //not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image + break; + } + case kAcquisitionDate: + char acquisitionDateTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], acquisitionDateTxt); + d.acquisitionDate = atof(acquisitionDateTxt); + break; + case kAcquisitionDateTime: + //char acquisitionDateTimeTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], acquisitionDateTimeTxt); + //printMessage("%s\n",acquisitionDateTimeTxt); + break; + case kStudyDate: + dcmStr(lLength, &buffer[lPos], d.studyDate); + if (((int)strlen(d.studyDate) > 7) && (strstr(d.studyDate, "19000101") != NULL)) + isNeologica = true; + break; + case kModality: + if (lLength < 2) + break; + if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'R')) + d.modality = kMODALITY_CR; + else if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'T')) + d.modality = kMODALITY_CT; + if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'R')) + d.modality = kMODALITY_MR; + if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'T')) + d.modality = kMODALITY_PT; + if ((buffer[lPos] == 'U') && (toupper(buffer[lPos + 1]) == 'S')) + d.modality = kMODALITY_US; + break; + case kManufacturer: + if (d.manufacturer == kMANUFACTURER_UNKNOWN) + d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]); + volDiffusion.manufacturer = d.manufacturer; + break; + case kInstitutionName: + dcmStr(lLength, &buffer[lPos], d.institutionName); + break; + case kInstitutionAddress: //VR is "ST": 1024 chars maximum + dcmStr(lLength, &buffer[lPos], d.institutionAddress); + break; + case kReferringPhysicianName: + dcmStr(lLength, &buffer[lPos], d.referringPhysicianName); + break; + case kComplexImageComponent: + if (is2005140FSQ) + break; //see Maastricht DICOM data for magnitude data with this field set as REAL! https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging + if (lLength < 2) + break; + //issue 256: Philips files report real ComplexImageComponent but Magnitude ImageType https://github.com/rordenlab/dcm2niix/issues/256 + isPhase = false; + isReal = false; + isImaginary = false; + isMagnitude = false; + //see Table C.8-85 http://dicom.nema.org/medical/Dicom/2017c/output/chtml/part03/sect_C.8.13.3.html + if ((buffer[lPos] == 'R') && (toupper(buffer[lPos + 1]) == 'E')) + isReal = true; + if ((buffer[lPos] == 'I') && (toupper(buffer[lPos + 1]) == 'M')) + isImaginary = true; + if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'H')) + isPhase = true; + if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'A')) + isMagnitude = true; + //not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image + if (isPhase) + d.isHasPhase = true; + if (isReal) + d.isHasReal = true; + if (isImaginary) + d.isHasImaginary = true; + if (isMagnitude) + d.isHasMagnitude = true; + break; + case kAcquisitionContrast: + char acqContrast[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], acqContrast); + if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "DIFFUSION") != NULL)) + d.isDiffusion = true; + if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "PERFUSION") != NULL)) + isASL = true; //see series 301 of dcm_qa_philips_asl + break; + case kAcquisitionTime: { + char acquisitionTimeTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], acquisitionTimeTxt); + d.acquisitionTime = atof(acquisitionTimeTxt); + if (d.manufacturer != kMANUFACTURER_UIH) + break; + //UIH slice timing- do not use for Siemens as Siemens de-identification can corrupt this field https://github.com/rordenlab/dcm2niix/issues/236 + d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.acquisitionTime; + acquisitionTimesGE_UIH++; + break; + } + case kSeriesTime: + dcmStr(lLength, &buffer[lPos], seriesTimeTxt); + break; + case kStudyTime: + if (strlen(d.studyTime) < 2) + dcmStr(lLength, &buffer[lPos], d.studyTime); + break; + case kPatientName: + dcmStr(lLength, &buffer[lPos], d.patientName); + break; + case kAnatomicalOrientationType: { + char aotTxt[kDICOMStr]; //ftp://dicom.nema.org/MEDICAL/dicom/2015b/output/chtml/part03/sect_C.7.6.2.html#sect_C.7.6.2.1.1 + dcmStr(lLength, &buffer[lPos], aotTxt); + int slen = (int)strlen(aotTxt); + if ((slen < 9) || (strstr(aotTxt, "QUADRUPED") == NULL)) + break; + printError("Anatomical Orientation Type (0010,2210) is QUADRUPED: rotate coordinates accordingly\n"); + break; + } + case kDeidentificationMethod: { //issue 383 + char anonTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], anonTxt); + int slen = (int)strlen(anonTxt); + if ((slen < 10) || (strstr(anonTxt, "DICOMANON") == NULL)) + break; + isDICOMANON = true; + printWarning("Matlab DICOMANON can scramble SeriesInstanceUID (0020,000e) and remove crucial data (see issue 383). \n"); + break; + } + case kPatientID: + if (strlen(d.patientID) > 1) + break; + dcmStr(lLength, &buffer[lPos], d.patientID); + break; + case kAccessionNumber: + dcmStr(lLength, &buffer[lPos], d.accessionNumber); + break; + case kPatientBirthDate: + dcmStr(lLength, &buffer[lPos], d.patientBirthDate); + break; + case kPatientSex: { + //must be M,F,O: http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.2.html + char patientSex = toupper(buffer[lPos]); + if ((patientSex == 'M') || (patientSex == 'F') || (patientSex == 'O')) + d.patientSex = patientSex; + break; + } + case kPatientAge: + dcmStr(lLength, &buffer[lPos], d.patientAge); + break; + case kPatientWeight: + d.patientWeight = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kStationName: + dcmStr(lLength, &buffer[lPos], d.stationName); + break; + case kSeriesDescription: + dcmStr(lLength, &buffer[lPos], d.seriesDescription); + break; + case kInstitutionalDepartmentName: + dcmStr(lLength, &buffer[lPos], d.institutionalDepartmentName); + break; + case kManufacturersModelName: + dcmStr(lLength, &buffer[lPos], d.manufacturersModelName); + break; + case kDerivationDescription: { + //strcmp(transferSyntax, "1.2.840.10008.1.2") + char derivationDescription[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], derivationDescription); //strcasecmp, strcmp + if (strcasecmp(derivationDescription, "MEDCOM_RESAMPLED") == 0) + d.isResampled = true; + break; + } + case kDeviceSerialNumber: { + dcmStr(lLength, &buffer[lPos], d.deviceSerialNumber); + break; + } + case kSoftwareVersions: { + dcmStr(lLength, &buffer[lPos], d.softwareVersions); + int slen = (int)strlen(d.softwareVersions); + if ((slen > 4) && (strstr(d.softwareVersions, "XA11") != NULL)) + d.isXA10A = true; + if ((slen > 4) && (strstr(d.softwareVersions, "XA20") != NULL)) + d.isXA10A = true; + if ((slen > 4) && (strstr(d.softwareVersions, "XA30") != NULL)) + d.isXA10A = true; + if ((slen < 5) || (strstr(d.softwareVersions, "XA10") == NULL)) + break; + d.isXA10A = true; + break; + } + case kProtocolName: { + dcmStr(lLength, &buffer[lPos], d.protocolName); + break; + } + case kPatientOrient: + dcmStr(lLength, &buffer[lPos], d.patientOrient); + break; + case kInversionRecovery: // CS [YES],[NO] + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'Y') + d.isIR = true; + break; + case kSpoiling: // CS 0018,9016 + if (lLength < 2) + break; + char sTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], sTxt); + if ((strstr(sTxt, "RF") != NULL) && (strstr(sTxt, "RF") != NULL)) + d.spoiling = kSPOILING_RF_AND_GRADIENT; + else if (strstr(sTxt, "RF") != NULL) + d.spoiling = kSPOILING_RF; + else if (strstr(sTxt, "GRADIENT") != NULL) + d.spoiling = kSPOILING_GRADIENT; + else if (strstr(sTxt, "NONE") != NULL) + d.spoiling = kSPOILING_NONE; + break; + case kEchoPlanarPulseSequence: // CS [YES],[NO] + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'Y') + d.isEPI = true; + break; + case kMagnetizationTransferAttribute: //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' + if (lLength < 2) + break; //https://github.com/bids-standard/bids-specification/pull/681 + if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos+1]) == 'F')) // OFF_RESONANCE + d.mtState = 1; //TRUE + if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos+1]) == 'N')) // ON_RESONANCE and NONE + d.mtState = 0; //FALSE + if ((toupper(buffer[lPos]) == 'N') && (toupper(buffer[lPos+1]) == 'O')) // ON_RESONANCE and NONE + d.mtState = 0; //FALSE + break; + case kRectilinearPhaseEncodeReordering: { //'CS' [REVERSE_LINEAR],[LINEAR],[CENTRIC],[REVERSE_CENTRIC] + if (d.manufacturer != kMANUFACTURER_GE) + break; //only found in GE software beginning with RX27 + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'L') + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + if (toupper(buffer[lPos]) == 'R') + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + break; + } + case kPartialFourierDirection: { //'CS' PHASE FREQUENCY SLICE_SELECT COMBINATION + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'P') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_PHASE; + if (toupper(buffer[lPos]) == 'F') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_FREQUENCY; + if (toupper(buffer[lPos]) == 'S') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT; + if (toupper(buffer[lPos]) == 'C') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION; + break; + } + case kCardiacSynchronizationTechnique: + if (toupper(buffer[lPos]) == 'P') + isProspectiveSynced = true; + break; + case kParallelReductionFactorInPlane: + if (d.manufacturer == kMANUFACTURER_SIEMENS) + break; + d.accelFactPE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kAcquisitionDuration: + //n.b. used differently by different vendors https://github.com/rordenlab/dcm2niix/issues/225 + d.acquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + //in theory, 0018,9074 could provide XA10 slice time information, but scrambled by XA10 de-identification: better to use 0021,1104 + //case kFrameAcquisitionDateTime: { + // //(0018,9074) DT [20190621095516.140000] YYYYMMDDHHMMSS + // //see https://github.com/rordenlab/dcm2niix/issues/303 + // char dateTime[kDICOMStr]; + // dcmStr(lLength, &buffer[lPos], dateTime); + // printf("%s\tkFrameAcquisitionDateTime\n", dateTime); + //} + case kDiffusionDirectionality: { // 0018, 9075 + set_directionality0018_9075(&volDiffusion, (&buffer[lPos])); + if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 10)) + break; + char dir[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], dir); + if (strcmp(dir, "ISOTROPIC") == 0) + isPhilipsDerived = true; + break; + } + case kParallelAcquisitionTechnique: //CS + dcmStr(lLength, &buffer[lPos], d.parallelAcquisitionTechnique); + break; + case kInversionTimes: { //issue 380 + if ((lLength < 8) || ((lLength % 8) != 0)) + break; + d.TI = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + } + case kPartialFourier: //(0018,9081) CS [YES],[NO] + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'Y') + d.isPartialFourier = true; + break; + case kMREchoSequence: + if (d.manufacturer != kMANUFACTURER_BRUKER) + break; + if (sqDepth == 0) + sqDepth = 1; //should not happen, in case faulty anonymization + sqDepth00189114 = sqDepth - 1; + break; + case kMRAcquisitionPhaseEncodingStepsInPlane: + d.phaseEncodingLines = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kNumberOfImagesInMosaic: + if (d.manufacturer == kMANUFACTURER_SIEMENS) + numberOfImagesInMosaic = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kSeriesPlaneGE: //SS 2=Axi, 4=Sag, 8=Cor, 16=Obl, 256=3plane + if (d.manufacturer != kMANUFACTURER_GE) + break; + if (dcmInt(lLength, &buffer[lPos], d.isLittleEndian) == 256) + d.isLocalizer = true; + break; + case kDwellTime: + d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); + break; + case kDiffusion_bValueSiemens: + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + //issue409 + B0Philips = dcmStrInt(lLength, &buffer[lPos]); + set_bVal(&volDiffusion, B0Philips); + break; + case kSliceTimeSiemens: { //Array of FD (64-bit double) + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + if ((lLength < 8) || ((lLength % 8) != 0)) + break; + int nSlicesTimes = lLength / 8; + if (nSlicesTimes > kMaxEPI3D) + break; + d.CSA.mosaicSlices = nSlicesTimes; + //printf(">>>> %d\n", nSlicesTimes); + //issue 296: for images de-identified to remove readCSAImageHeader + for (int z = 0; z < nSlicesTimes; z++) + d.CSA.sliceTiming[z] = dcmFloatDouble(8, &buffer[lPos + (z * 8)], d.isLittleEndian); + //for (int z = 0; z < nSlicesTimes; z++) + // printf("%d>>>%g\n", z+1, d.CSA.sliceTiming[z]); + checkSliceTimes(&d.CSA, nSlicesTimes, isVerbose, d.is3DAcq); + //d.CSA.dtiV[0] = dcmStrInt(lLength, &buffer[lPos]); + //d.CSA.numDti = 1; + break; + } + case kDiffusionGradientDirectionSiemens: { + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + float v[4]; + dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); + //printf(">>>%g %g %g\n", v[0], v[1], v[2]); + d.CSA.dtiV[1] = v[0]; + d.CSA.dtiV[2] = v[1]; + d.CSA.dtiV[3] = v[2]; + break; + } + case kNumberOfDiffusionDirectionGE: { + if (d.manufacturer != kMANUFACTURER_GE) + break; + float f = dcmStrFloat(lLength, &buffer[lPos]); + d.numberOfDiffusionDirectionGE = round(f); + break; + } + case kLastScanLoc: + d.lastScanLoc = dcmStrFloat(lLength, &buffer[lPos]); + break; + //GE bug: multiple echos can create identical instance numbers + // in theory, one could detect as kRawDataRunNumberGE varies + // sliceN of echoE will have the same value for all timepoints + // this value does not appear indexed + // different echoes record same echo time. + // use multiEchoSortGEDICOM.py to salvage + case kRawDataRunNumberGE: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.rawDataRunNumber = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMaxEchoNumGE: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.maxEchoNumGE = round(dcmStrFloat(lLength, &buffer[lPos])); + break; + case kUserData12GE: { + if (d.manufacturer != kMANUFACTURER_GE) + break; + userData12GE = round(dcmStrFloat(lLength, &buffer[lPos])); + //printf("%d<<<<\n", userData12GE); + break; } + case kDiffusionDirectionGEX: + if (d.manufacturer == kMANUFACTURER_GE) + set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 0); + break; + case kDiffusionDirectionGEY: + if (d.manufacturer == kMANUFACTURER_GE) + set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 1); + break; + case kDiffusionDirectionGEZ: + if (d.manufacturer == kMANUFACTURER_GE) + set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 2); + break; + case kPulseSequenceNameGE: { //LO 'epi'/'epiRT' + if (d.manufacturer != kMANUFACTURER_GE) + break; + char epiStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], epiStr); + if (strstr(epiStr, "epi_pepolar") != NULL) { + d.epiVersionGE = kGE_EPI_PEPOLAR_FWD; //n.b. combine with 0019,10B3 + } else if (strstr(epiStr, "epi2") != NULL) { + d.epiVersionGE = kGE_EPI_EPI2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2 + } else if (strstr(epiStr, "epiRT") != NULL) { + d.epiVersionGE = kGE_EPI_EPIRT; //-1 = not epi, 0 = epi, 1 = epiRT + } else if (strstr(epiStr, "epi") != NULL) { + d.epiVersionGE = kGE_EPI_EPI; //-1 = not epi, 0 = epi, 1 = epiRT + } + if (strcmp(epiStr, "3db0map") == 0) { + isGEfieldMap = true; //issue501 + } + break; + } + case kInternalPulseSequenceNameGE: { //LO 'EPI'(gradient echo)/'EPI2'(spin echo): + if (d.manufacturer != kMANUFACTURER_GE) + break; + char epiStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], epiStr); + if ((d.epiVersionGE < kGE_EPI_PEPOLAR_FWD) && (strcmp(epiStr, "EPI") == 0)) { + d.internalepiVersionGE = 1; //-1 = not EPI, 1 = EPI, 2 = EPI2 + if (d.epiVersionGE != 1) { // 1 = epiRT by kEpiRTGroupDelayGE or kPulseSequenceNameGE + d.epiVersionGE = 0; // 0 = epi (multi-phase epi) + } + } + if (strcmp(epiStr, "EPI2") == 0) { + d.internalepiVersionGE = 2; //-1 = not epi, 1 = EPI, 2 = EPI2 + } + if (strcmp(epiStr, "B0map") == 0) { + isGEfieldMap = true; //issue501 + } + break; + } + case kBandwidthPerPixelPhaseEncode: + d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kStudyInstanceUID: // 0020,000D + dcmStr(lLength, &buffer[lPos], d.studyInstanceUID); + break; + case kSeriesInstanceUID: // 0020,000E + dcmStr(lLength, &buffer[lPos], d.seriesInstanceUID); + //printMessage(">>%s\n", d.seriesInstanceUID); + d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); + break; + case kImagePositionPatient: { + if (is2005140FSQ) { + dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPositionPrivate[0]); + break; + } + patientPositionNum++; + isAtFirstPatientPosition = true; + //char dx[kDICOMStr]; + //dcmStr(lLength, &buffer[lPos], dx); + //printMessage("*%s*", dx); + dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPosition[0]); //slice position + if (isnan(d.patientPosition[1])) { + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPosition[0]); //slice position + for (int k = 0; k < 4; k++) + d.patientPosition[k] = patientPosition[k]; + } else { + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPositionLast[0]); //slice direction for 4D + for (int k = 0; k < 4; k++) + d.patientPositionLast[k] = patientPosition[k]; + if ((isFloatDiff(d.patientPositionLast[1], d.patientPosition[1])) || + (isFloatDiff(d.patientPositionLast[2], d.patientPosition[2])) || + (isFloatDiff(d.patientPositionLast[3], d.patientPosition[3]))) { + isAtFirstPatientPosition = false; //this slice is not at position of 1st slice + //if (d.patientPositionSequentialRepeats == 0) //this is the first slice with different position + // d.patientPositionSequentialRepeats = patientPositionNum-1; + } //if different position from 1st slice in file + } //if not first slice in file + set_isAtFirstPatientPosition_tvd(&volDiffusion, isAtFirstPatientPosition); + //if (isAtFirstPatientPosition) numFirstPatientPosition++; + if (isVerbose > 0) //verbose > 1 will report full DICOM tag + printMessage(" Patient Position 0020,0032 (#,@,X,Y,Z)\t%d\t%ld\t%g\t%g\t%g\n", patientPositionNum, lPos, patientPosition[1], patientPosition[2], patientPosition[3]); + if ((isOrient) && (nSliceMM < kMaxSlice2D)) { + vec3 pos = setVec3(patientPosition[1], patientPosition[2], patientPosition[3]); + sliceMM[nSliceMM] = dotProduct(pos, sliceV); + if (sliceMM[nSliceMM] < minSliceMM) { + minSliceMM = sliceMM[nSliceMM]; + for (int k = 0; k < 4; k++) + minPatientPosition[k] = patientPosition[k]; + } + if (sliceMM[nSliceMM] > maxSliceMM) { + maxSliceMM = sliceMM[nSliceMM]; + for (int k = 0; k < 4; k++) + maxPatientPosition[k] = patientPosition[k]; + } + nSliceMM++; + } + break; + } + case kInPlanePhaseEncodingDirection: + d.phaseEncodingRC = toupper(buffer[lPos]); //first character is either 'R'ow or 'C'ol + break; + case kSAR: + d.SAR = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kStudyID: + dcmStr(lLength, &buffer[lPos], d.studyID); + break; + case kSeriesNum: + d.seriesNum = dcmStrInt(lLength, &buffer[lPos]); + break; + case kAcquNum: + d.acquNum = dcmStrInt(lLength, &buffer[lPos]); + break; + case kImageNum: + //Enhanced Philips also uses this in once per file SQ 0008,1111 + //Enhanced Philips also uses this once per slice in SQ 2005,140f + if (d.imageNum < 1) + d.imageNum = dcmStrInt(lLength, &buffer[lPos]); //Philips renames each image again in 2001,9000, which can lead to duplicates + break; + case kInStackPositionNumber: + if ((d.manufacturer != kMANUFACTURER_CANON) && (d.manufacturer != kMANUFACTURER_HITACHI) && (d.manufacturer != kMANUFACTURER_UNKNOWN) && (d.manufacturer != kMANUFACTURER_PHILIPS) && (d.manufacturer != kMANUFACTURER_BRUKER)) + break; + inStackPositionNumber = dcmInt(4, &buffer[lPos], d.isLittleEndian); + //if (inStackPositionNumber == 1) numInStackPositionNumber1 ++; + //printf("<%d>\n",inStackPositionNumber); + if (inStackPositionNumber > maxInStackPositionNumber) + maxInStackPositionNumber = inStackPositionNumber; + break; + case kTemporalPositionIndex: + temporalPositionIndex = dcmInt(4, &buffer[lPos], d.isLittleEndian); + if (temporalPositionIndex > maxTemporalPositionIndex) + maxTemporalPositionIndex = temporalPositionIndex; + break; + case kDimensionIndexPointer: + dimensionIndexPointer[dimensionIndexPointerCounter++] = dcmAttributeTag(&buffer[lPos], d.isLittleEndian); + break; + case kFrameContentSequence: + //if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //see https://github.com/rordenlab/dcm2niix/issues/241 + if (sqDepth == 0) + sqDepth = 1; //should not happen, in case faulty anonymization + sqDepth00189114 = sqDepth - 1; + break; + case kTriggerDelayTime: { //0x0020+uint32_t(0x9153<< 16 ) //FD + if (prefs->isIgnoreTriggerTimes) + break; //issue499 + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + //if (isVerbose < 2) break; + double trigger = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + d.triggerDelayTime = trigger; + if (isSameFloatGE(d.triggerDelayTime, 0.0)) + d.triggerDelayTime = 0.0; //double to single + break; + } + case kDimensionIndexValues: { // kImageNum is not enough for 4D series from Philips 5.*. + if (lLength < 4) + break; + nDimIndxVal = lLength / 4; + if (nDimIndxVal > MAX_NUMBER_OF_DIMENSIONS) { + printError("%d is too many dimensions. Only up to %d are supported\n", nDimIndxVal, MAX_NUMBER_OF_DIMENSIONS); + nDimIndxVal = MAX_NUMBER_OF_DIMENSIONS; // Truncate + } + dcmMultiLongs(4 * nDimIndxVal, &buffer[lPos], nDimIndxVal, d.dimensionIndexValues, d.isLittleEndian); + break; + } + case kPhotometricInterpretation: { + char interp[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], interp); + if (strcmp(interp, "PALETTE_COLOR") == 0) + isPaletteColor = true; + //printError("Photometric Interpretation 'PALETTE COLOR' not supported\n"); + break; + } + case kPlanarRGB: + d.isPlanarRGB = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDim3: + d.xyzDim[3] = dcmStrInt(lLength, &buffer[lPos]); + numberOfFrames = d.xyzDim[3]; + break; + case kSamplesPerPixel: + d.samplesPerPixel = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDim2: + d.xyzDim[2] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDim1: + d.xyzDim[1] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + //order is Row,Column e.g. YX + case kXYSpacing: { + float yx[3]; + dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, yx); + d.xyzMM[1] = yx[2]; + d.xyzMM[2] = yx[1]; + break; + } + case kImageComments: + dcmStr(lLength, &buffer[lPos], d.imageComments, true); + break; + //group 21: siemens + //g21 + case kPATModeText: { //e.g. Siemens iPAT x2 listed as "p2" + char accelStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], accelStr); + char *ptr; + dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" + d.accelFactPE = (float)strtof(accelStr, &ptr); + if (*ptr != '\0') + d.accelFactPE = 0.0; + //between slice accel + dcmStr(lLength, &buffer[lPos], accelStr); + dcmStrDigitsOnlyKey('s', accelStr); //e.g. if "p2s4" return "4", if "p2" return "" + multiBandFactor = (int)strtol(accelStr, &ptr, 10); + if (*ptr != '\0') + multiBandFactor = 0.0; + //printMessage("p%gs%d\n", d.accelFactPE, multiBandFactor); + break; + } + case kTimeAfterStart: + //0021,1104 see https://github.com/rordenlab/dcm2niix/issues/303 + // 0021,1104 6@159630 DS 4.635 + // 0021,1104 2@161164 DS 0 + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + if (acquisitionTimesGE_UIH >= kMaxEPI3D) + break; + d.CSA.sliceTiming[acquisitionTimesGE_UIH] = dcmStrFloat(lLength, &buffer[lPos]); + d.CSA.sliceTiming[acquisitionTimesGE_UIH] *= 1000.0; //convert sec to msec + //printf("x\t%d\t%g\tkTimeAfterStart\n", acquisitionTimesGE_UIH, d.CSA.sliceTiming[acquisitionTimesGE_UIH]); + acquisitionTimesGE_UIH++; + break; + case kPhaseEncodingDirectionPositiveSiemens: { + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + int ph = dcmStrInt(lLength, &buffer[lPos]); + if (ph == 0) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + if (ph == 1) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + break; + } + //case kRealDwellTime : //https://github.com/rordenlab/dcm2niix/issues/240 + // if (d.manufacturer != kMANUFACTURER_SIEMENS) break; + // d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); + // break; + case kBandwidthPerPixelPhaseEncode21: + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kCoilElements: + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + dcmStr(lLength, &buffer[lPos], d.coilElements); + break; + //group 21: GE + case kLocationsInAcquisitionGE: + locationsInAcquisitionGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kRTIA_timer: + if (d.manufacturer != kMANUFACTURER_GE) + break; + //see dicm2nii slice timing from 0021,105E DS RTIA_timer + d.rtia_timerGE = dcmStrFloat(lLength, &buffer[lPos]); //RefAcqTimes = t/10; end % in ms + //printf("%s\t%g\n", fname, d.rtia_timerGE); + break; + case kProtocolDataBlockGE: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.protocolBlockLengthGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + d.protocolBlockStartGE = (int)lPos + (int)lFileOffset + 4; + //printError("ProtocolDataBlockGE %d @ %d\n", d.protocolBlockLengthGE, d.protocolBlockStartGE); + break; + case kNumberOfExcitations: //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.numberOfExcitations = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kNumberOfArms: //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.numberOfArms = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kNumberOfPointsPerArm: //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.numberOfPointsPerArm = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDoseCalibrationFactor: + d.doseCalibrationFactor = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kPETImageIndex: + PETImageIndex = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPEDirectionDisplayedUIH: + if (d.manufacturer != kMANUFACTURER_UIH) + break; + dcmStr(lLength, &buffer[lPos], d.phaseEncodingDirectionDisplayedUIH); + break; + case kDiffusion_bValueUIH: { + if (d.manufacturer != kMANUFACTURER_UIH) + break; + float v[4]; + dcmMultiFloatDouble(lLength, &buffer[lPos], 1, v, d.isLittleEndian); + B0Philips = v[0]; + set_bVal(&volDiffusion, v[0]); + break; + } + case kParallelInformationUIH: { //SENSE factor (0065,100d) SH [F:2S] + if (d.manufacturer != kMANUFACTURER_UIH) + break; + char accelStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], accelStr); + //char *ptr; + dcmStrDigitsDotOnlyKey(':', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" + d.accelFactPE = atof(accelStr); + break; + } + case kNumberOfImagesInGridUIH: + if (d.manufacturer != kMANUFACTURER_UIH) + break; + d.numberOfImagesInGridUIH = dcmStrFloat(lLength, &buffer[lPos]); + d.CSA.mosaicSlices = d.numberOfImagesInGridUIH; + break; + case kPhaseEncodingDirectionPositiveUIH: { + if (d.manufacturer != kMANUFACTURER_UIH) + break; + int ph = dcmStrInt(lLength, &buffer[lPos]); + if (ph == 1) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + if (ph == 0) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + break; + } + case kDiffusionGradientDirectionUIH: { + if (d.manufacturer != kMANUFACTURER_UIH) + break; + float v[4]; + dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); + //printf(">>>%g %g %g\n", v[0], v[1], v[2]); + d.CSA.dtiV[1] = v[0]; + d.CSA.dtiV[2] = v[1]; + d.CSA.dtiV[3] = v[2]; + //vRLPhilips = v[0]; + //vAPPhilips = v[1]; + //vFHPhilips = v[2]; + break; + } + case kBitsAllocated: + d.bitsAllocated = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kBitsStored: + d.bitsStored = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kIsSigned: //http://dicomiseasy.blogspot.com/2012/08/chapter-12-pixel-data.html + d.isSigned = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPixelPaddingValue: + // According to the DICOM standard, this can be either unsigned (US) or signed (SS). Currently this + // is used only in nii_saveNII3Dtilt() which only allows DT_INT16, so treat it as signed. + d.pixelPaddingValue = (float)(short)dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kFloatPixelPaddingValue: + d.pixelPaddingValue = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kTR: + d.TR = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kTE: + TE = dcmStrFloat(lLength, &buffer[lPos]); + if (d.TE <= 0.0) + d.TE = TE; + break; + case kNumberOfAverages: + d.numberOfAverages = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kImagingFrequency: + d.imagingFrequency = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kTriggerTime: { + if (prefs->isIgnoreTriggerTimes) + break; //issue499 + //untested method to detect slice timing for GE PSD “epi” with multiphase option + // will not work for current PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics) + if ((d.manufacturer != kMANUFACTURER_GE) && (d.manufacturer != kMANUFACTURER_PHILIPS)) + break; //issue384 + d.triggerDelayTime = dcmStrFloat(lLength, &buffer[lPos]); //???? issue 336 + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.triggerDelayTime; + //printf("%g\n", d.CSA.sliceTiming[acquisitionTimesGE_UIH]); + acquisitionTimesGE_UIH++; + break; + } + case kRadionuclideTotalDose: + d.radionuclideTotalDose = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kEffectiveTE: { + TE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + if (d.TE <= 0.0) + d.TE = TE; + break; + } + case kTI: + d.TI = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kEchoNum: + d.echoNum = dcmStrInt(lLength, &buffer[lPos]); + break; + case kMagneticFieldStrength: + d.fieldStrength = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kZSpacing: + d.zSpacing = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kPhaseEncodingSteps: + d.phaseEncodingSteps = dcmStrInt(lLength, &buffer[lPos]); + break; + case kEchoTrainLength: + d.echoTrainLength = dcmStrInt(lLength, &buffer[lPos]); + break; + case kPercentSampling: + d.percentSampling = dcmStrFloat(lLength, &buffer[lPos]); + case kPhaseFieldofView: + d.phaseFieldofView = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kPixelBandwidth: + d.pixelBandwidth = dcmStrFloat(lLength, &buffer[lPos]); + //printWarning(" PixelBandwidth (0018,0095)====> %g @%d\n", d.pixelBandwidth, lPos); + break; + case kAcquisitionMatrix: + if (lLength == 8) { + uint16_t acquisitionMatrix[4]; + dcmMultiShorts(lLength, &buffer[lPos], 4, &acquisitionMatrix[0], d.isLittleEndian); //slice position + //phaseEncodingLines stored in either image columns or rows + if (acquisitionMatrix[3] > 0) + d.phaseEncodingLines = acquisitionMatrix[3]; + if (acquisitionMatrix[2] > 0) + d.phaseEncodingLines = acquisitionMatrix[2]; + if (acquisitionMatrix[1] > 0) + frequencyRows = acquisitionMatrix[1]; + if (acquisitionMatrix[0] > 0) + frequencyRows = acquisitionMatrix[0]; + } + break; + case kFlipAngle: + d.flipAngle = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kRadionuclideHalfLife: + d.radionuclideHalfLife = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kRadionuclidePositronFraction: + d.radionuclidePositronFraction = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kGantryTilt: + d.gantryTilt = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kXRayTimeMS: //IS + d.exposureTimeMs = dcmStrInt(lLength, &buffer[lPos]);; + break; + case kXRayTubeCurrent: //IS + d.xRayTubeCurrent = dcmStrInt(lLength, &buffer[lPos]);; + break; + case kXRayExposure: //CTs do not have echo times, we use this field to detect different exposures: https://github.com/neurolabusc/dcm2niix/pull/48 + if (d.TE == 0) { // for CT we will use exposure (0018,1152) whereas for MR we use echo time (0018,0081) + d.isXRay = true; + d.TE = dcmStrFloat(lLength, &buffer[lPos]); + } + break; + case kConvolutionKernel: //CS + dcmStr(lLength, &buffer[lPos], d.convolutionKernel); + break; + case kFrameDuration: + d.frameDuration = dcmStrInt(lLength, &buffer[lPos]); + break; + case kReceiveCoilName: + dcmStr(lLength, &buffer[lPos], d.coilName); + if (strlen(d.coilName) < 1) + break; + d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName)); + break; + case kSlope: + d.intenScale = dcmStrFloat(lLength, &buffer[lPos]); + break; + //case kSpectroscopyDataPointColumns : + // d.xyzDim[4] = dcmInt(4,&buffer[lPos],d.isLittleEndian); + // break; + case kPhilipsSlope: + if ((lLength == 4) && (d.manufacturer == kMANUFACTURER_PHILIPS)) + d.intenScalePhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMRImageDynamicScanBeginTime: { //FL + if (lLength != 4) + break; + MRImageDynamicScanBeginTime = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + if (MRImageDynamicScanBeginTime < minDynamicScanBeginTime) + minDynamicScanBeginTime = MRImageDynamicScanBeginTime; + if (MRImageDynamicScanBeginTime > maxDynamicScanBeginTime) + maxDynamicScanBeginTime = MRImageDynamicScanBeginTime; + break; + } + case kIntercept: + d.intenIntercept = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kRadiopharmaceutical: + dcmStr(lLength, &buffer[lPos], d.radiopharmaceutical); + break; + case kZThick: + d.xyzMM[3] = dcmStrFloat(lLength, &buffer[lPos]); + d.zThick = d.xyzMM[3]; + break; + case kAcquisitionMatrixText21: + //fall through to kAcquisitionMatrixText + case kAcquisitionMatrixText: { + if (d.manufacturer == kMANUFACTURER_SIEMENS) { + char matStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], matStr); + char *pPosition = strchr(matStr, 'I'); + if (pPosition != NULL) + isInterpolated = true; + } + break; + } + case kImageOrientationText: //issue522 + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + dcmStr(lLength, &buffer[lPos], d.imageOrientationText); + break; + case kCoilSiemens: { + if (d.manufacturer == kMANUFACTURER_SIEMENS) { + //see if image from single coil "H12" or an array "HEA;HEP" + //char coilStr[kDICOMStr]; + //int coilNum; + dcmStr(lLength, &buffer[lPos], d.coilName); + if (strlen(d.coilName) < 1) + break; + //printf("-->%s\n", coilStr); + //d.coilName = coilStr; + //if (coilStr[0] == 'C') break; //kludge as Nova 32-channel defaults to "C:A32" https://github.com/rordenlab/dcm2niix/issues/187 + //char *ptr; + //dcmStrDigitsOnly(coilStr); + //coilNum = (int)strtol(coilStr, &ptr, 10); + d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName)); + //printf("%d:%s\n", d.coilNum, coilStr); + //if (*ptr != '\0') + // d.coilNum = 0; + } + break; + } + case kImaPATModeText: { //e.g. Siemens iPAT x2 listed as "p2" + char accelStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], accelStr); + char *ptr; + dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" + d.accelFactPE = (float)strtof(accelStr, &ptr); + if (*ptr != '\0') + d.accelFactPE = 0.0; + //between slice accel + dcmStr(lLength, &buffer[lPos], accelStr); + dcmStrDigitsOnlyKey('s', accelStr); //e.g. if "p2s4" return "4", if "p2" return "" + multiBandFactor = (int)strtol(accelStr, &ptr, 10); + if (*ptr != '\0') + multiBandFactor = 0.0; + break; + } + case kLocationsInAcquisition: + d.locationsInAcquisition = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kUnitsPT: //CS + dcmStr(lLength, &buffer[lPos], d.unitsPT); + break; + case kAttenuationCorrectionMethod: //LO + dcmStr(lLength, &buffer[lPos], d.attenuationCorrectionMethod); + break; + case kDecayCorrection: //CS + dcmStr(lLength, &buffer[lPos], d.decayCorrection); + break; + case kReconstructionMethod: //LO + dcmStr(lLength, &buffer[lPos], d.reconstructionMethod); + break; + case kDecayFactor: + d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kIconImageSequence: + isIconImageSequence = true; + if (sqDepthIcon < 0) + sqDepthIcon = sqDepth; + break; + //case kMRSeriesAcquisitionNumber: // 0x2001+(0x107B << 16 ) //IS + // mRSeriesAcquisitionNumber = dcmStrInt(lLength, &buffer[lPos]); + // break; + case kNumberOfDynamicScans: + //~d.numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]); + numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]); + break; + case kMRAcquisitionType: //detect 3D acquisition: we can reorient these without worrying about slice time correct or BVEC/BVAL orientation + if (lLength > 1) + d.is2DAcq = (buffer[lPos] == '2') && (toupper(buffer[lPos + 1]) == 'D'); + if (lLength > 1) + d.is3DAcq = (buffer[lPos] == '3') && (toupper(buffer[lPos + 1]) == 'D'); + //dcmStr(lLength, &buffer[lPos], d.mrAcquisitionType); + break; + case kBodyPartExamined: { + dcmStr(lLength, &buffer[lPos], d.bodyPartExamined); + break; + } + case kScanningSequence: { + dcmStr(lLength, &buffer[lPos], d.scanningSequence); + //According to the DICOM standard 0018,9018 is REQUIRED for EPI raw data + // http://dicom.nema.org/MEDICAL/Dicom/2015c/output/chtml/part03/sect_C.8.13.4.html + //In practice, this is not the case for all vendors + //Fortunately, the combination of 0018,0020 and 0018,9018 appears to reliably detect EPI data + //Siemens (pre-XA) omits 0018,9018, but reports [EP] for 0018,0020 (regardless of SE/GR) + //Siemens (XA) reports 0018,9018 but omits 0018,0020 + //Canon/Toshiba omits 0018,9018, but reports [SE\EP];[GR\EP] for 0018,0020 + //GE omits 0018,9018, but reports [EP\GR];[EP\SE] for 0018,0020 + //Philips reports 0018,9018, but reports [SE];[GR] for 0018,0020 + if ((lLength > 1) && (strstr(d.scanningSequence, "IR") != NULL)) + d.isIR = true; + if ((lLength > 1) && (strstr(d.scanningSequence, "EP") != NULL)) + d.isEPI = true; + break; //warp + } + case kSequenceVariant21: + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; //see GE dataset in dcm_qa_nih + //fall through... + case kSequenceVariant: { + dcmStr(lLength, &buffer[lPos], d.sequenceVariant); + break; + } + case kScanOptions: + dcmStr(lLength, &buffer[lPos], d.scanOptions); + if ((lLength > 1) && (strstr(d.scanOptions, "PFF") != NULL)) + d.isPartialFourier = true; //e.g. GE does not populate (0018,9081) + break; + case kSequenceName: { + dcmStr(lLength, &buffer[lPos], d.sequenceName); + break; + } + case kMRfMRIStatusIndicationPhilips: {//fmri volume number + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + int i = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + if (i > 0) //only if positive value, see Magdeburg_2014 sample data from dcm_qa_philips Philips MR 51.0 + volumeNumber = i; + break; + } + case kMRAcquisitionTypePhilips: //kMRAcquisitionType + if (lLength > 1) + d.is3DAcq = (buffer[lPos] == '3') && (toupper(buffer[lPos + 1]) == 'D'); + break; + case kAngulationRL: + d.angulation[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kAngulationAP: + d.angulation[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kAngulationFH: + d.angulation[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMRStackOffcentreRL: + d.stackOffcentre[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMRStackOffcentreAP: + d.stackOffcentre[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMRStackOffcentreFH: + d.stackOffcentre[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kSliceOrient: { + char orientStr[kDICOMStr]; + orientStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr + dcmStr(lLength, &buffer[lPos], orientStr); + if (toupper(orientStr[0]) == 'S') + d.sliceOrient = kSliceOrientSag; //sagittal + else if (toupper(orientStr[0]) == 'C') + d.sliceOrient = kSliceOrientCor; //coronal + else + d.sliceOrient = kSliceOrientTra; //transverse (axial) + break; + } + case kElscintIcon: + printWarning("Assuming icon SQ 07a3,10ce.\n"); + isIconImageSequence = true; + if (sqDepthIcon < 0) + sqDepthIcon = sqDepth; + break; + case kPMSCT_RLE1: + //https://groups.google.com/forum/#!topic/comp.protocols.dicom/8HuP_aNy9Pc + //https://discourse.slicer.org/t/fail-to-load-pet-ct-gemini/8158/3 + // d.compressionScheme = kCompressPMSCT_RLE1; //force RLE + if (d.compressionScheme != kCompressPMSCT_RLE1) + break; + d.imageStart = (int)lPos + (int)lFileOffset; + d.imageBytes = lLength; + break; + case kPrivateCreator: { + if (d.manufacturer != kMANUFACTURER_UNKNOWN) + break; + d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]); + volDiffusion.manufacturer = d.manufacturer; + break; + } + case kDiffusion_bValuePhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + B0Philips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + set_bVal(&volDiffusion, B0Philips); + break; + case kPhaseNumber: //IS issue529 + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + d.phaseNumber = dcmStrInt(lLength, &buffer[lPos]); //see dcm_qa_philips_asl + break; + case kCardiacSync: //CS [TRIGGERED],[NO] + if (lLength < 2) + break; + if (toupper(buffer[lPos]) != 'N') + isTriggerSynced = true; + break; + case kDiffusion_bValue: // 0018,9087 + if (d.manufacturer == kMANUFACTURER_UNKNOWN) { + d.manufacturer = kMANUFACTURER_PHILIPS; + printWarning("Found 0018,9087 but manufacturer (0008,0070) unknown: assuming Philips.\n"); + } + // Note that this is ahead of kImagePositionPatient (0020,0032), so + // isAtFirstPatientPosition is not necessarily set yet. + // Philips uses this tag too, at least as of 5.1, but they also + // use kDiffusionBFactor (see above), and we do not want to + // double count. More importantly, with Philips this tag + // (sometimes?) gets repeated in a nested sequence with the + // value *unset*! + // GE started using this tag in 27, and annoyingly, NOT including + // the b value if it is 0 for the slice. + //if((d.manufacturer != kMANUFACTURER_PHILIPS) || !is2005140FSQ){ + // d.CSA.numDti++; + // if (d.CSA.numDti == 2) { //First time we know that this is a 4D DTI dataset + // dti4D->S[0].V[0] = d.CSA.dtiV[0]; + // dti4D->S[0].V[1] = d.CSA.dtiV[1]; + // dti4D->S[0].V[2] = d.CSA.dtiV[2]; + // dti4D->S[0].V[3] = d.CSA.dtiV[3]; + //} + B0Philips = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + //d.CSA.dtiV[0] = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bVal(&volDiffusion, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); + // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) + // dti4D->S[d.CSA.numDti-1].V[0] = d.CSA.dtiV[0]; + //} + break; + case kDiffusionOrientation: // 0018, 9089 + // Note that this is ahead of kImagePositionPatient (0020,0032), so + // isAtFirstPatientPosition is not necessarily set yet. + // Philips uses this tag too, at least as of 5.1, but they also + // use kDiffusionDirectionRL, etc., and we do not want to double + // count. More importantly, with Philips this tag (sometimes?) + // gets repeated in a nested sequence with the value *unset*! + // if (((d.manufacturer == kMANUFACTURER_SIEMENS) || + // ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) && + // (isAtFirstPatientPosition || isnan(d.patientPosition[1]))) + //if((d.manufacturer == kMANUFACTURER_SIEMENS) || ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) + if ((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_HITACHI) || (d.manufacturer == kMANUFACTURER_SIEMENS) || (d.manufacturer == kMANUFACTURER_PHILIPS)) { + //for kMANUFACTURER_HITACHI see https://nciphub.org/groups/qindicom/wiki/StandardcompliantenhancedmultiframeDWI + float v[4]; + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); + //dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); + dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); + vRLPhilips = v[0]; + vAPPhilips = v[1]; + vFHPhilips = v[2]; + //printMessage("><>< 0018,9089:\t%g\t%g\t%g\n", v[0], v[1], v[2]); + //https://github.com/rordenlab/dcm2niix/issues/256 + //d.CSA.dtiV[1] = v[0]; + //d.CSA.dtiV[2] = v[1]; + //d.CSA.dtiV[3] = v[2]; + //printMessage("><>< 0018,9089: DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); + hasDwiDirectionality = true; + d.isBVecWorldCoordinates = true; //e.g. Canon saved image space coordinates in Comments, world space in 0018, 9089 + set_orientation0018_9089(&volDiffusion, lLength, &buffer[lPos], d.isLittleEndian); + } + break; + case kImagingFrequency2: + d.imagingFrequency = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kParallelReductionFactorOutOfPlane: + if (d.manufacturer == kMANUFACTURER_SIEMENS) + break; + d.accelFactOOP = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + //case kFrameAcquisitionDuration : + // frameAcquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //issue369 + // break; + case kDiffusionBValueXX: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 0); + break; + } + case kDiffusionBValueXY: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 1); + break; + } + case kDiffusionBValueXZ: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 2); + break; + } + case kDiffusionBValueYY: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 3); + break; + } + case kDiffusionBValueYZ: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 4); + break; + } + case kDiffusionBValueZZ: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 5); + d.isVectorFromBMatrix = true; + break; + } + case kSliceNumberMrPhilips: { + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + sliceNumberMrPhilips = dcmStrInt(lLength, &buffer[lPos]); + int sliceNumber = sliceNumberMrPhilips; + //use public patientPosition if it exists - fall back to private patient position + if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) { + for (int k = 0; k < 4; k++) + patientPositionStartPhilips[k] = patientPosition[k]; + } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) { + for (int k = 0; k < 4; k++) + patientPositionStartPhilips[k] = patientPositionPrivate[k]; + } + if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPosition[1]))) { + for (int k = 0; k < 4; k++) + patientPositionEndPhilips[k] = patientPosition[k]; + } else if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPositionPrivate[1]))) { + for (int k = 0; k < 4; k++) + patientPositionEndPhilips[k] = patientPositionPrivate[k]; + } + break; + } + case kEPIFactorPhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + echoTrainLengthPhil = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPrepulseDelay: //FL + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + d.TI = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPrepulseType: //CS [INV] + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + if (lLength < 3) + break; + if ((toupper(buffer[lPos]) != 'I') && (toupper(buffer[lPos + 1]) != 'N') && (toupper(buffer[lPos + 2]) != 'V')) + d.isIR = true; + break; + case kRespirationSync: //CS [TRIGGERED],[NO] + if (lLength < 2) + break; + if (toupper(buffer[lPos]) != 'N') + isTriggerSynced = true; + break; + case kNumberOfSlicesMrPhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + locationsInAcquisitionPhilips = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + //printMessage("====> locationsInAcquisitionPhilips\t%d\n", locationsInAcquisitionPhilips); + break; + case kPartialMatrixScannedPhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'Y') + d.isPartialFourier = true; + break; + case kWaterFatShiftPhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + d.waterFatShift = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDiffusionDirectionRL: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + vRLPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + set_diffusion_directionPhilips(&volDiffusion, vRLPhilips, 0); + break; + case kDiffusionDirectionAP: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + vAPPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + set_diffusion_directionPhilips(&volDiffusion, vAPPhilips, 1); + break; + case kDiffusionDirectionFH: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + vFHPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + set_diffusion_directionPhilips(&volDiffusion, vFHPhilips, 2); + break; + case kPrivatePerFrameSq: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + //if ((vr[0] == 'S') && (vr[1] == 'Q')) break; + //if (!is2005140FSQwarned) + // printWarning("expected VR of 2005,140F to be 'SQ' (prior DICOM->DICOM conversion error?)\n"); + is2005140FSQ = true; + //is2005140FSQwarned = true; + break; + case kMRImageGradientOrientationNumber : + if (d.manufacturer == kMANUFACTURER_PHILIPS) + gradientOrientationNumberPhilips = dcmStrInt(lLength, &buffer[lPos]); + break; + case kMRImageLabelType : //CS ??? LBL CTL + if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 2)) break; + //TODO529: issue529 for ASL LBL/CTL "LABEL" + //if (toupper(buffer[lPos]) == 'L') isLabel = true; + if (toupper(buffer[lPos]) == 'L') d.aslFlags = kASL_FLAG_PHILIPS_LABEL; + if (toupper(buffer[lPos]) == 'C') d.aslFlags = kASL_FLAG_PHILIPS_CONTROL; + break; + case kMRImageDiffBValueNumber: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + philMRImageDiffBValueNumber = dcmStrInt(lLength, &buffer[lPos]); + break; + case kMRImageDiffVolumeNumber: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + philMRImageDiffVolumeNumber = dcmStrInt(lLength, &buffer[lPos]); + break; + case kWaveformSq: + d.imageStart = 1; //abort!!! + printMessage("Skipping DICOM (audio not image) '%s'\n", fname); + break; + case kSpectroscopyData: //kSpectroscopyDataPointColumns + printMessage("Skipping Spectroscopy DICOM '%s'\n", fname); + d.imageStart = (int)lPos + (int)lFileOffset; + break; + case kCSAImageHeaderInfo: + if ((lPos + lLength) > fileLen) + break; + readCSAImageHeader(&buffer[lPos], lLength, &d.CSA, isVerbose, d.is3DAcq); //, dti4D); + if (!d.isHasPhase) + d.isHasPhase = d.CSA.isPhaseMap; + break; + case kCSASeriesHeaderInfo: + if ((lPos + lLength) > fileLen) + break; + d.CSA.SeriesHeader_offset = (int)lPos; + d.CSA.SeriesHeader_length = lLength; + break; + case kRealWorldIntercept: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + d.RWVIntercept = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + if (isSameFloat(0.0, d.intenIntercept)) //give precedence to standard value + d.intenIntercept = d.RWVIntercept; + break; + case kRealWorldSlope: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + d.RWVScale = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + if (d.RWVScale > 1.0E38) + d.RWVScale = 0.0; + else if (isSameFloat(1.0, d.intenScale)) //give precedence to standard value + d.intenScale = d.RWVScale; + break; + case kUserDefineDataGE: { //0043,102A + if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 128)) + break; +#define MY_DEBUG_GE // <- uncomment this to use following code to infer GE phase encoding direction +#ifdef MY_DEBUG_GE + int isVerboseX = isVerbose; //for debugging only - in standard release we will enable user defined "isVerbose" + //int isVerboseX = 2; + if (isVerboseX > 1) + printMessage(" UserDefineDataGE file offset/length %ld %u\n", lFileOffset + lPos, lLength); + if (lLength < 916) { //minimum size is hdr_offset=0, read 0x0394 + printMessage(" GE header too small to be valid (A)\n"); + break; + } + //debug code to export binary data + /* + char str[kDICOMStr]; + sprintf(str, "%s_ge.bin",fname); + FILE *pFile = fopen(str, "wb"); + fwrite(&buffer[lPos], 1, lLength, pFile); + fclose (pFile); + */ + if ((size_t)(lPos + lLength) > MaxBufferSz) { + //we could re-read the buffer in this case, however in practice GE headers are concise so we never see this issue + printMessage(" GE header overflows buffer\n"); + break; + } + uint16_t hdr_offset = dcmInt(2, &buffer[lPos + 24], true); + if (isVerboseX > 1) + printMessage(" header offset: %d\n", hdr_offset); + if (lLength < (hdr_offset + 916)) { //minimum size is hdr_offset=0, read 0x0394 + printMessage(" GE header too small to be valid (B)\n"); + break; + } + //size_t hdr = lPos+hdr_offset; + float version = dcmFloat(4, &buffer[lPos + hdr_offset], true); + if (isVerboseX > 1) + printMessage(" version %g\n", version); + if (version < 5.0 || version > 40.0) { + //printMessage(" GE header file format incorrect %g\n", version); + break; + } + //char const *hdr = &buffer[lPos + hdr_offset]; + char *hdr = (char *)&buffer[lPos + hdr_offset]; + int epi_chk_off = 0x003a; + int pepolar_off = 0x0030; + int kydir_off = 0x0394; + if (version >= 25.002) { + hdr += 0x004c; + kydir_off -= 0x008c; + } + //int seqOrInter =dcmInt(2,(unsigned char*)(hdr + pepolar_off-638),true); + //int seqOrInter2 =dcmInt(2,(unsigned char*)(hdr + kydir_off-638),true); + //printf("%d %d<<<\n", seqOrInter,seqOrInter2); + //check if EPI + if (true) { + //int check = *(short const *)(hdr + epi_chk_off) & 0x800; + int check = dcmInt(2, (unsigned char *)hdr + epi_chk_off, true) & 0x800; + if (check == 0) { + if (isVerboseX > 1) + printMessage("%s: Warning: Data is not EPI\n", fname); + break; + } + } + //Check for PE polarity + // int flag1 = *(short const *)(hdr + pepolar_off) & 0x0004; + //Check for ky direction (view order) + // int flag2 = *(int const *)(hdr + kydir_off); + int phasePolarityFlag = dcmInt(2, (unsigned char *)hdr + pepolar_off, true) & 0x0004; + //Check for ky direction (view order) + int sliceOrderFlag = dcmInt(2, (unsigned char *)hdr + kydir_off, true); + if (isVerboseX > 1) + printMessage(" GE phasePolarity/sliceOrder flags %d %d\n", phasePolarityFlag, sliceOrderFlag); + if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_FLIPPED) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) { + //https://cfmriweb.ucsd.edu/Howto/3T/operatingtips.html + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + else + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + } +//if (sliceOrderFlag == kGE_SLICE_ORDER_TOP_DOWN) +// d.sliceOrderGE = kGE_SLICE_ORDER_TOP_DOWN; +//if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) +// d.sliceOrderGE = kGE_SLICE_ORDER_BOTTOM_UP; +#endif + break; + } + case kEffectiveEchoSpacingGE: + if (d.manufacturer == kMANUFACTURER_GE) + d.effectiveEchoSpacingGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kImageTypeGE: { //0/1/2/3 for magnitude/phase/real/imaginary + if (d.manufacturer != kMANUFACTURER_GE) + break; + int dt = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + if (dt == 0) + d.isHasMagnitude = true; + if (dt == 1) + d.isHasPhase = true; + if (dt == 2) + d.isHasReal = true; + if (dt == 3) + d.isHasImaginary = true; + break; + } + case kDiffusion_bValueGE: + if (d.manufacturer == kMANUFACTURER_GE) { + d.CSA.dtiV[0] = (float)set_bValGE(&volDiffusion, lLength, &buffer[lPos]); + d.CSA.numDti = 1; + } + break; + case kEpiRTGroupDelayGE: //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.groupDelay = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + d.groupDelay *= 1000.0; //sec -> ms + // If kEpiRTGroupDelayGE (0043,107C) exists, epiRT + d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT + break; + case kAssetRFactorsGE: { //DS issue427GE + if (d.manufacturer != kMANUFACTURER_GE) + break; + float PhaseSlice[3]; + dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, PhaseSlice); + if (PhaseSlice[1] > 0.0) + d.accelFactPE = 1.0f / PhaseSlice[1]; + if (PhaseSlice[2] > 0.0) + d.accelFactOOP = 1.0f / PhaseSlice[2]; + break; + } + case kASLContrastTechniqueGE: { //CS + if (d.manufacturer != kMANUFACTURER_GE) + break; + char st[kDICOMStr]; + //aslFlags + dcmStr(lLength, &buffer[lPos], st); + if (strstr(st, "PSEUDOCONTINUOUS") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PSEUDOCONTINUOUS); + else if (strstr(st, "CONTINUOUS") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_CONTINUOUS); + break; + } + case kASLLabelingTechniqueGE: { //LO issue427GE + if (d.manufacturer != kMANUFACTURER_GE) + break; + char st[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], st); + if (strstr(st, "3D continuous") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DCASL); + if (strstr(st, "3D pulsed continuous") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DPCASL); + break; + } + case kDurationLabelPulseGE: { //IS + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.durationLabelPulseGE = dcmStrInt(lLength, &buffer[lPos]); + break; + } + case kMultiBandGE: { //LO issue427GE + if (d.manufacturer != kMANUFACTURER_GE) + break; + //LO array: Value 1 = Multiband factor, Value 2 = Slice FOV Shift Factor, Value 3 = Calibration method + int mb = dcmStrInt(lLength, &buffer[lPos]); + if (mb > 1) + d.CSA.multiBandFactor = mb; + break; + } + case kGeiisFlag: + if ((lLength > 4) && (buffer[lPos] == 'G') && (buffer[lPos + 1] == 'E') && (buffer[lPos + 2] == 'I') && (buffer[lPos + 3] == 'I')) { + //read a few digits, as bug is specific to GEIIS, while GEMS are fine + printWarning("GEIIS violates the DICOM standard. Inspect results and admonish your vendor.\n"); + isIconImageSequence = true; + if (sqDepthIcon < 0) + sqDepthIcon = sqDepth; + //geiisBug = true; //compressed thumbnails do not follow transfer syntax! GE should not re-use pulbic tags for these proprietary images http://sonca.kasshin.net/gdcm/Doc/GE_ImageThumbnails + } + break; + case kStudyComments: { + //char commentStr[kDICOMStr]; + //dcmStr(lLength, &buffer[lPos], commentStr); + //printf(">> %s\n", commentStr); + break; + } + case kProcedureStepDescription: + dcmStr(lLength, &buffer[lPos], d.procedureStepDescription); + break; + case kOrientationACR: //use in emergency if kOrientation is not present! + if (!isOrient) + dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient); + break; + case kOrientation: { + if (isOrient) { //already read orient - read for this slice to see if it varies (localizer) + float orient[7]; + dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, orient); + if ((!isSameFloatGE(d.orient[1], orient[1]) || !isSameFloatGE(d.orient[2], orient[2]) || !isSameFloatGE(d.orient[3], orient[3]) || + !isSameFloatGE(d.orient[4], orient[4]) || !isSameFloatGE(d.orient[5], orient[5]) || !isSameFloatGE(d.orient[6], orient[6]))) { + if (!d.isLocalizer) + printMessage("slice orientation varies (localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n", + d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6], + orient[1], orient[2], orient[3], orient[4], orient[5], orient[6]); + d.isLocalizer = true; + } + } + dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient); + vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]); + vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]); + sliceV = crossProduct(readV, phaseV); + //printf("sliceV %g %g %g\n", sliceV.v[0], sliceV.v[1], sliceV.v[2]); + isOrient = true; + break; + } + case kTemporalPosition: //fall through, both kSliceNumberMrPhilips (2001,100A) and kTemporalPosition are is + volumeNumber = dcmStrInt(lLength, &buffer[lPos]); + //temporalPositionIdentifier = volumeNumber; + break; + case kTemporalResolution: + temporalResolutionMS = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kImagesInAcquisition: + imagesInAcquisition = dcmStrInt(lLength, &buffer[lPos]); + break; + //case kSliceLocation : //optional so useless, infer from image position patient (0020,0032) and image orientation (0020,0037) + // sliceLocation = dcmStrFloat(lLength, &buffer[lPos]); + // break; + case kImageStart: + //if ((!geiisBug) && (!isIconImageSequence)) //do not exit for proprietary thumbnails + if (isIconImageSequence) { + //20200116 see example from Tashrif Bilah that saves GEIIS thumbnails uncompressed + // therefore, the next couple lines are not a perfect detection for GEIIS thumbnail icons + //int imgBytes = (d.xyzDim[1] * d.xyzDim[2] * int(d.bitsAllocated / 8)); + //if (imgBytes == lLength) + // isIconImageSequence = false; + if ((isIconImageSequence) && (sqDepth < 1)) + printWarning("Assuming 7FE0,0010 refers to an icon not the main image\n"); + } + if ((d.compressionScheme == kCompressNone) && (!isIconImageSequence)) //do not exit for proprietary thumbnails + d.imageStart = (int)lPos + (int)lFileOffset; + //geiisBug = false; + //http://www.dclunie.com/medical-image-faq/html/part6.html + //unlike raw data, Encapsulated data is stored as Fragments contained in Items that are the Value field of Pixel Data + if ((d.compressionScheme != kCompressNone) && (!isIconImageSequence)) { + isEncapsulatedData = true; + encapsulatedDataImageStart = (int)lPos + (int)lFileOffset; + lLength = 0; + } + isIconImageSequence = false; + break; + case kImageStartFloat: + d.isFloat = true; + if (!isIconImageSequence) //do not exit for proprietary thumbnails + d.imageStart = (int)lPos + (int)lFileOffset; + isIconImageSequence = false; + break; + case kImageStartDouble: + printWarning("Double-precision DICOM conversion untested: please provide samples to developer\n"); + d.isFloat = true; + if (!isIconImageSequence) //do not exit for proprietary thumbnails + d.imageStart = (int)lPos + (int)lFileOffset; + isIconImageSequence = false; + break; + } //switch/case for groupElement + if ((((groupElement >> 8) & 0xFF) == 0x60) && (groupElement % 2 == 0) && ((groupElement & 0xFF) < 0x1E)) { //Group 60xx: OverlayGroup http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.9.html + //even group numbers 0x6000..0x601E + int overlayN = ((groupElement & 0xFF) >> 1); + //printf("%08x %d %d\n", groupElement, (groupElement & 0xFF), overlayN); + int element = groupElement >> 16; + switch (element) { + case 0x0010: //US OverlayRows + overlayRows = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case 0x0011: //US OverlayColumns + overlayCols = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case 0x0050: { //SSx2! OverlayOrigin + if (lLength != 4) + break; + int row = dcmInt(2, &buffer[lPos], d.isLittleEndian); + int col = dcmInt(2, &buffer[lPos + 2], d.isLittleEndian); + if ((row == 1) && (col == 1)) + break; + printMessage("Unsupported overlay origin %d/%d\n", row, col); + overlayOK = false; + break; + } + case 0x0100: { //US OverlayBitsAllocated + int bits = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + if (bits == 1) + break; + //old style Burned-In + printMessage("Illegal/Obsolete DICOM: Overlay Bits Allocated must be 1, not %d\n", bits); + overlayOK = false; + break; + } + case 0x0102: { //US OverlayBitPosition + int pos = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + if (pos == 0) + break; + //old style Burned-In + printMessage("Illegal/Obsolete DICOM: Overlay Bit Position shall be 0, not %d\n", pos); + overlayOK = false; + break; + } + case 0x3000: { + d.overlayStart[overlayN] = (int)lPos + (int)lFileOffset; + d.isHasOverlay = true; + break; + } + } + } //Group 60xx even values 0x6000..0x601E https://www.medicalconnections.co.uk/kb/Number-Of-Overlays-In-Image/ +#ifndef USING_R + if (isVerbose > 1) { + //dcm2niix i fast because it does not use a dictionary. + // this is a very incomplete DICOM header report, and not a substitute for tools like dcmdump + // the purpose is to see how dcm2niix has parsed the image for diagnostics + // this section will report very little for implicit data + //if (d.isHasReal) printf("r");else printf("m"); + char str[kDICOMStr]; + sprintf(str, "%*c%04x,%04x %u@%ld ", sqDepth + 1, ' ', groupElement & 65535, groupElement >> 16, lLength, lFileOffset + lPos); + bool isStr = false; + if (d.isExplicitVR) { + sprintf(str, "%s%c%c ", str, vr[0], vr[1]); + if ((vr[0] == 'F') && (vr[1] == 'D')) + sprintf(str, "%s%g ", str, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'F') && (vr[1] == 'L')) + sprintf(str, "%s%g ", str, dcmFloat(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'S') && (vr[1] == 'S')) + sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'S') && (vr[1] == 'L')) + sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'U') && (vr[1] == 'S')) + sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'U') && (vr[1] == 'L')) + sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'A') && (vr[1] == 'E')) + isStr = true; + if ((vr[0] == 'A') && (vr[1] == 'S')) + isStr = true; + if ((vr[0] == 'C') && (vr[1] == 'S')) + isStr = true; + if ((vr[0] == 'D') && (vr[1] == 'A')) + isStr = true; + if ((vr[0] == 'D') && (vr[1] == 'S')) + isStr = true; + if ((vr[0] == 'D') && (vr[1] == 'T')) + isStr = true; + if ((vr[0] == 'I') && (vr[1] == 'S')) + isStr = true; + if ((vr[0] == 'L') && (vr[1] == 'O')) + isStr = true; + if ((vr[0] == 'L') && (vr[1] == 'T')) + isStr = true; + //if ((vr[0]=='O') && (vr[1]=='B')) isStr = xxx; + //if ((vr[0]=='O') && (vr[1]=='D')) isStr = xxx; + //if ((vr[0]=='O') && (vr[1]=='F')) isStr = xxx; + //if ((vr[0]=='O') && (vr[1]=='W')) isStr = xxx; + if ((vr[0] == 'P') && (vr[1] == 'N')) + isStr = true; + if ((vr[0] == 'S') && (vr[1] == 'H')) + isStr = true; + if ((vr[0] == 'S') && (vr[1] == 'T')) + isStr = true; + if ((vr[0] == 'T') && (vr[1] == 'M')) + isStr = true; + if ((vr[0] == 'U') && (vr[1] == 'I')) + isStr = true; + if ((vr[0] == 'U') && (vr[1] == 'T')) + isStr = true; + } else + isStr = (lLength > 12); //implicit encoding: not always true as binary vectors may exceed 12 bytes, but often true + if (lLength > 128) { + sprintf(str, "%s<%d bytes> ", str, lLength); + printMessage("%s\n", str); + } else if (isStr) { //if length is greater than 8 bytes (+4 hdr) the MIGHT be a string + char tagStr[kDICOMStr]; + //tagStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr + strcpy(tagStr, ""); + if (lLength > 0) + dcmStr(lLength, &buffer[lPos], tagStr); + if (strlen(tagStr) > 1) { + for (size_t pos = 0; pos < strlen(tagStr); pos++) + if ((tagStr[pos] == '<') || (tagStr[pos] == '>') || (tagStr[pos] == ':') || (tagStr[pos] == '"') || (tagStr[pos] == '\\') || (tagStr[pos] == '/') || (tagStr[pos] < 32) //issue398 + //|| (tagStr[pos] == '^') || (tagStr[pos] < 33) + || (tagStr[pos] == '*') || (tagStr[pos] == '|') || (tagStr[pos] == '?')) + tagStr[pos] = '_'; + } + printMessage("%s %s\n", str, tagStr); + } else + printMessage("%s\n", str); + //if (d.isExplicitVR) printMessage(" VR=%c%c\n", vr[0], vr[1]); + } //printMessage(" tag=%04x,%04x length=%u pos=%ld %c%c nest=%d\n", groupElement & 65535,groupElement>>16, lLength, lPos,vr[0], vr[1], nest); +#endif + lPos = lPos + (lLength); + } //while d.imageStart == 0 + free(buffer); + if (d.bitsStored < 0) + d.isValid = false; + if (d.bitsStored == 1) + printWarning("1-bit binary DICOMs not supported\n"); //maybe not valid - no examples to test + //printf("%d bval=%g bvec=%g %g %g<<<\n", d.CSA.numDti, d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); + //printMessage("><>< DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); + if (encapsulatedDataFragmentStart > 0) { + if ((encapsulatedDataFragments > 1) && (encapsulatedDataFragments == numberOfFrames) && (encapsulatedDataFragments < kMaxDTI4D)) { + printWarning("Compressed image stored as %d fragments: if conversion fails decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname); + d.imageStart = encapsulatedDataFragmentStart; + } else if (encapsulatedDataFragments > 1) { + printError("Compressed image stored as %d fragments: decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname); + } else { + d.imageStart = encapsulatedDataFragmentStart; + //dti4D->fragmentOffset[0] = -1; + } + } else if ((isEncapsulatedData) && (d.imageStart < 128)) { + //http://www.dclunie.com/medical-image-faq/html/part6.html + //Uncompressed data (unencapsulated) is sent in DICOM as a series of raw bytes or words (little or big endian) in the Value field of the Pixel Data element (7FE0,0010). Encapsulated data on the other hand is sent not as raw bytes or words but as Fragments contained in Items that are the Value field of Pixel Data + printWarning("DICOM violation (contact vendor): compressed image without image fragments, assuming image offset defined by 0x7FE0,x0010: %s\n", fname); + d.imageStart = encapsulatedDataImageStart; + } + if ((d.manufacturer == kMANUFACTURER_GE) && (d.groupDelay > 0.0)) + d.TR += d.groupDelay; //Strangely, for GE the sample rate is (0018,0080) + ((0043,107c) * 1000.0) + if ((d.modality == kMODALITY_PT) && (PETImageIndex > 0)) { + d.imageNum = PETImageIndex; //https://github.com/rordenlab/dcm2niix/issues/184 + //printWarning("PET scan using 0054,1330 for image number %d\n", PETImageIndex); + } + if (d.isHasOverlay) { + if ((overlayCols > 0) && (d.xyzDim[1] != overlayCols)) + overlayOK = false; + if ((overlayRows > 0) && (d.xyzDim[2] != overlayRows)) + overlayOK = false; + if (!overlayOK) + d.isHasOverlay = false; + } +//Recent Philips images include DateTime (0008,002A) but not separate date and time (0008,0022 and 0008,0032) +#define kYYYYMMDDlen 8 //how many characters to encode year,month,day in "YYYYDDMM" format + if ((strlen(acquisitionDateTimeTxt) > (kYYYYMMDDlen + 5)) && (!isFloatDiff(d.acquisitionTime, 0.0f)) && (!isFloatDiff(d.acquisitionDate, 0.0f))) { + // 20161117131643.80000 -> date 20161117 time 131643.80000 + //printMessage("acquisitionDateTime %s\n",acquisitionDateTimeTxt); + char acquisitionDateTxt[kDICOMStr]; + memcpy(acquisitionDateTxt, acquisitionDateTimeTxt, kYYYYMMDDlen); + acquisitionDateTxt[kYYYYMMDDlen] = '\0'; // IMPORTANT! + d.acquisitionDate = atof(acquisitionDateTxt); + char acquisitionTimeTxt[kDICOMStr]; + int timeLen = (int)strlen(acquisitionDateTimeTxt) - kYYYYMMDDlen; + strncpy(acquisitionTimeTxt, &acquisitionDateTimeTxt[kYYYYMMDDlen], timeLen); + acquisitionTimeTxt[timeLen] = '\0'; // IMPORTANT! + d.acquisitionTime = atof(acquisitionTimeTxt); + } + d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime); + //printMessage("slices in Acq %d %d %g %g\n",locationsInAcquisitionGE, d.locationsInAcquisition, d.xyzMM[3], d.zSpacing); + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.locationsInAcquisition == 0)) + d.locationsInAcquisition = locationsInAcquisitionPhilips; + if ((d.manufacturer == kMANUFACTURER_GE) && (imagesInAcquisition > 0)) + d.locationsInAcquisition = imagesInAcquisition; //e.g. if 72 slices acquired but interpolated as 144 + if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition > 0) && (locationsInAcquisitionGE > 0) && (d.locationsInAcquisition != locationsInAcquisitionGE)) { + if (isVerbose) + printMessage("Check number of slices, discrepancy between tags (0020,1002; 0021,104F; 0054,0081) (%d vs %d) %s\n", locationsInAcquisitionGE, d.locationsInAcquisition, fname); + /* SAH.start: Fix for ZIP2 */ + int zipFactor = (int)roundf(d.xyzMM[3] / d.zSpacing); + if (zipFactor > 1) { + d.interp3D = zipFactor; + //printMessage("Issue 373: Check for ZIP2 Factor: %d SliceThickness+SliceGap: %f, SpacingBetweenSlices: %f \n", zipFactor, d.xyzMM[3], d.zSpacing); + locationsInAcquisitionGE *= zipFactor; // Multiply number of slices by ZIP factor. Do this prior to checking for conflict below (?). + } + if (isGEfieldMap) { //issue501 : to do check zip factor + //Volume 1) derived phase field map [Hz] and 2) magnitude volume. + d.isDerived = (d.imageNum <= locationsInAcquisitionGE); //first volume + d.isRealIsPhaseMapHz = d.isDerived; + d.isHasReal = d.isDerived; + } + /* SAH.end */ + if (locationsInAcquisitionGE < d.locationsInAcquisition) { + d.locationsInAcquisitionConflict = d.locationsInAcquisition; + d.locationsInAcquisition = locationsInAcquisitionGE; + } else + d.locationsInAcquisitionConflict = locationsInAcquisitionGE; + } + if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition == 0)) + d.locationsInAcquisition = locationsInAcquisitionGE; + if (d.zSpacing > 0.0) + d.xyzMM[3] = d.zSpacing; //use zSpacing if provided: depending on vendor, kZThick may or may not include a slice gap + //printMessage("patientPositions = %d XYZT = %d slicePerVol = %d numberOfDynamicScans %d\n",patientPositionNum,d.xyzDim[3], d.locationsInAcquisition, d.numberOfDynamicScans); + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (patientPositionNum > d.xyzDim[3])) { + d.CSA.numDti = d.xyzDim[3]; //issue506 + printMessage("Please check slice thicknesses: Philips R3.2.2 bug can disrupt estimation (%d positions reported for %d slices)\n", patientPositionNum, d.xyzDim[3]); //Philips reported different positions for each slice! + } + if ((d.imageStart > 144) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1)) + d.isValid = true; + //if ((d.imageStart > 144) && (d.xyzDim[1] >= 1) && (d.xyzDim[2] >= 1) && (d.xyzDim[4] > 1)) //Spectroscopy + // d.isValid = true; + if ((d.xyzMM[1] > FLT_EPSILON) && (d.xyzMM[2] < FLT_EPSILON)) { + printMessage("Please check voxel size\n"); + d.xyzMM[2] = d.xyzMM[1]; + } + if ((d.xyzMM[2] > FLT_EPSILON) && (d.xyzMM[1] < FLT_EPSILON)) { + printMessage("Please check voxel size\n"); + d.xyzMM[1] = d.xyzMM[2]; + } + if ((d.xyzMM[3] < FLT_EPSILON)) { + printMessage("Unable to determine slice thickness: please check voxel size\n"); + d.xyzMM[3] = 1.0; + } + //printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3]); + //printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\tStart\t%d\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3], d.imageStart); + // printMessage("ser %ld\n", d.seriesNum); + //int kEchoMult = 100; //For Siemens/GE Series 1,2,3... save 2nd echo as 201, 3rd as 301, etc + //if (d.seriesNum > 100) + // kEchoMult = 10; //For Philips data Saved as Series 101,201,301... save 2nd echo as 111, 3rd as 121, etc + //if (coilNum > 0) //segment images with multiple coils + // d.seriesNum = d.seriesNum + (100*coilNum); + //if (d.echoNum > 1) //segment images with multiple echoes + // d.seriesNum = d.seriesNum + (kEchoMult*d.echoNum); + if (isPaletteColor) { + d.isValid = false; + d.isDerived = true; //to my knowledge, palette images always derived + printWarning("Photometric Interpretation 'PALETTE COLOR' not supported\n"); + } + if ((d.compressionScheme == kCompress50) && (d.bitsAllocated > 8)) { + //dcmcjpg with +ee can create .51 syntax images that are 8,12,16,24-bit: we can only decode 8/24-bit + printError("Unable to decode %d-bit images with Transfer Syntax 1.2.840.10008.1.2.4.51, decompress with dcmdjpg or gdcmconv\n", d.bitsAllocated); + d.isValid = false; + } + if ((isMosaic) && (d.CSA.mosaicSlices < 1) && (numberOfImagesInMosaic < 1) && (!isInterpolated) && (d.phaseEncodingLines > 0) && (frequencyRows > 0) && ((d.xyzDim[1] % frequencyRows) == 0) && ((d.xyzDim[1] / frequencyRows) > 2) && ((d.xyzDim[2] % d.phaseEncodingLines) == 0) && ((d.xyzDim[2] / d.phaseEncodingLines) > 2)) { + //n.b. in future check if frequency is in row or column direction (and same with phase) + // >2 avoids detecting interpolated as mosaic, in future perhaps check "isInterpolated" + numberOfImagesInMosaic = (d.xyzDim[1] / frequencyRows) * (d.xyzDim[2] / d.phaseEncodingLines); + printWarning("Guessing this is a mosaic up to %d slices (issue 337).\n", numberOfImagesInMosaic); + } + if ((numberOfImagesInMosaic > 1) && (d.CSA.mosaicSlices < 1)) + d.CSA.mosaicSlices = numberOfImagesInMosaic; + if (d.isXA10A) + d.manufacturer = kMANUFACTURER_SIEMENS; //XA10A mosaics omit Manufacturer 0008,0070! + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (isMosaic) && (d.CSA.mosaicSlices < 1) && (d.phaseEncodingSteps > 0) && ((d.xyzDim[1] % d.phaseEncodingSteps) == 0) && ((d.xyzDim[2] % d.phaseEncodingSteps) == 0)) { + d.CSA.mosaicSlices = (d.xyzDim[1] / d.phaseEncodingSteps) * (d.xyzDim[2] / d.phaseEncodingSteps); + printWarning("Mosaic inferred without CSA header (check number of slices and spatial orientation)\n"); + } + if ((d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) + d.CSA.mosaicSlices = -1; //mark as bogus DICOM + if ((!d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) //See Erlangen Vida dataset - never reports "XA10" but mosaics have no attributes + printWarning("0008,0008=MOSAIC but number of slices not specified: %s\n", fname); + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.dtiV[1] < -1.0) && (d.CSA.dtiV[2] < -1.0) && (d.CSA.dtiV[3] < -1.0)) + d.CSA.dtiV[0] = 0; //SiemensTrio-Syngo2004A reports B=0 images as having impossible b-vectors. + if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1)) + strcpy(d.protocolName, d.seriesDescription); + if ((strlen(d.protocolName) > 1) && (isMoCo)) + strcat(d.protocolName, "_MoCo"); //disambiguate MoCo https://github.com/neurolabusc/MRIcroGL/issues/31 + if ((strlen(d.protocolName) < 1) && (strlen(d.sequenceName) > 1) && (d.manufacturer != kMANUFACTURER_SIEMENS)) + strcpy(d.protocolName, d.sequenceName); //protocolName (0018,1030) optional, sequence name (0018,0024) is not a good substitute for Siemens as it can vary per volume: *ep_b0 *ep_b1000#1, *ep_b1000#2, etc https://www.nitrc.org/forum/forum.php?thread_id=8771&forum_id=4703 + if (numberOfFrames == 0) + numberOfFrames = d.xyzDim[3]; + if ((locationsInAcquisitionPhilips > 0) && ((d.xyzDim[3] % locationsInAcquisitionPhilips) == 0)) { + d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips; + d.xyzDim[3] = locationsInAcquisitionPhilips; + } else if ((numberOfDynamicScans > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % numberOfDynamicScans) == 0)) { + d.xyzDim[3] = d.xyzDim[3] / numberOfDynamicScans; + d.xyzDim[4] = numberOfDynamicScans; + } + if ((maxInStackPositionNumber > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % maxInStackPositionNumber) == 0)) { + d.xyzDim[4] = d.xyzDim[3] / maxInStackPositionNumber; + d.xyzDim[3] = maxInStackPositionNumber; + } + if ((!isnan(patientPositionStartPhilips[1])) && (!isnan(patientPositionEndPhilips[1]))) { + for (int k = 0; k < 4; k++) { + d.patientPosition[k] = patientPositionStartPhilips[k]; + d.patientPositionLast[k] = patientPositionEndPhilips[k]; + } + } + if ((numberOfFrames > 1) && (locationsInAcquisitionPhilips > 0) && ((numberOfFrames % locationsInAcquisitionPhilips) != 0)) { //issue515 + printWarning("Number of frames (%d) not divisible by locations in acquisition (2001,1018) %d (issue 515)\n", numberOfFrames, locationsInAcquisitionPhilips); + d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips; + d.xyzDim[3] = locationsInAcquisitionPhilips; + d.xyzDim[0] = numberOfFrames; + } + if ((B0Philips >= 0) && (d.CSA.numDti == 0)) { + d.CSA.dtiV[0] = B0Philips; + d.CSA.numDti = 1; + } //issue409 Siemens XA saved as classic 2D not enhanced + if (!isnan(patientPositionStartPhilips[1])) //for Philips data without + for (int k = 0; k < 4; k++) + d.patientPosition[k] = patientPositionStartPhilips[k]; + if (isVerbose) { + printMessage("DICOM file: %s\n", fname); + printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1], d.patientPosition[2], d.patientPosition[3]); + if (!isnan(patientPositionEndPhilips[1])) + printMessage(" patient position end (0020,0032)\t%g\t%g\t%g\n", patientPositionEndPhilips[1], patientPositionEndPhilips[2], patientPositionEndPhilips[3]); + printMessage(" orient (0020,0037)\t%g\t%g\t%g\t%g\t%g\t%g\n", d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6]); + printMessage(" acq %d img %d ser %ld dim %dx%dx%dx%d mm %gx%gx%g offset %d loc %d valid %d ph %d mag %d nDTI %d 3d %d bits %d littleEndian %d echo %d coilCRC %d TE %g TR %g\n", d.acquNum, d.imageNum, d.seriesNum, d.xyzDim[1], d.xyzDim[2], d.xyzDim[3], d.xyzDim[4], d.xyzMM[1], d.xyzMM[2], d.xyzMM[3], d.imageStart, d.locationsInAcquisition, d.isValid, d.isHasPhase, d.isHasMagnitude, d.CSA.numDti, d.is3DAcq, d.bitsAllocated, d.isLittleEndian, d.echoNum, d.coilCrc, d.TE, d.TR); + if (d.CSA.dtiV[0] > 0) + printMessage(" DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); + } + if ((d.isValid) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1) && (d.imageStart < 132) && (!d.isRawDataStorage)) { + //20190524: Philips MR 55.1 creates non-image files that report kDim1/kDim2 - we can detect them since 0008,0016 reports "RawDataStorage" + //see https://neurostars.org/t/dcm2niix-error-from-philips-dicom-qsm-data-can-this-be-skipped/4883 + printError("Conversion aborted due to corrupt file: %s %dx%d %d\n", fname, d.xyzDim[1], d.xyzDim[2], d.imageStart); +#ifdef USING_R + Rf_error("Irrecoverable error during conversion"); +#else + exit(kEXIT_CORRUPT_FILE_FOUND); +#endif + } + if ((numberOfFrames > 1) && (numDimensionIndexValues == 0) && (numberOfFrames == nSliceMM)) { //issue 372 + fidx *objects = (fidx *)malloc(sizeof(struct fidx) * numberOfFrames); + for (int i = 0; i < numberOfFrames; i++) { + objects[i].value = sliceMM[i]; + objects[i].index = i; + } + qsort(objects, numberOfFrames, sizeof(struct fidx), fcmp); + numDimensionIndexValues = numberOfFrames; + for (int i = 0; i < numberOfFrames; i++) { + // printf("%d > %g\n", objects[i].index, objects[i].value); + dcmDim[objects[i].index].dimIdx[0] = i; + } + for (int i = 0; i < 4; i++) { + d.patientPosition[i] = minPatientPosition[i]; + d.patientPositionLast[i] = maxPatientPosition[i]; + } + //printf("%g -> %g\n", objects[0].value, objects[numberOfFrames-1].value); + //printf("%g %g %g -> %g %g %g\n", d.patientPosition[1], d.patientPosition[2], d.patientPosition[3], d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3]); + free(objects); + } //issue 372 + if ((d.echoTrainLength == 0) && (echoTrainLengthPhil)) + d.echoTrainLength = echoTrainLengthPhil; //+1 ?? to convert "EPI factor" to echo train length, see issue 377 + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (minDynamicScanBeginTime < maxDynamicScanBeginTime)) { //issue369 + float TR = 1000.0 * ((maxDynamicScanBeginTime - minDynamicScanBeginTime) / (d.xyzDim[4] - 1)); //-1 : fence post problem + if (fabs(TR - d.TR) > 0.001) { + printWarning("Assuming TR = %gms, not 0018,0080 = %gms (see issue 369)\n", TR, d.TR); + d.TR = TR; + } + } + // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]); + //printf("%g %g\n", minDynamicScanBeginTime, maxDynamicScanBeginTime); + //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (frameAcquisitionDuration > 0.0)) //issue369 + // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]); + if (numDimensionIndexValues > 1) + strcpy(d.imageType, imageType1st); //for multi-frame datasets, return name of book, not name of last chapter + if ((numDimensionIndexValues > 1) && (numDimensionIndexValues == numberOfFrames)) { + //Philips enhanced datasets can have custom slice orders and pack images with different TE, Phase/Magnitude/Etc. + int maxVariableItem = 0; + int nVariableItems = 0; + if (true) { // + int mn[MAX_NUMBER_OF_DIMENSIONS]; + int mx[MAX_NUMBER_OF_DIMENSIONS]; + for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) { + mx[j] = dcmDim[0].dimIdx[j]; + mn[j] = mx[j]; + for (int i = 0; i < numDimensionIndexValues; i++) { + if (mx[j] < dcmDim[i].dimIdx[j]) + mx[j] = dcmDim[i].dimIdx[j]; + if (mn[j] > dcmDim[i].dimIdx[j]) + mn[j] = dcmDim[i].dimIdx[j]; + } + if (mx[j] != mn[j]) { + maxVariableItem = j; + nVariableItems++; + } + } + if (isVerbose > 1) { + printMessage(" DimensionIndexValues (0020,9157), dimensions with variability:\n"); + for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) + if (mn[i] != mx[i]) + printMessage(" Dimension %d Range: %d..%d\n", i, mn[i], mx[i]); + } + } //verbose > 1 + //see http://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.8.24.3.3.html + //Philips puts spatial position as lower item than temporal position, the reverse is true for Bruker and Canon + int stackPositionItem = 0; + if (dimensionIndexPointerCounter > 0) + for (size_t i = 0; i < dimensionIndexPointerCounter; i++) + if (dimensionIndexPointer[i] == kInStackPositionNumber) + stackPositionItem = i; + if ((d.manufacturer == kMANUFACTURER_CANON) && (nVariableItems == 1) && (d.xyzDim[4] > 1)) { + //WARNING: Canon CANON V6.0SP2001* (0018,9005) = "AX fMRI" strangely sets TemporalPositionIndex(0020,9128) as 1 for all volumes: (0020,9157) and (0020,9128) are INCORRECT! + printf("Invalid enhanced DICOM created by Canon: Only single dimension in DimensionIndexValues (0020,9157) varies, for 4D file (e.g. BOTH space and time should vary)\n"); + printf("%d %d\n", stackPositionItem, maxVariableItem); + int stackTimeItem = 0; + if (stackPositionItem == 0) { + maxVariableItem++; + stackTimeItem++; //e.g. slot 0 = space, slot 1 = time + } + int vol = 0; + for (int i = 0; i < numDimensionIndexValues; i++) { + if (1 == dcmDim[i].dimIdx[stackPositionItem]) + vol++; + dcmDim[i].dimIdx[stackTimeItem] = vol; + //printf("vol %d slice %d\n", dcmDim[i].dimIdx[stackTimeItem], dcmDim[i].dimIdx[stackPositionItem]); + } + } //Kuldge for corrupted CANON 0020,9157 + /* //issue533: this code fragment will replicate dicm2nii (reverse order of volume hierarchy) https://github.com/xiangruili/dicm2nii/blob/f918366731162e6895be6e5ee431444642e0e7f9/dicm2nii.m#L2205 + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (stackPositionItem == 1) && ( maxVariableItem > 2)) { + //replicate dicm2nii: s2.DimensionIndexValues([3:end 1]) + uint32_t tmp[MAX_NUMBER_OF_DIMENSIONS]; + for (int i = 0; i < numDimensionIndexValues; i++) { + for (int j = 2; j <= maxVariableItem; j++) + tmp[j] = dcmDim[i].dimIdx[j]; + for (int j = 2; j <= maxVariableItem; j++) + dcmDim[i].dimIdx[j] = tmp[maxVariableItem - j + 2]; + } + }*/ + if ((isKludgeIssue533) && (numDimensionIndexValues > 1)) + printWarning("Guessing temporal order for Philips enhanced DICOM ASL (issue 532).\n"); + //sort dimensions +#ifdef USING_R + if (stackPositionItem < maxVariableItem) + std::sort(dcmDim.begin(), dcmDim.begin() + numberOfFrames, compareTDCMdim); + else + std::sort(dcmDim.begin(), dcmDim.begin() + numberOfFrames, compareTDCMdimRev); +#else + if (stackPositionItem < maxVariableItem) + qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdim); + else + qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdimRev); +#endif + //for (int i = 0; i < numberOfFrames; i++) + // printf("i %d diskPos= %d dimIdx= %d %d %d %d TE= %g\n", i, dcmDim[i].diskPos, dcmDim[i].dimIdx[0], dcmDim[i].dimIdx[1], dcmDim[i].dimIdx[2], dcmDim[i].dimIdx[3], dti4D->TE[i]); + for (int i = 0; i < numberOfFrames; i++) { + dti4D->sliceOrder[i] = dcmDim[i].diskPos; + dti4D->intenScale[i] = dcmDim[i].intenScale; + dti4D->intenIntercept[i] = dcmDim[i].intenIntercept; + dti4D->intenScalePhilips[i] = dcmDim[i].intenScalePhilips; + dti4D->RWVIntercept[i] = dcmDim[i].RWVIntercept; + dti4D->RWVScale[i] = dcmDim[i].RWVScale; + if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) + d.isScaleVariesEnh = true; + if (dti4D->intenScale[i] != dti4D->intenScale[0]) + d.isScaleVariesEnh = true; + if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) + d.isScaleVariesEnh = true; + } + if (!(d.manufacturer == kMANUFACTURER_BRUKER && d.isDiffusion) && (d.xyzDim[4] > 1) && (d.xyzDim[4] < kMaxDTI4D)) { //record variations in TE + d.isScaleOrTEVaries = false; + bool isTEvaries = false; + bool isScaleVaries = false; + //setting j = 1 in next few lines is a hack, just in case TE/scale/intercept listed AFTER dimensionIndexValues + int j = 0; + for (int i = 0; i < d.xyzDim[4]; i++) { + int slice = j + (i * d.xyzDim[3]); + //dti4D->gradDynVol[i] = 0; //only PAR/REC + dti4D->TE[i] = dcmDim[slice].TE; + dti4D->isPhase[i] = dcmDim[slice].isPhase; + dti4D->isReal[i] = dcmDim[slice].isReal; + dti4D->isImaginary[i] = dcmDim[slice].isImaginary; + dti4D->triggerDelayTime[i] = dcmDim[slice].triggerDelayTime; + dti4D->S[i].V[0] = dcmDim[slice].V[0]; + dti4D->S[i].V[1] = dcmDim[slice].V[1]; + dti4D->S[i].V[2] = dcmDim[slice].V[2]; + dti4D->S[i].V[3] = dcmDim[slice].V[3]; + //printf("te=\t%g\tscl=\t%g\tintercept=\t%g\n",dti4D->TE[i], dti4D->intenScale[i],dti4D->intenIntercept[i]); + if ((!isSameFloatGE(dti4D->TE[i], 0.0)) && (dti4D->TE[i] != d.TE)) + isTEvaries = true; + if (dti4D->isPhase[i] != isPhase) + d.isScaleOrTEVaries = true; + if (dti4D->triggerDelayTime[i] != d.triggerDelayTime) + d.isScaleOrTEVaries = true; + if (dti4D->isReal[i] != isReal) + d.isScaleOrTEVaries = true; + if (dti4D->isImaginary[i] != isImaginary) + d.isScaleOrTEVaries = true; + /*Philips can vary intensity scalings for separate slices within a volume! + dti4D->intenScale[i] = dcmDim[slice].intenScale; + dti4D->intenIntercept[i] = dcmDim[slice].intenIntercept; + dti4D->intenScalePhilips[i] = dcmDim[slice].intenScalePhilips; + dti4D->RWVIntercept[i] = dcmDim[slice].RWVIntercept; + dti4D->RWVScale[i] = dcmDim[slice].RWVScale; + if (dti4D->intenScale[i] != d.intenScale) isScaleVaries = true; + if (dti4D->intenIntercept[i] != d.intenIntercept) isScaleVaries = true;*/ + } + if ((isScaleVaries) || (isTEvaries)) + d.isScaleOrTEVaries = true; + if (isTEvaries) + d.isMultiEcho = true; + //if echoVaries,count number of echoes + /*int echoNum = 1; + for (int i = 1; i < d.xyzDim[4]; i++) { + if (dti4D->TE[i-1] != dti4D->TE[i]) + }*/ + if ((isVerbose) && (d.isScaleOrTEVaries)) { + printMessage("Parameters vary across 3D volumes packed in single DICOM file:\n"); + for (int i = 0; i < d.xyzDim[4]; i++) { + int slice = (i * d.xyzDim[3]); + printMessage(" %d TE=%g Slope=%g Inter=%g PhilipsScale=%g Phase=%d\n", i, dti4D->TE[i], dti4D->intenScale[slice], dti4D->intenIntercept[slice], dti4D->intenScalePhilips[slice], dti4D->isPhase[i]); + } + } + } + if ((d.xyzDim[3] == maxInStackPositionNumber) && (maxInStackPositionNumber > 1) && (d.zSpacing <= 0.0)) { + float dx = sqrt(pow(d.patientPosition[1] - d.patientPositionLast[1], 2) + + pow(d.patientPosition[2] - d.patientPositionLast[2], 2) + + pow(d.patientPosition[3] - d.patientPositionLast[3], 2)); + dx = dx / (maxInStackPositionNumber - 1); + if ((dx > 0.0) && (!isSameFloatGE(dx, d.xyzMM[3]))) //patientPosition has some rounding error + d.xyzMM[3] = dx; + } //d.zSpacing <= 0.0: Bruker does not populate 0018,0088 https://github.com/rordenlab/dcm2niix/issues/241 + } //if numDimensionIndexValues > 1 : enhanced DICOM + if (d.CSA.numDti >= kMaxDTI4D) { + printError("Unable to convert DTI [recompile with increased kMaxDTI4D] detected=%d, max = %d\n", d.CSA.numDti, kMaxDTI4D); + d.CSA.numDti = 0; + } + if ((hasDwiDirectionality) && (d.CSA.numDti < 1)) + d.CSA.numDti = 1; + if ((d.isValid) && (d.imageNum == 0)) { //Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers + //TYPE 1 for MR image http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,0013) + // Only type 2 for some other DICOMs! Therefore, generate warning not error + printWarning("Instance number (0020,0013) not found: %s\n", fname); + d.imageNum = abs((int)d.instanceUidCrc) % 2147483647; //INT_MAX; + if (d.imageNum == 0) + d.imageNum = 1; //https://github.com/rordenlab/dcm2niix/issues/341 + //d.imageNum = 1; //not set + } + if ((numDimensionIndexValues < 1) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.seriesNum > 99999) && (philMRImageDiffBValueNumber > 0)) { + //Ugly kludge to distinguish Philips classic DICOM dti + // images from a single sequence can have identical series number, instance number, gradient number + // the ONLY way to distinguish slices is using the private tag MRImageDiffBValueNumber + // confusingly, the same sequence can also generate MULTIPLE series numbers! + // for examples see https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging + d.seriesNum += (philMRImageDiffBValueNumber * 1000); + } + //if (contentTime != 0.0) && (numDimensionIndexValues < (MAX_NUMBER_OF_DIMENSIONS - 1)){ + // uint_32t timeCRC = mz_crc32X((unsigned char*) &contentTime, sizeof(double)); + //} + //if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strcmp(d.sequenceName, "fldyn3d1")== 0)) { + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fldyn3d1") != NULL)) { + //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252 + d.isStackableSeries = true; + d.imageNum += (d.seriesNum * 1000); + strcpy(d.seriesInstanceUID, d.studyInstanceUID); + d.seriesUidCrc = mz_crc32X((unsigned char *)&d.protocolName, strlen(d.protocolName)); + } + //TODO533: alias Philips ASL PLD as frameDuration? isKludgeIssue533 + //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408 + // d.triggerDelayTime = 0.0; //Philips ASL use "(0018,9037) CS [NONE]" but "(2001,1010) CS [TRIGGERED]", a situation not described in issue408 + if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime)) //issue395 + d.triggerDelayTime = 0.0; + if (d.phaseNumber > 0) //Philips TurboQUASAR set this uniquely for each slice + d.triggerDelayTime = 0.0; + //printf("%d\t%g\t%g\t%g\n", d.imageNum, d.acquisitionTime, d.triggerDelayTime, MRImageDynamicScanBeginTime); + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strlen(seriesTimeTxt) > 1) && (d.isXA10A) && (d.xyzDim[3] == 1) && (d.xyzDim[4] < 2)) { + //printWarning(">>Ignoring series number of XA data saved as classic DICOM (issue 394)\n"); + d.isStackableSeries = true; + d.imageNum += (d.seriesNum * 1000); + strcpy(d.seriesInstanceUID, seriesTimeTxt); // dest <- src + d.seriesUidCrc = mz_crc32X((unsigned char *)&seriesTimeTxt, strlen(seriesTimeTxt)); + } + if (((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON)) && (B0Philips > 0.0)) { //issue 388 + char txt[1024] = {""}; + sprintf(txt, "b=%d(", (int)round(B0Philips)); + if (strstr(d.imageComments, txt) != NULL) { + //printf("%s>>>%s %g\n", txt, d.imageComments, B0Philips); + int len = strlen(txt); + strcpy(txt, (char *)&d.imageComments[len]); + len = strlen(txt); + for (int i = 0; i <= len; i++) { + if ((txt[i] >= '0') && (txt[i] <= '9')) + continue; + if ((txt[i] == '.') || (txt[i] == '-')) + continue; + txt[i] = ' '; + } + float v[4]; + dcmMultiFloat(len, (char *)&txt[0], 3, &v[0]); + d.CSA.dtiV[0] = B0Philips; +#ifdef swizzleCanon //see issue422 and dcm_qa_canon + d.CSA.dtiV[1] = v[2]; + d.CSA.dtiV[2] = v[1]; + d.CSA.dtiV[3] = -v[3]; +#else + d.CSA.dtiV[1] = v[2]; + d.CSA.dtiV[2] = v[1]; + d.CSA.dtiV[3] = v[3]; + d.manufacturer = kMANUFACTURER_CANON; +#endif + //d.CSA.dtiV[1] = v[1]; + //d.CSA.dtiV[2] = v[2]; + //d.CSA.dtiV[3] = v[3]; + d.CSA.numDti = 1; + } + } + if ((isDICOMANON) && (isMATLAB)) { + //issue 383 + strcpy(d.seriesInstanceUID, d.studyDate); + // This check is unlikely to be important in practice, but it silences a warning from GCC with -Wrestrict + if (strlen(d.studyDate) < kDICOMStr) { + strncat(d.seriesInstanceUID, d.studyTime, kDICOMStr - strlen(d.studyDate)); + } + d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); + } + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fl2d1") != NULL)) { + d.isLocalizer = true; + } + //detect pepolar https://github.com/nipy/heudiconv/issues/479 + if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 1)) + d.epiVersionGE = kGE_EPI_PEPOLAR_REV; + if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 2)) + d.epiVersionGE = kGE_EPI_PEPOLAR_REV_FWD; + if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 3)) + d.epiVersionGE = kGE_EPI_PEPOLAR_FWD_REV; + if ((d.epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD) && (volumeNumber > 0) && ((volumeNumber % 2) == 1)) + d.epiVersionGE = kGE_EPI_PEPOLAR_REV_FWD_FLIP; + if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV) && (volumeNumber > 0) && ((volumeNumber % 2) == 0)) + d.epiVersionGE = kGE_EPI_PEPOLAR_FWD_REV_FLIP; + #ifndef myDisableGEPEPolarFlip //e.g. to disable patch for issue 532 "make CFLAGS=-DmyDisableGEPEPolarFlip" + if ((d.epiVersionGE == kGE_EPI_PEPOLAR_REV) || (d.epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV_FLIP) || (d.epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD_FLIP)) { + if (d.epiVersionGE != kGE_EPI_PEPOLAR_REV) d.seriesNum += 1000; + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + else if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + } + #endif + //UIH 3D T1 scans report echo train length, which is interpreted as 3D EPI + if ((d.manufacturer == kMANUFACTURER_UIH) && (strstr(d.sequenceName, "gre_fsp") != NULL)) + d.echoTrainLength = 0; + //printf(">>%s\n", d.sequenceName); d.isValid = false; + // Andrey Fedorov has requested keeping GE bvalues, see issue 264 + //if ((d.CSA.numDti > 0) && (d.manufacturer == kMANUFACTURER_GE) && (d.numberOfDiffusionDirectionGE < 1)) + // d.CSA.numDti = 0; //https://github.com/rordenlab/dcm2niix/issues/264 + if ((!d.isLocalizer) && (isInterpolated) && (d.imageNum <= 1)) + printWarning("interpolated protocol '%s' may be unsuitable for dwidenoise/mrdegibbs. %s\n", d.protocolName, fname); + if (((numDimensionIndexValues + 3) < MAX_NUMBER_OF_DIMENSIONS) && (d.rawDataRunNumber > 0)) + d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 4] = d.rawDataRunNumber; + if ((numDimensionIndexValues + 2) < MAX_NUMBER_OF_DIMENSIONS) + d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 3] = d.instanceUidCrc; + if ((numDimensionIndexValues + 1) < MAX_NUMBER_OF_DIMENSIONS) + d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 2] = d.echoNum; + if (numDimensionIndexValues < MAX_NUMBER_OF_DIMENSIONS) //https://github.com/rordenlab/dcm2niix/issues/221 + d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); + if ((d.isValid) && (d.seriesUidCrc == 0)) { + if (d.seriesNum < 1) + d.seriesUidCrc = 1; //no series information + else + d.seriesUidCrc = d.seriesNum; //file does not have Series UID, use series number instead + } + if (d.seriesNum < 1) //https://github.com/rordenlab/dcm2niix/issues/218 + d.seriesNum = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); + getFileName(d.imageBaseName, fname); + if (multiBandFactor > d.CSA.multiBandFactor) + d.CSA.multiBandFactor = multiBandFactor; //SMS reported in 0051,1011 but not CSA header +#ifndef myLoadWholeFileToReadHeader + fclose(file); +#endif + if ((temporalResolutionMS > 0.0) && (isSameFloatGE(d.TR, temporalResolutionMS))) { + //do something profound + //in practice 0020,0110 not used + //https://github.com/bids-standard/bep001/blob/repetitiontime/Proposal_RepetitionTime.md + } + //issue 542 + if ((d.manufacturer == kMANUFACTURER_GE) && (isNeologica) && (!isSameFloat(d.CSA.dtiV[0], 0.0f)) && ((isSameFloat(d.CSA.dtiV[1], 0.0f)) && (isSameFloat(d.CSA.dtiV[2], 0.0f)) && (isSameFloat(d.CSA.dtiV[3], 0.0f)) ) ) + printWarning("GE DWI vectors may have been removed by Neologica DICOM Anonymizer Pro (Issue 542)\n"); + //start: issue529 TODO JJJJ + if ((!isSameFloat(d.CSA.dtiV[0], 0.0f)) && ((isSameFloat(d.CSA.dtiV[1], 0.0f)) && (isSameFloat(d.CSA.dtiV[2], 0.0f)) && (isSameFloat(d.CSA.dtiV[3], 0.0f)) ) ) + gradientOrientationNumberPhilips = kMaxDTI4D + 1; //Philips includes derived Trace/ADC images into raw DTI, these should be removed... + //if (sliceNumberMrPhilips == 1) printf("instance\t %d\ttime\t%g\tvolume\t%d\tgradient\t%d\tphase\t%d\tisLabel\t%d\n", d.imageNum, MRImageDynamicScanBeginTime, volumeNumber, gradientOrientationNumberPhilips, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL); + //if (sliceNumberMrPhilips == 1) printf("ACQtime\t%g\tinstance\t %d\ttime\t%g\tvolume\t%d\tphase\t%d\tisLabel\t%d\n", d.acquisitionTime, d.imageNum, MRImageDynamicScanBeginTime, volumeNumber, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL); + //if (sliceNumberMrPhilips == 1) printf("%d\t%d\t%g\t%d\n", philMRImageDiffBValueNumber, gradientOrientationNumberPhilips, d.CSA.dtiV[0], philMRImageDiffVolumeNumber); //issue546 + d.phaseNumber = (d.phaseNumber > philMRImageDiffBValueNumber) ? d.phaseNumber : philMRImageDiffBValueNumber; //we need both BValueNumber(2005,1412) and GradientOrientationNumber(2005,1413) to resolve volumes: issue546 + d.rawDataRunNumber = (d.rawDataRunNumber > volumeNumber) ? d.rawDataRunNumber : volumeNumber; + d.rawDataRunNumber = (d.rawDataRunNumber > gradientOrientationNumberPhilips) ? d.rawDataRunNumber : gradientOrientationNumberPhilips; + if ((d.rawDataRunNumber < 0) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (nDimIndxVal > 1) && (d.dimensionIndexValues[nDimIndxVal - 1] > 0)) + d.rawDataRunNumber = d.dimensionIndexValues[nDimIndxVal - 1]; //Philips enhanced scans converted to classic with dcuncat + if (philMRImageDiffVolumeNumber > 0) { //use 2005,1596 for Philips DWI >= R5.6 + d.rawDataRunNumber = philMRImageDiffVolumeNumber; + d.phaseNumber = 0; + } + // d.rawDataRunNumber = (d.rawDataRunNumber > d.phaseNumber) ? d.rawDataRunNumber : d.phaseNumber; //will not work: conflict for MultiPhase ASL with multiple averages + //end: issue529 + if (hasDwiDirectionality) + d.isVectorFromBMatrix = false; //issue 265: Philips/Siemens have both directionality and bmatrix, Bruker only has bmatrix + //printf("%s\t%s\t%s\t%s\t%s_%s\n",d.patientBirthDate, d.procedureStepDescription,d.patientName, fname, d.studyDate, d.studyTime); + //d.isValid = false; + //printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]); + //printf("%d\t%g\t%g\t%g\t%g\n", d.imageNum, d.rtia_timerGE, d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]); + //printf("%g\t\t%g\t%g\t%g\t%s\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3], fname); + //printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz); + return d; +} // readDICOM() + +void setDefaultPrefs(struct TDCMprefs *prefs) { + prefs->isVerbose = false; + prefs->compressFlag = kCompressSupport; + prefs->isIgnoreTriggerTimes = false; +} + +struct TDICOMdata readDICOMv(char *fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { + struct TDCMprefs prefs; + setDefaultPrefs(&prefs); + prefs.isVerbose = isVerbose; + prefs.compressFlag = compressFlag; + TDICOMdata ret = readDICOMx(fname, &prefs, dti4D); + return ret; +} + +struct TDICOMdata readDICOM(char *fname) { + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); //unused + TDICOMdata ret = readDICOMv(fname, false, kCompressSupport, dti4D); + free(dti4D); + return ret; +} // readDICOM() diff --git a/packages/dcm2niix/nii_dicom.h b/packages/dcm2niix/nii_dicom.h new file mode 100644 index 00000000000..800d29e247a --- /dev/null +++ b/packages/dcm2niix/nii_dicom.h @@ -0,0 +1,269 @@ +#include //requires VS 2015 or later +#include +#include +#include "nifti1_io_core.h" +#ifndef USING_R +#include "nifti1.h" +#endif + +#ifndef MRIpro_nii_dcm_h + +#define MRIpro_nii_dcm_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + + #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) + #define kLSsuf " (JP-LS:CharLS)" + #else + #define kLSsuf "" + #endif + #ifdef myEnableJasper + #define kJP2suf " (JP2:JasPer)" + #else + #ifdef myDisableOpenJPEG + #define kJP2suf "" + #else + #define kJP2suf " (JP2:OpenJPEG)" + #endif + #endif +#if defined(__ICC) || defined(__INTEL_COMPILER) + #define kCCsuf " IntelCC" STR(__INTEL_COMPILER) +#elif defined(_MSC_VER) + #define kCCsuf " MSC" STR(_MSC_VER) +#elif defined(__clang__) + #define kCCsuf " Clang" STR(__clang_major__) "." STR(__clang_minor__) "." STR(__clang_patchlevel__) +#elif defined(__GNUC__) || defined(__GNUG__) + #define kCCsuf " GCC" STR(__GNUC__) "." STR(__GNUC_MINOR__) "." STR(__GNUC_PATCHLEVEL__) +#else + #define kCCsuf " CompilerNA" //unknown compiler! +#endif +#if defined(__arm__) || defined(__ARM_ARCH) + #define kCPUsuf " ARM" +#elif defined(__x86_64) + #define kCPUsuf " x86-64" +#else + #define kCPUsuf " " //unknown CPU +#endif + +#define kDCMdate "v1.0.20211006" +#define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf + +static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic +static const int kMaxSlice2D = 65535; //issue460 maximum number of 2D slices in 4D (Philips) images +static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI directions for 4D (Philips) images, also maximum number of 2D slices for Enhanced DICOM and PAR/REC + +#define kDICOMStr 66 //64 characters plus NULL https://github.com/rordenlab/dcm2niix/issues/268 +#define kDICOMStrLarge 256 + +#define kMANUFACTURER_UNKNOWN 0 +#define kMANUFACTURER_SIEMENS 1 +#define kMANUFACTURER_GE 2 +#define kMANUFACTURER_PHILIPS 3 +#define kMANUFACTURER_TOSHIBA 4 +#define kMANUFACTURER_UIH 5 +#define kMANUFACTURER_BRUKER 6 +#define kMANUFACTURER_HITACHI 7 +#define kMANUFACTURER_CANON 8 +#define kMANUFACTURER_MEDISO 9 + +//note: note a complete modality list, e.g. XA,PX, etc +#define kMODALITY_UNKNOWN 0 +#define kMODALITY_CR 1 +#define kMODALITY_CT 2 +#define kMODALITY_MR 3 +#define kMODALITY_PT 4 +#define kMODALITY_US 5 + +// PartialFourierDirection 0018,9036 +#define kPARTIAL_FOURIER_DIRECTION_UNKNOWN 0 +#define kPARTIAL_FOURIER_DIRECTION_PHASE 1 +#define kPARTIAL_FOURIER_DIRECTION_FREQUENCY 2 +#define kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT 3 +#define kPARTIAL_FOURIER_DIRECTION_COMBINATION 4 + +//GE EPI settings +#define kGE_EPI_UNKNOWN -1 +#define kGE_EPI_EPI 0 +#define kGE_EPI_EPIRT 1 +#define kGE_EPI_EPI2 2 +#define kGE_EPI_PEPOLAR_FWD 3 +#define kGE_EPI_PEPOLAR_REV 4 +#define kGE_EPI_PEPOLAR_REV_FWD 5 +#define kGE_EPI_PEPOLAR_FWD_REV 6 +#define kGE_EPI_PEPOLAR_REV_FWD_FLIP 7 +#define kGE_EPI_PEPOLAR_FWD_REV_FLIP 8 + + +//GE phase encoding +#define kGE_PHASE_ENCODING_POLARITY_UNKNOWN -1 +#define kGE_PHASE_ENCODING_POLARITY_UNFLIPPED 0 +#define kGE_PHASE_ENCODING_POLARITY_FLIPPED 4 +#define kGE_SLICE_ORDER_UNKNOWN -1 +#define kGE_SLICE_ORDER_TOP_DOWN 0 +#define kGE_SLICE_ORDER_BOTTOM_UP 2 + + +//#define kGE_PHASE_DIRECTION_CENTER_OUT_REV 3 +//#define kGE_PHASE_DIRECTION_CENTER_OUT 4 + +//EXIT_SUCCESS 0 +//EXIT_FAILURE 1 +#define kEXIT_NO_VALID_FILES_FOUND 2 +#define kEXIT_REPORT_VERSION 3 +#define kEXIT_CORRUPT_FILE_FOUND 4 +#define kEXIT_INPUT_FOLDER_INVALID 5 +#define kEXIT_OUTPUT_FOLDER_INVALID 6 +#define kEXIT_OUTPUT_FOLDER_READ_ONLY 7 +#define kEXIT_SOME_OK_SOME_BAD 8 +#define kEXIT_RENAME_ERROR 9 +#define kEXIT_INCOMPLETE_VOLUMES_FOUND 10 //issue 515 +#define kEXIT_NOMINAL 11 //did not expect to convert files + +//0043,10A3 ---: PSEUDOCONTINUOUS +//0043,10A4 ---: 3D pulsed continuous ASL technique +#define kASL_FLAG_NONE 0 +#define kASL_FLAG_GE_3DPCASL 1 +#define kASL_FLAG_GE_3DCASL 2 +#define kASL_FLAG_GE_PSEUDOCONTINUOUS 4 +#define kASL_FLAG_GE_CONTINUOUS 8 +#define kASL_FLAG_PHILIPS_CONTROL 16 +#define kASL_FLAG_PHILIPS_LABEL 32 + + +//for spoiling 0018,9016 +#define kSPOILING_UNKOWN -1 +#define kSPOILING_NONE 0 +#define kSPOILING_RF 1 +#define kSPOILING_GRADIENT 2 +#define kSPOILING_RF_AND_GRADIENT 3 + +static const int kSliceOrientUnknown = 0; +static const int kSliceOrientTra = 1; +static const int kSliceOrientSag = 2; +static const int kSliceOrientCor = 3; +static const int kSliceOrientMosaicNegativeDeterminant = 4; +static const int kCompressNone = 0; +static const int kCompressYes = 1; +static const int kCompressC3 = 2; //obsolete JPEG lossless +static const int kCompress50 = 3; //obsolete JPEG lossy +static const int kCompressRLE = 4; //run length encoding +static const int kCompressPMSCT_RLE1 = 5; //see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1 +static const int kCompressJPEGLS = 6; //LoCo JPEG-LS +static const int kMaxOverlay = 16; //even group values 0x6000..0x601E +#ifdef myEnableJasper + static const int kCompressSupport = kCompressYes; //JASPER for JPEG2000 +#else + #ifdef myDisableOpenJPEG + static const int kCompressSupport = kCompressNone; //no decompressor + #else + static const int kCompressSupport = kCompressYes; //OPENJPEG for JPEG2000 + #endif +#endif + +// Maximum number of dimensions for .dimensionIndexValues, i.e. possibly the +// number of axes in the output .nii. +static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; + struct TDTI { + float V[4]; + //int totalSlicesIn4DOrder; + }; + struct TDTI4D { + struct TDTI S[kMaxDTI4D]; + int sliceOrder[kMaxSlice2D]; // [7,3,2] means the first slice on disk should be moved to 7th position + int gradDynVol[kMaxDTI4D]; //used to parse dimensions of Philips data, e.g. file with multiple dynamics, echoes, phase+magnitude + //int fragmentOffset[kMaxDTI4D], fragmentLength[kMaxDTI4D]; //for images with multiple compressed fragments + float frameDuration[kMaxDTI4D], decayFactor[kMaxDTI4D], volumeOnsetTime[kMaxDTI4D], triggerDelayTime[kMaxDTI4D], TE[kMaxDTI4D], RWVScale[kMaxDTI4D], RWVIntercept[kMaxDTI4D], intenScale[kMaxDTI4D], intenIntercept[kMaxDTI4D], intenScalePhilips[kMaxDTI4D]; + bool isReal[kMaxDTI4D]; + bool isImaginary[kMaxDTI4D]; + bool isPhase[kMaxDTI4D]; + float repetitionTimeExcitation, repetitionTimeInversion; + }; + +#ifdef _MSC_VER //Microsoft nomenclature for packed structures is different... + #pragma pack(2) + typedef struct { + char name[64]; //null-terminated + int32_t vm; + char vr[4]; // possibly nul-term string + int32_t syngodt;// ?? + int32_t nitems;// number of items in CSA + int32_t xx;// maybe == 77 or 205 + } TCSAtag; //Siemens csa tag structure + typedef struct { + int32_t xx1, xx2_Len, xx3_77, xx4; + } TCSAitem; //Siemens csa item structure + #pragma pack() +#else + typedef struct __attribute__((packed)) { + char name[64]; //null-terminated + int32_t vm; + char vr[4]; // possibly nul-term string + int32_t syngodt;// ?? + int32_t nitems;// number of items in CSA + int32_t xx;// maybe == 77 or 205 + } TCSAtag; //Siemens csa tag structure + typedef struct __attribute__((packed)) { + int32_t xx1, xx2_Len, xx3_77, xx4; + } TCSAitem; //Siemens csa item structure +#endif + struct TCSAdata { + float sliceTiming[kMaxEPI3D], dtiV[4], sliceNormV[4], bandwidthPerPixelPhaseEncode, sliceMeasurementDuration; + int numDti, SeriesHeader_offset, SeriesHeader_length, multiBandFactor, sliceOrder, slice_start, slice_end, mosaicSlices, protocolSliceNumber1, phaseEncodingDirectionPositive; + bool isPhaseMap; + + }; + struct TDICOMdata { + long seriesNum; + int xyzDim[5]; + uint32_t coilCrc, seriesUidCrc, instanceUidCrc; + int overlayStart[kMaxOverlay]; + int phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; + float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; + float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; + float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) + float frameDuration, ecat_isotope_halflife, ecat_dosage; + float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. + double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; + char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; + char imageOrientationText[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageType[kDICOMStr], institutionalDepartmentName[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr], accessionNumber[kDICOMStr], seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr],sequenceVariant[kDICOMStr],scanningSequence[kDICOMStr], patientBirthDate[kDICOMStr], patientAge[kDICOMStr], studyDate[kDICOMStr],studyTime[kDICOMStr]; + char institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; + uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; + struct TCSAdata CSA; + bool isRealIsPhaseMapHz, isPrivateCreatorRemap, isHasOverlay, isEPI, isIR, isPartialFourier, isDiffusion, isVectorFromBMatrix, isRawDataStorage, isGrayscaleSoftcopyPresentationState, isStackableSeries, isCoilVaries, isNonParallelSlices, isBVecWorldCoordinates, isSegamiOasis, isXA10A, isScaleOrTEVaries, isScaleVariesEnh, isDerived, isXRay, isMultiEcho, isValid, is3DAcq, is2DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase, isHasImaginary, isHasReal, isHasMagnitude,isHasMixed, isFloat, isResampled, isLocalizer; + char phaseEncodingRC, patientSex; + }; + struct TDCMprefs { + int isVerbose, compressFlag, isIgnoreTriggerTimes; + }; + + size_t nii_ImgBytes(struct nifti_1_header hdr); + void setDefaultPrefs (struct TDCMprefs *prefs); + int isSameFloatGE (float a, float b); + void getFileNameX( char *pathParent, const char *path, int maxLen); + struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D); + struct TDICOMdata readDICOMx(char * fname, struct TDCMprefs* prefs, struct TDTI4D *dti4D); + + struct TDICOMdata readDICOM(char * fname); + struct TDICOMdata clear_dicom_data(void); + struct TDICOMdata nii_readParRec (char * parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase); + unsigned char * nii_flipY(unsigned char* bImg, struct nifti_1_header *h); + unsigned char *nii_flipImgY(unsigned char *bImg, struct nifti_1_header *hdr); + unsigned char * nii_flipZ(unsigned char* bImg, struct nifti_1_header *h); + //*unsigned char * nii_reorderSlices(unsigned char* bImg, struct nifti_1_header *h, struct TDTI4D *dti4D); + void changeExt (char *file_name, const char* ext); + unsigned char * nii_planar2rgb(unsigned char* bImg, struct nifti_1_header *hdr, int isPlanar); + int isDICOMfile(const char * fname); //0=not DICOM, 1=DICOM, 2=NOTSURE(not part 10 compliant) + void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose); + int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose); + int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm) ; + unsigned char * nii_loadImgXL(char* imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/dcm2niix/nii_dicom_batch.cpp b/packages/dcm2niix/nii_dicom_batch.cpp new file mode 100644 index 00000000000..ebb088bbed2 --- /dev/null +++ b/packages/dcm2niix/nii_dicom_batch.cpp @@ -0,0 +1,8148 @@ +//#define myNoSave //do not save images to disk +#ifdef _MSC_VER +#include +#define getcwd _getcwd +#define chdir _chrdir +#include "io.h" +//#include +#define MiniZ +#else +#include +#ifdef myDisableMiniZ +#undef MiniZ +#else +#define MiniZ +#endif +#endif + +#if defined(__APPLE__) && defined(__MACH__) +#endif +#ifndef myDisableZLib +#ifdef MiniZ +#include "miniz.c" //single file clone of libz +#else +#include +#endif +#else +#undef MiniZ +#endif +#include "tinydir.h" +#include "print.h" +#include "nifti1_io_core.h" +#ifndef USING_R +#include "nifti1.h" +#endif +#include "nii_dicom_batch.h" +#ifndef USING_R +#include "nii_foreign.h" +#endif +#include "nii_dicom.h" +#include "nii_ortho.h" +#include //toupper +#include +#include +#include //requires VS 2015 or later +#include +#include +#include +#include +#include +#include +#include // clock_t, clock, CLOCKS_PER_SEC +#if defined(_WIN64) || defined(_WIN32) +#include //write to registry +#endif +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#if defined(_WIN64) || defined(_WIN32) +const char kPathSeparator = '\\'; +const char kFileSep[2] = "\\"; +#else +const char kPathSeparator = '/'; +const char kFileSep[2] = "/"; +#endif + +#ifdef USING_R +#include "ImageList.h" + +#undef isnan +#define isnan ISNAN + +#undef isfinite +#define isfinite R_FINITE +#endif + +#define newTilt + +#ifdef USING_R + +#ifndef max +#define max(a, b) std::max(a, b) +#endif + +#ifndef min +#define min(a, b) std::min(a, b) +#endif + +#else + +#ifndef max +#define max(a, b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) +#endif + +#ifndef min +#define min(a, b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) +#endif + +#endif + +#ifdef MGH_FREESURFER +MRIFSSTRUCT mrifsStruct; + +MRIFSSTRUCT* nii_getMrifsStruct() +{ + return &mrifsStruct; +} + +void nii_clrMrifsStruct() +{ + free(mrifsStruct.imgM); + free(mrifsStruct.tdti); +} +#endif + +bool isADCnotDTI(TDTI bvec) { //returns true if bval!=0 but all bvecs == 0 (Philips code for derived ADC image) + return ((!isSameFloat(bvec.V[0], 0.0f)) && //not a B-0 image + ((isSameFloat(bvec.V[1], 0.0f)) && (isSameFloat(bvec.V[2], 0.0f)) && (isSameFloat(bvec.V[3], 0.0f)))); +} + +struct TDCMsort { + uint64_t indx, img; + uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; +}; + +struct TSearchList { + unsigned long numItems, maxItems; + char **str; +}; + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +void dropFilenameFromPath(char *path) { + const char *dirPath = strrchr(path, '/'); //UNIX + if (dirPath == 0) + dirPath = strrchr(path, '\\'); //Windows + if (dirPath == NULL) { + strcpy(path, ""); + } else + path[dirPath - path] = 0; // please make sure there is enough space in TargetDirectory + if (strlen(path) == 0) { //file name did not specify path, assume relative path and return current working directory + //strcat (path,"."); //relative path - use cwd <- not sure if this works on Windows! + char cwd[PATH_MAX]; + char *ok = getcwd(cwd, sizeof(cwd)); + if (ok != NULL) + strcat(path, cwd); + } +} + +void dropTrailingFileSep(char *path) { // + size_t len = strlen(path) - 1; + if (len <= 0) + return; + if (path[len] == '/') + path[len] = '\0'; + else if (path[len] == '\\') + path[len] = '\0'; +} + +bool is_fileexists(const char *filename) { + FILE *fp = NULL; + if ((fp = fopen(filename, "r"))) { + fclose(fp); + return true; + } + return false; +} + +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) +#endif + +#ifndef S_ISREG +#define S_ISREG(mode) (((mode)&S_IFMT) == S_IFREG) +#endif + +bool is_fileNotDir(const char *path) { //returns false if path is a folder; requires #include + struct stat buf; + stat(path, &buf); + return S_ISREG(buf.st_mode); +} //is_file() + +bool is_exe(const char *path) { //requires #include + struct stat buf; + if (stat(path, &buf) != 0) + return false; //file does not eist + if (!S_ISREG(buf.st_mode)) + return false; //not regular file, e.g. '..' + return (buf.st_mode & 0111); + //return (S_ISREG(buf.st_mode) && (buf.st_mode & 0111) ); +} //is_exe() + +#if defined(_WIN64) || defined(_WIN32) +//Windows does not support lstat +int is_dir(const char *pathname, int follow_link) { + struct stat s; + if ((NULL == pathname) || (0 == strlen(pathname))) + return 0; + int err = stat(pathname, &s); + if (-1 == err) + return 0; // does not exist + else { + if (S_ISDIR(s.st_mode)) { + return 1; // it's a dir + } else { + return 0; // exists but is no dir + } + } +} // is_dir() +#else //if windows else Unix +int is_dir(const char *pathname, int follow_link) { + struct stat s; + int retval; + if ((NULL == pathname) || (0 == strlen(pathname))) + return 0; // does not exist + retval = follow_link ? stat(pathname, &s) : lstat(pathname, &s); + if ((-1 != retval) && (S_ISDIR(s.st_mode))) + return 1; // it's a dir + return 0; // exists but is no dir +} // is_dir() +#endif + +void opts2Prefs(struct TDCMopts *opts, struct TDCMprefs *prefs) { + setDefaultPrefs(prefs); + prefs->isVerbose = opts->isVerbose; + prefs->compressFlag = opts->compressFlag; + prefs->isIgnoreTriggerTimes = opts->isIgnoreTriggerTimes; +} + +void geCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isVerbose) { + //0018,1312 phase encoding is either in row or column direction + //0043,1039 (or 0043,a039). b value (as the first number in the string). + //0019,10bb (or 0019,a0bb). phase diffusion direction + //0019,10bc (or 0019,a0bc). frequency diffusion direction + //0019,10bd (or 0019,a0bd). slice diffusion direction + //These directions are relative to freq,phase,slice, so although no + //transformations are required, you need to check the direction of the + //phase encoding. This is in DICOM message 0018,1312. If this has value + //COL then if swap the x and y value and reverse the sign on the z value. + //If the phase encoding is not COL, then just reverse the sign on the x value. + if ((d->manufacturer != kMANUFACTURER_GE) && (d->manufacturer != kMANUFACTURER_CANON)) + return; + if (d->isBVecWorldCoordinates) + return; //Canon classic DICOMs use image space, enhanced use world space! + if ((!d->isEPI) && (d->CSA.numDti == 1)) + d->CSA.numDti = 0; //issue449 + if (d->CSA.numDti < 1) + return; + if ((toupper(d->patientOrient[0]) == 'H') && (toupper(d->patientOrient[1]) == 'F') && (toupper(d->patientOrient[2]) == 'S')) + ; //participant was head first supine + else { + printMessage("GE DTI directions require head first supine acquisition\n"); + return; + } + bool col = false; + if (d->phaseEncodingRC == 'C') + col = true; + else if (d->phaseEncodingRC != 'R') { + printWarning("Unable to determine DTI gradients, 0018,1312 should be either R or C"); + return; + } + if (abs(sliceDir) != 3) + printWarning("Limited validation for non-Axial DTI: confirm gradient vector transformation.\n"); + //GE vectors from Xiangrui Li' dicm2nii, validated with datasets from https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging + ivec3 flp; + if (abs(sliceDir) == 1) + flp = setiVec3(1, 1, 0); //SAGITTAL + else if (abs(sliceDir) == 2) + flp = setiVec3(0, 1, 1); //CORONAL + else if (abs(sliceDir) == 3) + flp = setiVec3(0, 0, 1); //AXIAL + else { + printMessage("Impossible GE slice orientation!"); + flp = setiVec3(0, 0, 1); //AXIAL??? + } + if (sliceDir < 0) + flp.v[2] = 1 - flp.v[2]; + if ((isVerbose) || (!col)) { + printMessage("Saving %d DTI gradients. GE Reorienting %s : please validate. isCol=%d sliceDir=%d flp=%d %d %d\n", d->CSA.numDti, d->protocolName, col, sliceDir, flp.v[0], flp.v[1], flp.v[2]); + if (!col) + printWarning("Reorienting for ROW phase-encoding untested.\n"); + } + bool scaledBValWarning = false; + for (int i = 0; i < d->CSA.numDti; i++) { + float vLen = sqrt((vx[i].V[1] * vx[i].V[1]) + (vx[i].V[2] * vx[i].V[2]) + (vx[i].V[3] * vx[i].V[3])); + if ((vx[i].V[0] <= FLT_EPSILON) || (vLen <= FLT_EPSILON)) { //bvalue=0 + for (int v = 1; v < 4; v++) + vx[i].V[v] = 0.0f; + continue; //do not normalize or reorient 0 vectors + } + if ((vLen > 0.03) && (vLen < 0.97)) { + //bVal scaled by norm(g)^2 issue163,245 + float bValtemp = 0, bVal = 0, bVecScale = 0; + // rounding by 5 with minimum of 5 if b-value > 0 + bValtemp = vx[i].V[0] * (vLen * vLen); + if (bValtemp > 0 && bValtemp < 5) { + bVal = 5; + } else { + bVal = (int)((bValtemp + 2.5f) / 5) * 5; + } + if (bVal == 0) + bVecScale = 0; + else { + bVecScale = sqrt((float)vx[i].V[0] / bVal); + } + if (!scaledBValWarning) { + printMessage("GE BVal scaling (e.g. %g -> %g s/mm^2)\n", vx[i].V[0], bVal); + scaledBValWarning = true; + } + vx[i].V[0] = bVal; + vx[i].V[1] = vx[i].V[1] * bVecScale; + vx[i].V[2] = vx[i].V[2] * bVecScale; + vx[i].V[3] = vx[i].V[3] * bVecScale; + } + if (!col) { //rows need to be swizzled + //see Stanford dataset Ax_DWI_Tetrahedral_7 unable to resolve between possible solutions + // http://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging + float swap = vx[i].V[1]; + vx[i].V[1] = vx[i].V[2]; + vx[i].V[2] = swap; + vx[i].V[2] = -vx[i].V[2]; //because of transpose? + } + for (int v = 0; v < 3; v++) + if (flp.v[v] == 1) + vx[i].V[v + 1] = -vx[i].V[v + 1]; + vx[i].V[2] = -vx[i].V[2]; //we read out Y-direction opposite order as dicm2nii, see also opts.isFlipY + } + //These next lines are only so files appear identical to old versions of dcm2niix: + // dicm2nii and dcm2niix generate polar opposite gradient directions. + // this does not matter, since intensity is the normal of the gradient vector. + for (int i = 0; i < d->CSA.numDti; i++) + for (int v = 1; v < 4; v++) + vx[i].V[v] = -vx[i].V[v]; + //These next lines convert any "-0" values to "0" + for (int i = 0; i < d->CSA.numDti; i++) + for (int v = 1; v < 4; v++) + if (isSameFloat(vx[i].V[v], -0)) + vx[i].V[v] = 0.0f; +} // geCorrectBvecs() + +void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isVerbose) { + //see Matthew Robson's http://users.fmrib.ox.ac.uk/~robson/internal/Dicom2Nifti111.m + //convert DTI vectors from scanner coordinates to image frame of reference + //Uses 6 orient values from ImageOrientationPatient (0020,0037) + // requires PatientPosition 0018,5100 is HFS (head first supine) + if ((!d->isBVecWorldCoordinates) && (d->manufacturer != kMANUFACTURER_BRUKER) && (d->manufacturer != kMANUFACTURER_TOSHIBA) && (d->manufacturer != kMANUFACTURER_HITACHI) && (d->manufacturer != kMANUFACTURER_UIH) && (d->manufacturer != kMANUFACTURER_SIEMENS) && (d->manufacturer != kMANUFACTURER_PHILIPS)) + return; + if (d->CSA.numDti < 1) + return; + if (d->manufacturer == kMANUFACTURER_UIH) { + for (int i = 0; i < d->CSA.numDti; i++) { + vx[i].V[2] = -vx[i].V[2]; + for (int v = 0; v < 4; v++) + if (vx[i].V[v] == -0.0f) + vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero + } +#ifndef USING_R + //simple diagnostics for data prior to realignment: useful as first direction is the same for al Philips sequences + //for (int i = 0; i < 3; i++) + // printf("%g = %g %g %g\n", vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); +#endif + return; + } //https://github.com/rordenlab/dcm2niix/issues/225 + if ((toupper(d->patientOrient[0]) == 'H') && (toupper(d->patientOrient[1]) == 'F') && (toupper(d->patientOrient[2]) == 'S')) + ; //participant was head first supine + else { + printMessage("Check Siemens/Philips bvecs: expected Patient Position (0018,5100) to be 'HFS' not '%s'\n", d->patientOrient); + //return; //see https://github.com/rordenlab/dcm2niix/issues/238 + } + vec3 read_vector = setVec3(d->orient[1], d->orient[2], d->orient[3]); + vec3 phase_vector = setVec3(d->orient[4], d->orient[5], d->orient[6]); + vec3 slice_vector = crossProduct(read_vector, phase_vector); + read_vector = nifti_vect33_norm(read_vector); + phase_vector = nifti_vect33_norm(phase_vector); + slice_vector = nifti_vect33_norm(slice_vector); + for (int i = 0; i < d->CSA.numDti; i++) { + float vLen = sqrt((vx[i].V[1] * vx[i].V[1]) + (vx[i].V[2] * vx[i].V[2]) + (vx[i].V[3] * vx[i].V[3])); + if ((vx[i].V[0] <= FLT_EPSILON) || (vLen <= FLT_EPSILON)) { //bvalue=0 + if (vx[i].V[0] > 5.0) //Philip stores n.b. UIH B=1.25126 Vec=0,0,0 while Philips stored isotropic images + printWarning("Volume %d appears to be derived image ADC/Isotropic (non-zero b-value with zero vector length)\n", i); + continue; //do not normalize or reorient b0 vectors + } //if bvalue=0 + vec3 bvecs_old = setVec3(vx[i].V[1], vx[i].V[2], vx[i].V[3]); + vec3 bvecs_new = setVec3(dotProduct(bvecs_old, read_vector), dotProduct(bvecs_old, phase_vector), dotProduct(bvecs_old, slice_vector)); + bvecs_new = nifti_vect33_norm(bvecs_new); + vx[i].V[1] = bvecs_new.v[0]; + vx[i].V[2] = -bvecs_new.v[1]; + vx[i].V[3] = bvecs_new.v[2]; + if (abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant) + vx[i].V[2] = -vx[i].V[2]; + for (int v = 0; v < 4; v++) + if (vx[i].V[v] == -0.0f) + vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero + } //for each direction + if (d->isVectorFromBMatrix) { + printWarning("Saving %d DTI gradients. Eddy users: B-matrix does not encode b-vector polarity (issue 265).\n", d->CSA.numDti); + } else if (abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant) { + printWarning("Saving %d DTI gradients. Validate vectors (matrix had a negative determinant).\n", d->CSA.numDti); //perhaps Siemens sagittal + } else if ((d->sliceOrient == kSliceOrientTra) || (d->manufacturer != kMANUFACTURER_PHILIPS)) { + if (isVerbose) + printMessage("Saving %d DTI gradients. Validate vectors.\n", d->CSA.numDti); + } else if (d->sliceOrient == kSliceOrientUnknown) + printWarning("Saving %d DTI gradients. Validate vectors (image slice orientation not reported, e.g. 2001,100B).\n", d->CSA.numDti); + if (d->manufacturer == kMANUFACTURER_BRUKER) + printWarning("Bruker DTI support experimental (issue 265).\n"); +} // siemensPhilipsCorrectBvecs() + +bool isNanPosition(struct TDICOMdata d) { //in 2007 some Siemens RGB DICOMs did not include the PatientPosition 0020,0032 tag + if (isnan(d.patientPosition[1])) + return true; + if (isnan(d.patientPosition[2])) + return true; + if (isnan(d.patientPosition[3])) + return true; + return false; +} // isNanPosition() + +bool isSamePosition(struct TDICOMdata d, struct TDICOMdata d2) { + if (isNanPosition(d) || isNanPosition(d2)) + return false; + if (!isSameFloat(d.patientPosition[1], d2.patientPosition[1])) + return false; + if (!isSameFloat(d.patientPosition[2], d2.patientPosition[2])) + return false; + if (!isSameFloat(d.patientPosition[3], d2.patientPosition[3])) + return false; + return true; +} // isSamePosition() + +void nii_saveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, char *dcmname) { + if (!opts.isCreateText) + return; + char txtname[2048] = {""}; + strcpy(txtname, pathoutname); + strcat(txtname, ".txt"); + //printMessage("Saving text %s\n",txtname); + FILE *fp = fopen(txtname, "w"); + fprintf(fp, "%s\tField Strength:\t%g\tProtocolName:\t%s\tScanningSequence00180020:\t%s\tTE:\t%g\tTR:\t%g\tSeriesNum:\t%ld\tAcquNum:\t%d\tImageNum:\t%d\tImageComments:\t%s\tDateTime:\t%f\tName:\t%s\tConvVers:\t%s\tDoB:\t%s\tGender:\t%c\tAge:\t%s\tDimXYZT:\t%d\t%d\t%d\t%d\tCoil:\t%d\tEchoNum:\t%d\tOrient(6)\t%g\t%g\t%g\t%g\t%g\t%g\tbitsAllocated\t%d\tInputName\t%s\n", + pathoutname, d.fieldStrength, d.protocolName, d.scanningSequence, d.TE, d.TR, d.seriesNum, d.acquNum, d.imageNum, d.imageComments, + d.dateTime, d.patientName, kDCMvers, d.patientBirthDate, d.patientSex, d.patientAge, h->dim[1], h->dim[2], h->dim[3], h->dim[4], + d.coilCrc, d.echoNum, d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6], + d.bitsAllocated, dcmname); + fclose(fp); +} // nii_saveText() + +#define myReadAsciiCsa + +#ifdef myReadAsciiCsa +//read from the ASCII portion of the Siemens CSA series header +// this is not recommended: poorly documented +// it is better to stick to the binary portion of the Siemens CSA image header + +#if defined(_WIN64) || defined(_WIN32) || defined(__sun) || (defined(__APPLE__) && defined(__POWERPC__)) +//https://opensource.apple.com/source/Libc/Libc-1044.1.2/string/FreeBSD/memmem.c +/*- + * Copyright (c) 2005 Pascal Gloor + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +const void *memmem(const char *l, size_t l_len, const char *s, size_t s_len) { + char *cur, *last; + const char *cl = (const char *)l; + const char *cs = (const char *)s; + /* we need something to compare */ + if (l_len == 0 || s_len == 0) + return NULL; + /* "s" must be smaller or equal to "l" */ + if (l_len < s_len) + return NULL; + /* special case where s_len == 1 */ + if (s_len == 1) + return memchr(l, (int)*cs, l_len); + /* the last position where its possible to find "s" in "l" */ + last = (char *)cl + l_len - s_len; + for (cur = (char *)cl; cur <= last; cur++) + if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) + return cur; + return NULL; +} +//n.b. memchr returns "const void *" not "void *" for Windows C++ https://msdn.microsoft.com/en-us/library/d7zdhf37.aspx +#endif //for systems without memmem + +int readKeyN1(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value + char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); + if (!keyPos) + return -1; + int ret = 0; + int i = (int)strlen(key); + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if (keyPos[i] >= '0' && keyPos[i] <= '9') + ret = (10 * ret) + keyPos[i] - '0'; + i++; + } + return ret; +} //readKeyN1() //return -1 if key not found + +int readKey(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value + int ret = 0; + char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); + if (!keyPos) + return ret; + int i = (int)strlen(key); + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if (keyPos[i] >= '0' && keyPos[i] <= '9') + ret = (10 * ret) + keyPos[i] - '0'; + i++; + } + return ret; +} //readKey() //return 0 if key not found + +float readKeyFloatNan(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value + char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); + if (!keyPos) + return NAN; + char str[kDICOMStr]; + strcpy(str, ""); + char tmpstr[2]; + tmpstr[1] = 0; + int i = (int)strlen(key); + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if ((keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-')) { + tmpstr[0] = keyPos[i]; + strcat(str, tmpstr); + } + i++; + } + if (strlen(str) < 1) + return NAN; + return atof(str); +} //readKeyFloatNan() + +float readKeyFloat(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value + char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); + if (!keyPos) + return 0.0; + char str[kDICOMStr]; + strcpy(str, ""); + char tmpstr[2]; + tmpstr[1] = 0; + int i = (int)strlen(key); + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if ((keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-')) { + tmpstr[0] = keyPos[i]; + strcat(str, tmpstr); + } + i++; + } + if (strlen(str) < 1) + return 0.0; + return atof(str); +} //readKeyFloat() + +void readKeyStr(const char *key, char *buffer, int remLength, char *outStr) { + //if key is CoilElementID.tCoilID the string 'CoilElementID.tCoilID = ""Head_32""' returns 'Head32' + strcpy(outStr, ""); + char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); + if (!keyPos) + return; + int i = (int)strlen(key); + int outLen = 0; + char tmpstr[2]; + tmpstr[1] = 0; + bool isQuote = false; + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if ((isQuote) && (keyPos[i] != '"') && (outLen < kDICOMStrLarge)) { + tmpstr[0] = keyPos[i]; + strcat(outStr, tmpstr); + outLen++; + } + if (keyPos[i] == '"') { + if (outLen > 0) + break; + isQuote = true; + } + i++; + } +} //readKeyStr() + +int phoenixOffsetCSASeriesHeader(unsigned char *buff, int lLength) { + //returns offset to ASCII Phoenix data + if (lLength < 36) + return 0; + if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0')) + return EXIT_FAILURE; + int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2 + int lnTag = buff[lPos] + (buff[lPos + 1] << 8) + (buff[lPos + 2] << 16) + (buff[lPos + 3] << 24); + if ((buff[lPos + 4] != 77) || (lnTag < 1)) + return 0; + lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0 + TCSAtag tagCSA; + TCSAitem itemCSA; + for (int lT = 1; lT <= lnTag; lT++) { + memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &tagCSA.nitems); + //printf("%d CSA of %s %d\n",lPos, tagCSA.name, tagCSA.nitems); + lPos += sizeof(tagCSA); + if (strcmp(tagCSA.name, "MrPhoenixProtocol") == 0) + return lPos; + for (int lI = 1; lI <= tagCSA.nitems; lI++) { + memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); + lPos += sizeof(itemCSA); + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &itemCSA.xx2_Len); + lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; + } + } + return 0; +} //phoenixOffsetCSASeriesHeader() + +#define kMaxWipFree 64 +typedef struct { + float TE0, TE1, delayTimeInTR, phaseOversampling, phaseResolution, txRefAmp; + int phaseEncodingLines, existUcImageNumb, ucMode, baseResolution, interp, partialFourier, echoSpacing, + difBipolar, parallelReductionFactorInPlane, refLinesPE, combineMode, patMode, ucMTC, accelFact3D; + float alFree[kMaxWipFree]; + float adFree[kMaxWipFree]; + float alTI[kMaxWipFree]; + float dAveragesDouble, dThickness, ulShape, sPositionDTra, sNormalDTra; +} TCsaAscii; + +void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, int csaLength, float *shimSetting, char *coilID, char *consistencyInfo, char *coilElements, char *pulseSequenceDetails, char *fmriExternalInfo, char *protocolName, char *wipMemBlock) { + //reads ASCII portion of CSASeriesHeaderInfo and returns lEchoTrainDuration or lEchoSpacing value + // returns 0 if no value found + csaAscii->TE0 = 0.0; + csaAscii->TE1 = 0.0; + csaAscii->delayTimeInTR = -0.001; + csaAscii->phaseOversampling = 0.0; + csaAscii->phaseResolution = 0.0; + csaAscii->txRefAmp = 0.0; + csaAscii->phaseEncodingLines = 0; + csaAscii->existUcImageNumb = 0; + csaAscii->ucMode = -1; + csaAscii->baseResolution = 0; + csaAscii->interp = 0; + csaAscii->partialFourier = 0; + csaAscii->echoSpacing = 0; + csaAscii->difBipolar = 0; //0=not assigned,1=bipolar,2=monopolar + csaAscii->parallelReductionFactorInPlane = 0; + csaAscii->accelFact3D = 0;//lAccelFact3D + csaAscii->refLinesPE = 0; + csaAscii->combineMode = 0; + csaAscii->patMode = 0; + csaAscii->ucMTC = 0; + for (int i = 0; i < 8; i++) + shimSetting[i] = 0.0; + strcpy(coilID, ""); + strcpy(consistencyInfo, ""); + strcpy(coilElements, ""); + strcpy(pulseSequenceDetails, ""); + strcpy(fmriExternalInfo, ""); + strcpy(wipMemBlock, ""); + strcpy(protocolName, ""); + if ((csaOffset < 0) || (csaLength < 8)) + return; + FILE *pFile = fopen(filename, "rb"); + if (pFile == NULL) + return; + fseek(pFile, 0, SEEK_END); + long lSize = ftell(pFile); + if (lSize < (csaOffset + csaLength)) { + fclose(pFile); + return; + } + fseek(pFile, csaOffset, SEEK_SET); + char *buffer = (char *)malloc(csaLength); + if (buffer == NULL) + return; + size_t result = fread(buffer, 1, csaLength, pFile); + if ((int)result != csaLength) + return; + //next bit complicated: restrict to ASCII portion to avoid buffer overflow errors in BINARY portion + int startAscii = phoenixOffsetCSASeriesHeader((unsigned char *)buffer, csaLength); + int csaLengthTrim = csaLength; + char *bufferTrim = buffer; + if ((startAscii > 0) && (startAscii < csaLengthTrim)) { //ignore binary data at start + bufferTrim += startAscii; + csaLengthTrim -= startAscii; + } + char keyStr[] = "### ASCCONV BEGIN"; //skip to start of ASCII often "### ASCCONV BEGIN ###" but also "### ASCCONV BEGIN object=MrProtDataImpl@MrProtocolData" + char *keyPos = (char *)memmem(bufferTrim, csaLengthTrim, keyStr, strlen(keyStr)); + if (keyPos) { + //We could detect multi-echo MPRAGE here, e.g. "lContrasts = 4"- but ideally we want an earlier detection + csaLengthTrim -= (keyPos - bufferTrim); +//FmriExternalInfo listed AFTER AscConvEnd and uses different delimiter || +// char keyStrExt[] = "FmriExternalInfo"; +// readKeyStr(keyStrExt, keyPos, csaLengthTrim, fmriExternalInfo); +#define myCropAtAscConvEnd +#ifdef myCropAtAscConvEnd + char keyStrEnd[] = "### ASCCONV END"; + char *keyPosEnd = (char *)memmem(keyPos, csaLengthTrim, keyStrEnd, strlen(keyStrEnd)); + if ((keyPosEnd) && ((keyPosEnd - keyPos) < csaLengthTrim)) //ignore binary data at end + csaLengthTrim = (int)(keyPosEnd - keyPos); +#endif + char keyStrLns[] = "sKSpace.lPhaseEncodingLines"; + csaAscii->phaseEncodingLines = readKey(keyStrLns, keyPos, csaLengthTrim); + char keyStrUcImg[] = "sSliceArray.ucImageNumb"; + csaAscii->existUcImageNumb = readKey(keyStrUcImg, keyPos, csaLengthTrim); + char keyStrUcMode[] = "sSliceArray.ucMode"; + csaAscii->ucMode = readKeyN1(keyStrUcMode, keyPos, csaLengthTrim); + char keyStrBase[] = "sKSpace.lBaseResolution"; + csaAscii->baseResolution = readKey(keyStrBase, keyPos, csaLengthTrim); + char keyStrInterp[] = "sKSpace.uc2DInterpolation"; + csaAscii->interp = readKey(keyStrInterp, keyPos, csaLengthTrim); + char keyStrPF[] = "sKSpace.ucPhasePartialFourier"; + csaAscii->partialFourier = readKey(keyStrPF, keyPos, csaLengthTrim); + char keyStrES[] = "sFastImaging.lEchoSpacing"; + csaAscii->echoSpacing = readKey(keyStrES, keyPos, csaLengthTrim); + char keyStrDS[] = "sDiffusion.dsScheme"; + csaAscii->difBipolar = readKey(keyStrDS, keyPos, csaLengthTrim); + if (csaAscii->difBipolar == 0) { + char keyStrROM[] = "ucReadOutMode"; + csaAscii->difBipolar = readKey(keyStrROM, keyPos, csaLengthTrim); + if ((csaAscii->difBipolar >= 1) && (csaAscii->difBipolar <= 2)) { //E11C Siemens/CMRR dsScheme: 1=bipolar, 2=unipolar, B17 CMRR ucReadOutMode 0x1=monopolar, 0x2=bipolar + csaAscii->difBipolar = 3 - csaAscii->difBipolar; + } //https://github.com/poldracklab/fmriprep/pull/1359#issuecomment-448379329 + } + char keyStrAF[] = "sPat.lAccelFactPE"; + csaAscii->parallelReductionFactorInPlane = readKey(keyStrAF, keyPos, csaLengthTrim); + char keyStrAF3D[] = "sPat.lAccelFact3D"; + csaAscii->accelFact3D = readKey(keyStrAF3D, keyPos, csaLengthTrim); + char keyStrRef[] = "sPat.lRefLinesPE"; + csaAscii->refLinesPE = readKey(keyStrRef, keyPos, csaLengthTrim); + char keyStrCombineMode[] = "ucCoilCombineMode"; + csaAscii->combineMode = readKeyN1(keyStrCombineMode, keyPos, csaLengthTrim); + //BIDS CoilCombinationMethod <- Siemens 'Coil Combine Mode' CSA ucCoilCombineMode 1 = Sum of Squares, 2 = Adaptive Combine, + //printf("CoilCombineMode %d\n", csaAscii->combineMode); + char keyStrPATMode[] = "sPat.ucPATMode"; //n.b. field set even if PAT not enabled, e.g. will list SENSE for a R-factor of 1 + csaAscii->patMode = readKeyN1(keyStrPATMode, keyPos, csaLengthTrim); + char keyStrucMTC[] = "sPrepPulses.ucMTC"; //n.b. field set even if PAT not enabled, e.g. will list SENSE for a R-factor of 1 + csaAscii->ucMTC = readKeyN1(keyStrucMTC, keyPos, csaLengthTrim); + //printf("PATMODE %d\n", csaAscii->patMode); + //char keyStrETD[] = "sFastImaging.lEchoTrainDuration"; + //*echoTrainDuration = readKey(keyStrETD, keyPos, csaLengthTrim); + //char keyStrEF[] = "sFastImaging.lEPIFactor"; + //ret = readKey(keyStrEF, keyPos, csaLengthTrim); + char keyStrCoil[] = "sCoilElementID.tCoilID"; + readKeyStr(keyStrCoil, keyPos, csaLengthTrim, coilID); + char keyStrCI[] = "sProtConsistencyInfo.tMeasuredBaselineString"; + readKeyStr(keyStrCI, keyPos, csaLengthTrim, consistencyInfo); + char keyStrCS[] = "sCoilSelectMeas.sCoilStringForConversion"; + readKeyStr(keyStrCS, keyPos, csaLengthTrim, coilElements); + char keyStrSeq[] = "tSequenceFileName"; + readKeyStr(keyStrSeq, keyPos, csaLengthTrim, pulseSequenceDetails); + char keyStrWipMemBlock[] = "sWipMemBlock.tFree"; + readKeyStr(keyStrWipMemBlock, keyPos, csaLengthTrim, wipMemBlock); + char keyStrPn[] = "tProtocolName"; + readKeyStr(keyStrPn, keyPos, csaLengthTrim, protocolName); + char keyStrTE0[] = "alTE[0]"; + csaAscii->TE0 = readKeyFloatNan(keyStrTE0, keyPos, csaLengthTrim); + char keyStrTE1[] = "alTE[1]"; + csaAscii->TE1 = readKeyFloatNan(keyStrTE1, keyPos, csaLengthTrim); + //read ALL alTI[*] values + for (int k = 0; k < kMaxWipFree; k++) + csaAscii->alTI[k] = NAN; + char keyStrTiFree[] = "alTI["; + //check if ANY csaAscii.alFree tags exist + char *keyPosTi = (char *)memmem(keyPos, csaLengthTrim, keyStrTiFree, strlen(keyStrTiFree)); + if (keyPosTi) { + for (int k = 0; k < kMaxWipFree; k++) { + char txt[1024] = {""}; + sprintf(txt, "%s%d]", keyStrTiFree, k); + csaAscii->alTI[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim); + } + } + //read ALL csaAscii.alFree[*] values + for (int k = 0; k < kMaxWipFree; k++) + csaAscii->alFree[k] = 0.0; + char keyStrAlFree[] = "sWipMemBlock.alFree["; + //check if ANY csaAscii.alFree tags exist + char *keyPosFree = (char *)memmem(keyPos, csaLengthTrim, keyStrAlFree, strlen(keyStrAlFree)); + if (keyPosFree) { + for (int k = 0; k < kMaxWipFree; k++) { + char txt[1024] = {""}; + sprintf(txt, "%s%d]", keyStrAlFree, k); + csaAscii->alFree[k] = readKeyFloat(txt, keyPos, csaLengthTrim); + } + } + //read ALL csaAscii.adFree[*] values + for (int k = 0; k < kMaxWipFree; k++) + csaAscii->adFree[k] = NAN; + char keyStrAdFree[50]; + strcpy(keyStrAdFree, "sWipMemBlock.adFree["); + //char keyStrAdFree[] = "sWipMemBlock.adFree["; + //check if ANY csaAscii.adFree tags exist + keyPosFree = (char *)memmem(keyPos, csaLengthTrim, keyStrAdFree, strlen(keyStrAdFree)); + if (!keyPosFree) { //"Wip" -> "WiP", modern -> old Siemens + strcpy(keyStrAdFree, "sWiPMemBlock.adFree["); + keyPosFree = (char *)memmem(keyPos, csaLengthTrim, keyStrAdFree, strlen(keyStrAdFree)); + } + if (keyPosFree) { + for (int k = 0; k < kMaxWipFree; k++) { + char txt[1024] = {""}; + sprintf(txt, "%s%d]", keyStrAdFree, k); + csaAscii->adFree[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim); + } + } + //read labelling plane + char keyStrDThickness[] = "sRSatArray.asElm[1].dThickness"; + csaAscii->dThickness = readKeyFloat(keyStrDThickness, keyPos, csaLengthTrim); + if (csaAscii->dThickness > 0.0) { + char keyStrUlShape[] = "sRSatArray.asElm[1].ulShape"; + csaAscii->ulShape = readKeyFloat(keyStrUlShape, keyPos, csaLengthTrim); + char keyStrSPositionDTra[] = "sRSatArray.asElm[1].sPosition.dTra"; + csaAscii->sPositionDTra = readKeyFloat(keyStrSPositionDTra, keyPos, csaLengthTrim); + char keyStrSNormalDTra[] = "sRSatArray.asElm[1].sNormal.dTra"; + csaAscii->sNormalDTra = readKeyFloat(keyStrSNormalDTra, keyPos, csaLengthTrim); + } + //Read NEX number of averages + char keyStrDAveragesDouble[] = "dAveragesDouble"; + csaAscii->dAveragesDouble = readKeyFloat(keyStrDAveragesDouble, keyPos, csaLengthTrim); + //read delay time + char keyStrDelay[] = "lDelayTimeInTR"; + csaAscii->delayTimeInTR = readKeyFloat(keyStrDelay, keyPos, csaLengthTrim); + char keyStrOver[] = "sKSpace.dPhaseOversamplingForDialog"; + csaAscii->phaseOversampling = readKeyFloat(keyStrOver, keyPos, csaLengthTrim); + char keyStrPhase[] = "sKSpace.dPhaseResolution"; + csaAscii->phaseResolution = readKeyFloat(keyStrPhase, keyPos, csaLengthTrim); + char keyStrAmp[] = "sTXSPEC.asNucleusInfo[0].flReferenceAmplitude"; + csaAscii->txRefAmp = readKeyFloat(keyStrAmp, keyPos, csaLengthTrim); + //lower order shims: newer sequences + char keyStrSh0[] = "sGRADSPEC.asGPAData[0].lOffsetX"; + shimSetting[0] = readKeyFloat(keyStrSh0, keyPos, csaLengthTrim); + char keyStrSh1[] = "sGRADSPEC.asGPAData[0].lOffsetY"; + shimSetting[1] = readKeyFloat(keyStrSh1, keyPos, csaLengthTrim); + char keyStrSh2[] = "sGRADSPEC.asGPAData[0].lOffsetZ"; + shimSetting[2] = readKeyFloat(keyStrSh2, keyPos, csaLengthTrim); + //lower order shims: older sequences + char keyStrSh0s[] = "sGRADSPEC.lOffsetX"; + if (shimSetting[0] == 0.0) + shimSetting[0] = readKeyFloat(keyStrSh0s, keyPos, csaLengthTrim); + char keyStrSh1s[] = "sGRADSPEC.lOffsetY"; + if (shimSetting[1] == 0.0) + shimSetting[1] = readKeyFloat(keyStrSh1s, keyPos, csaLengthTrim); + char keyStrSh2s[] = "sGRADSPEC.lOffsetZ"; + if (shimSetting[2] == 0.0) + shimSetting[2] = readKeyFloat(keyStrSh2s, keyPos, csaLengthTrim); + //higher order shims: older sequences + char keyStrSh3[] = "sGRADSPEC.alShimCurrent[0]"; + shimSetting[3] = readKeyFloat(keyStrSh3, keyPos, csaLengthTrim); + char keyStrSh4[] = "sGRADSPEC.alShimCurrent[1]"; + shimSetting[4] = readKeyFloat(keyStrSh4, keyPos, csaLengthTrim); + char keyStrSh5[] = "sGRADSPEC.alShimCurrent[2]"; + shimSetting[5] = readKeyFloat(keyStrSh5, keyPos, csaLengthTrim); + char keyStrSh6[] = "sGRADSPEC.alShimCurrent[3]"; + shimSetting[6] = readKeyFloat(keyStrSh6, keyPos, csaLengthTrim); + char keyStrSh7[] = "sGRADSPEC.alShimCurrent[4]"; + shimSetting[7] = readKeyFloat(keyStrSh7, keyPos, csaLengthTrim); + } + fclose(pFile); + free(buffer); + return; +} // siemensCsaAscii() + +#endif //myReadAsciiCsa() + +#ifndef myDisableZLib +//Uncomment next line to decode GE Protocol Data Block, for caveats see https://github.com/rordenlab/dcm2niix/issues/163 +#define myReadGeProtocolBlock +#endif +#ifdef myReadGeProtocolBlock +int geProtocolBlock(const char *filename, int geOffset, int geLength, int isVerbose, int *sliceOrder, int *viewOrder, int *mbAccel, int *nSlices, float *groupDelay, char ioptGE[]) { + *sliceOrder = -1; + *viewOrder = 0; + *mbAccel = 0; + *nSlices = 0; + *groupDelay = 0.0; + int ret = EXIT_FAILURE; + if ((geOffset < 0) || (geLength < 20)) + return ret; + FILE *pFile = fopen(filename, "rb"); + if (pFile == NULL) + return ret; + fseek(pFile, 0, SEEK_END); + long lSize = ftell(pFile); + if (lSize < (geOffset + geLength)) { + fclose(pFile); + return ret; + } + fseek(pFile, geOffset, SEEK_SET); + uint8_t *pCmp = (uint8_t *)malloc(geLength); //uint8_t -> mz_uint8 + if (pCmp == NULL) + return ret; + size_t result = fread(pCmp, 1, geLength, pFile); + if ((int)result != geLength) + return ret; + int cmpSz = geLength; + //http://www.forensicswiki.org/wiki/Gzip + // always little endia! http://www.onicos.com/staff/iz/formats/gzip.html + if (cmpSz < 20) + return ret; + if ((pCmp[0] != 31) || (pCmp[1] != 139) || (pCmp[2] != 8)) + return ret; //check signature and deflate algorithm + uint8_t flags = pCmp[3]; + bool isFNAME = ((flags & 0x08) == 0x08); + bool isFCOMMENT = ((flags & 0x10) == 0x10); + uint32_t hdrSz = 10; + if (isFNAME) { //skip null-terminated string FNAME + for (; hdrSz < cmpSz; hdrSz++) + if (pCmp[hdrSz] == 0) + break; + hdrSz++; + } + if (isFCOMMENT) { //skip null-terminated string COMMENT + for (; hdrSz < cmpSz; hdrSz++) + if (pCmp[hdrSz] == 0) + break; + hdrSz++; + } + uint32_t unCmpSz = ((uint32_t)pCmp[cmpSz - 4]) + ((uint32_t)pCmp[cmpSz - 3] << 8) + ((uint32_t)pCmp[cmpSz - 2] << 16) + ((uint32_t)pCmp[cmpSz - 1] << 24); + //printf(">> %d %d %zu %zu %zu\n", isFNAME, isFCOMMENT, cmpSz, unCmpSz, hdrSz); + z_stream s; + memset(&s, 0, sizeof(z_stream)); +#ifdef myDisableMiniZ +#define MZ_DEFAULT_WINDOW_BITS 15 // Window bits +#endif + inflateInit2(&s, -MZ_DEFAULT_WINDOW_BITS); + uint8_t *pUnCmp = (uint8_t *)malloc((size_t)unCmpSz); + s.avail_out = unCmpSz; + s.next_in = pCmp + hdrSz; + s.avail_in = cmpSz - hdrSz - 8; + s.next_out = (uint8_t *)pUnCmp; +#ifdef myDisableMiniZ + ret = inflate(&s, Z_SYNC_FLUSH); + if (ret != Z_STREAM_END) { + free(pUnCmp); + return EXIT_FAILURE; + } +#else + ret = mz_inflate(&s, MZ_SYNC_FLUSH); + if (ret != MZ_STREAM_END) { + free(pUnCmp); + return EXIT_FAILURE; + } +#endif + //https://groups.google.com/forum/#!msg/comp.protocols.dicom/mxnCkv8A-i4/W_uc6SxLwHQJ + // DISCOVERY MR750 / 24\MX\MR Software release:DV24.0_R01_1344.a) are now storing an XML file + // + if ((pUnCmp[0] == '<') && (pUnCmp[1] == '?')) + printWarning("New XML-based GE Protocol Block is not yet supported: please report issue on dcm2niix Github page\n"); + char keyStrSO[] = "SLICEORDER"; + *sliceOrder = readKeyN1(keyStrSO, (char *)pUnCmp, unCmpSz); + char keyStrVO[] = "VIEWORDER"; + *viewOrder = readKey(keyStrVO, (char *)pUnCmp, unCmpSz); + char keyStrMB[] = "MBACCEL"; + *mbAccel = readKey(keyStrMB, (char *)pUnCmp, unCmpSz); + char keyStrNS[] = "NOSLC"; + *nSlices = readKey(keyStrNS, (char *)pUnCmp, unCmpSz); + char keyStrDELACQ[] = "DELACQ"; + char DELACQ[100]; + readKeyStr(keyStrDELACQ, (char *)pUnCmp, unCmpSz, DELACQ); + char keyStrGD[] = "DELACQNOAV"; + *groupDelay = readKeyFloat(keyStrGD, (char *)pUnCmp, unCmpSz); + char keyStrIOPT[] = "IOPT"; + readKeyStr(keyStrIOPT, (char *)pUnCmp, unCmpSz, ioptGE); + char PHASEDELAYS1[10000]; + char keyStrPHASEDELAYS1[] = "PHASEDELAYS1"; + readKeyStr(keyStrPHASEDELAYS1, (char *)pUnCmp, unCmpSz, PHASEDELAYS1); + if (strstr(ioptGE, "MPh") != NULL) { + if (strcmp(DELACQ, "Minimum") == 0) { + *groupDelay = 0; + } + if (strstr(ioptGE, "MPhVar") != NULL) { + *groupDelay = -1; + // Multiphase EPI with Variable Delays + // TO-DO + // NEED TO rescue ALL_PHASES case (=Group delay) + // IF values in PHASEDELAYS1 are all same except 1st value (0), this case should be same as Group Delay + } + } + if (isVerbose > 1) { + printMessage("GE Protocol Block %s bytes %d compressed, %d uncompressed @ %d\n", filename, geLength, unCmpSz, geOffset); + printMessage(" ViewOrder %d SliceOrder %d\n", *viewOrder, *sliceOrder); + printMessage("%s\n", pUnCmp); + } + free(pUnCmp); + return EXIT_SUCCESS; +} +#endif //myReadGeProtocolBlock() + +void json_Str(FILE *fp, const char *sLabel, char *sVal) { // issue131,425 + if (strlen(sVal) < 1) + return; + unsigned char sValEsc[2048] = {""}; + unsigned char *iVal = (unsigned char *)sVal; + int o = 0; + for (int i = 0; i < strlen(sVal); i++) { + //escape double quote (") and Backslash + if ((sVal[i] == '"') || (sVal[i] == '\\')) { //escape double quotes and back slash + sValEsc[o] = '\\'; + o++; + } + if ((sVal[i] >= 0x01) && (sVal[i] <= 0x07)) + continue; //control characters like "bell" + //http://dicom.nema.org/medical/dicom/current/output/html/part05.html + //0x08 Backspace is replaced with \b + //0x09 Tab is replaced with \t + //0x0A Newline is replaced with \n + //0x0B Escape ?? + //0x0C Form feed is replaced with \f + //0x0D Carriage return is replaced with \r + if ((sVal[i] >= 0x08) && (sVal[i] <= 0x0D)) { + sValEsc[o] = '\\'; + o++; + if (sVal[i] == 0x08) + sValEsc[o] = 'b'; + if (sVal[i] == 0x09) + sValEsc[o] = '9'; + if (sVal[i] == 0x0A) + sValEsc[o] = 'n'; + if (sVal[i] == 0x0B) + sValEsc[o] = '\\'; + if (sVal[i] == 0x0C) + sValEsc[o] = 'f'; + if (sVal[i] == 0x0D) + sValEsc[o] = 'r'; + o++; + continue; + } + //https://stackoverflow.com/questions/4059775/convert-iso-8859-1-strings-to-utf-8-in-c-c + if (iVal[i] >= 128) { + sValEsc[o] = 0xc2 + (iVal[i] > 0xbf); + o++; + sValEsc[o] = (iVal[i] & 0x3f) + 0x80; + } else { + sValEsc[o] = sVal[i]; + } + o++; + } + sValEsc[o] = '\0'; + fprintf(fp, sLabel, sValEsc); +} //json_Str + +void json_FloatNotNan(FILE *fp, const char *sLabel, float sVal) { + if (isnan(sVal)) + return; + fprintf(fp, sLabel, sVal); +} //json_Float + +void print_FloatNotNan(const char *sLabel, int iVal, float sVal) { + if (isnan(sVal)) + return; + printMessage(sLabel, iVal, sVal); +} //json_Float + +void json_Float(FILE *fp, const char *sLabel, float sVal) { + if (!isfinite(sVal)) { //isfinite() defined in C99 + // https://github.com/bids-standard/bids-2-devel/issues/12 + printWarning(sLabel, sVal); + return; + } + if (sVal <= 0.0) + return; + fprintf(fp, sLabel, sVal); +} //json_Float + +void json_Bool(FILE *fp, const char *sLabel, int sVal) { + // json_Str(fp, "\t\"MTState\"", d.mtState); + //n.b. in JSON, true and false are lower case, whereas in Python they are capitalized + // only print 0 and >=1 for false and true, ignore negative values + if (sVal == 0) + fprintf(fp, sLabel, "false"); + if (sVal > 0) + fprintf(fp, sLabel, "true"); +} //json_Bool + +void rescueProtocolName(struct TDICOMdata *d, const char *filename) { + //tools like gdcmanon strip protocol name (0018,1030) but for Siemens we can recover it from CSASeriesHeaderInfo (0029,1020) + if ((d->manufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) + return; + if (strlen(d->protocolName) > 0) + return; +#ifdef myReadAsciiCsa + float shimSetting[8]; + char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge]; + TCsaAscii csaAscii; + siemensCsaAscii(filename, &csaAscii, d->CSA.SeriesHeader_offset, d->CSA.SeriesHeader_length, shimSetting, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock); + if (strlen(protocolName) >= kDICOMStr) + protocolName[kDICOMStr - 1] = 0; + strcpy(d->protocolName, protocolName); +#endif +} + +void nii_SaveBIDSX(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char *filename, struct TDTI4D *dti4D) { + //https://docs.google.com/document/d/1HFUkAEE-pB-angVcYe6pf_-fVf4sCpOHKesUvfb8Grc/edit# + // Generate Brain Imaging Data Structure (BIDS) info + // sidecar JSON file (with the same filename as the .nii.gz file, but with .json extension). + // we will use %g for floats since exponents are allowed + // we will not set the locale, so decimal separator is always a period, as required + // https://www.ietf.org/rfc/rfc4627.txt + if ((!opts.isCreateBIDS) && (opts.isOnlyBIDS)) + printMessage("Input-only mode: no BIDS/NIfTI output generated for '%s'\n", pathoutname); + if (!opts.isCreateBIDS) + return; + char txtname[2048] = {""}; + strcpy(txtname, pathoutname); + strcat(txtname, ".json"); + FILE *fp = fopen(txtname, "w"); + fprintf(fp, "{\n"); + switch (d.modality) { + case kMODALITY_CR: + fprintf(fp, "\t\"Modality\": \"CR\",\n"); + break; + case kMODALITY_CT: + fprintf(fp, "\t\"Modality\": \"CT\",\n"); + break; + case kMODALITY_MR: + fprintf(fp, "\t\"Modality\": \"MR\",\n"); + break; + case kMODALITY_PT: + fprintf(fp, "\t\"Modality\": \"PT\",\n"); + break; + case kMODALITY_US: + fprintf(fp, "\t\"Modality\": \"US\",\n"); + break; + }; + //attempt to determine BIDS sequence type + /*(0018,0024) SequenceName +ep_b: dwi +epfid2d: perf +epfid2d: bold +epfid3d1_15: swi +epse2d: dwi (when b-vals specified) +epse2d: fmap (spin echo, e.g. TOPUP, nb could also be extra B=0 for DWI sequence) +fl2d: localizer +fl3d1r_t: angio +fl3d1r_tm: angio +fl3d1r: angio +fl3d1r: swi +fl3d1r: ToF +fm2d: fmap (gradient echo, e.g. FUGUE) +spc3d: T2 +spcir: flair (dark fluid) +spcR: PD +tfl3d: T1 +tfl_me3d5_16ns: T1 (ME-MPRAGE) +tir2d: flair +tse2d: PD +tse2d: T2 +tse3d: T2*/ + /* + if (d.manufacturer == kMANUFACTURER_SIEMENS) { + #define kLabel_UNKNOWN 0 + #define kLabel_T1w 1 + #define kLabel_T2w 2 + #define kLabel_bold 3 + #define kLabel_perf 4 + #define kLabel_dwi 5 + #define kLabel_fieldmap 6 + int iLabel = kLabel_UNKNOWN; + if (d.CSA.numDti > 1) iLabel = kLabel_dwi; + //if ((iLabel == kLabel_UNKNOWN) && (d.is2DAcq)) + //if ((iLabel == kLabel_UNKNOWN) && (d.is3DAcq)) + if (iLabel != kLabel_UNKNOWN) { + char tLabel[20] = {""}; + if (iLabel == kLabel_dwi) strcat (tLabel,"dwi"); + json_Str(fp, "\t\"ModalityLabel\": \"%s\",\n", tLabel); + } + }*/ + //report vendor + if (d.fieldStrength > 0.0) + fprintf(fp, "\t\"MagneticFieldStrength\": %g,\n", d.fieldStrength); + //Imaging Frequency (0018,0084) can be useful https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth + // however, UIH stores 128176031 not 128.176031 https://github.com/rordenlab/dcm2niix/issues/225 + if (d.imagingFrequency < 9000000) + json_Float(fp, "\t\"ImagingFrequency\": %g,\n", d.imagingFrequency); + switch (d.manufacturer) { + case kMANUFACTURER_BRUKER: + fprintf(fp, "\t\"Manufacturer\": \"Bruker\",\n"); + break; + case kMANUFACTURER_SIEMENS: + fprintf(fp, "\t\"Manufacturer\": \"Siemens\",\n"); + break; + case kMANUFACTURER_GE: + fprintf(fp, "\t\"Manufacturer\": \"GE\",\n"); + break; + case kMANUFACTURER_PHILIPS: + fprintf(fp, "\t\"Manufacturer\": \"Philips\",\n"); + break; + case kMANUFACTURER_TOSHIBA: + fprintf(fp, "\t\"Manufacturer\": \"Toshiba\",\n"); + break; + case kMANUFACTURER_CANON: + fprintf(fp, "\t\"Manufacturer\": \"Canon\",\n"); + break; + case kMANUFACTURER_MEDISO: + fprintf(fp, "\t\"Manufacturer\": \"Mediso\",\n"); + break; + case kMANUFACTURER_HITACHI: + fprintf(fp, "\t\"Manufacturer\": \"Hitachi\",\n"); + break; + case kMANUFACTURER_UIH: + fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n"); + break; + }; + if (d.epiVersionGE == 0) + fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n"); + if (d.epiVersionGE == 1) + fprintf(fp, "\t\"PulseSequenceName\": \"epiRT\",\n"); + if (d.internalepiVersionGE == 1) + fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI\",\n"); + if (d.internalepiVersionGE == 2) + fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI2\",\n"); + //GE pepolar with phase encoding direction reversed within a series + //if (d.epiVersionGE >= kGE_EPI_PEPOLAR_FWD) + // printWarning("Validate results for custom ABCD GE pepolar sequence\n"); + json_Str(fp, "\t\"ManufacturersModelName\": \"%s\",\n", d.manufacturersModelName); + json_Str(fp, "\t\"InstitutionName\": \"%s\",\n", d.institutionName); + json_Str(fp, "\t\"InstitutionalDepartmentName\": \"%s\",\n", d.institutionalDepartmentName); + json_Str(fp, "\t\"InstitutionAddress\": \"%s\",\n", d.institutionAddress); + json_Str(fp, "\t\"DeviceSerialNumber\": \"%s\",\n", d.deviceSerialNumber); + json_Str(fp, "\t\"StationName\": \"%s\",\n", d.stationName); + if (!opts.isAnonymizeBIDS) { + json_Str(fp, "\t\"SeriesInstanceUID\": \"%s\",\n", d.seriesInstanceUID); + json_Str(fp, "\t\"StudyInstanceUID\": \"%s\",\n", d.studyInstanceUID); + json_Str(fp, "\t\"ReferringPhysicianName\": \"%s\",\n", d.referringPhysicianName); + json_Str(fp, "\t\"StudyID\": \"%s\",\n", d.studyID); + //Next lines directly reveal patient identity + json_Str(fp, "\t\"PatientName\": \"%s\",\n", d.patientName); + json_Str(fp, "\t\"PatientID\": \"%s\",\n", d.patientID); + json_Str(fp, "\t\"AccessionNumber\": \"%s\",\n", d.accessionNumber); + if (strlen(d.patientBirthDate) == 8) { //DICOM DA YYYYMMDD -> ISO 8601 "YYYY-MM-DD" + int ayear, amonth, aday; + sscanf(d.patientBirthDate, "%4d%2d%2d", &ayear, &amonth, &aday); + fprintf(fp, "\t\"PatientBirthDate\": "); + fprintf(fp, (ayear >= 0 && ayear <= 9999) ? "\"%4d" : "\"%+4d", ayear); + fprintf(fp, "-%02d-%02d\",\n", amonth, aday); + } + if (d.patientSex != '?') + fprintf(fp, "\t\"PatientSex\": \"%c\",\n", d.patientSex); + json_Float(fp, "\t\"PatientWeight\": %g,\n", d.patientWeight); + //d.patientBirthDate //convert from DICOM YYYYMMDD to JSON + //d.patientAge //4-digit Age String: nnnD, nnnW, nnnM, nnnY; + } + json_Str(fp, "\t\"BodyPartExamined\": \"%s\",\n", d.bodyPartExamined); + json_Str(fp, "\t\"PatientPosition\": \"%s\",\n", d.patientOrient); // 0018,5100 = PatientPosition in DICOM + json_Str(fp, "\t\"ProcedureStepDescription\": \"%s\",\n", d.procedureStepDescription); + json_Str(fp, "\t\"SoftwareVersions\": \"%s\",\n", d.softwareVersions); + //json_Str(fp, "\t\"MRAcquisitionType\": \"%s\",\n", d.mrAcquisitionType); + if (d.is2DAcq) + fprintf(fp, "\t\"MRAcquisitionType\": \"2D\",\n"); + if (d.is3DAcq) + fprintf(fp, "\t\"MRAcquisitionType\": \"3D\",\n"); + json_Str(fp, "\t\"SeriesDescription\": \"%s\",\n", d.seriesDescription); + json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", d.protocolName); + json_Str(fp, "\t\"ScanningSequence\": \"%s\",\n", d.scanningSequence); + json_Str(fp, "\t\"SequenceVariant\": \"%s\",\n", d.sequenceVariant); + json_Str(fp, "\t\"ScanOptions\": \"%s\",\n", d.scanOptions); + json_Str(fp, "\t\"SequenceName\": \"%s\",\n", d.sequenceName); + if (strlen(d.imageType) > 0) { + fprintf(fp, "\t\"ImageType\": [\""); + bool isSep = false; + for (size_t i = 0; i < strlen(d.imageType); i++) { + if (d.imageType[i] != '_') { + if (isSep) + fprintf(fp, "\", \""); + isSep = false; + fprintf(fp, "%c", d.imageType[i]); + } else + isSep = true; + } + if ((d.isHasPhase) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_PHASE_") == NULL))) + fprintf(fp, "\", \"PHASE"); //"_IMAGINARY_" + if ((d.isHasReal) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_REAL_") == NULL))) + fprintf(fp, "\", \"REAL"); + if ((d.isHasImaginary) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_IMAGINARY_") == NULL))) + fprintf(fp, "\", \"IMAGINARY"); + if ((d.isRealIsPhaseMapHz)) // && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_IMAGINARY_") == NULL)) ) + fprintf(fp, "\", \"FIELDMAPHZ"); + fprintf(fp, "\"],\n"); + } + if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc) + fprintf(fp, "\t\"RawImage\": false,\n"); + if (d.seriesNum > 0) + fprintf(fp, "\t\"SeriesNumber\": %ld,\n", d.seriesNum); + //Chris Gorgolewski: BIDS standard specifies ISO8601 date-time format (Example: 2016-07-06T12:49:15.679688) + //Lines below directly save DICOM values + if (d.acquisitionTime > 0.0 && d.acquisitionDate > 0.0) { + long acquisitionDate = d.acquisitionDate; + double acquisitionTime = d.acquisitionTime; + char acqDateTimeBuf[64]; + //snprintf(acqDateTimeBuf, sizeof acqDateTimeBuf, "%+08ld%+08f", acquisitionDate, acquisitionTime); + snprintf(acqDateTimeBuf, sizeof acqDateTimeBuf, "%+08ld%+013.5f", acquisitionDate, acquisitionTime); //CR 20170404 add zero pad so 1:23am appears as +012300.00000 not +12300.00000 + //printMessage("acquisitionDateTime %s\n",acqDateTimeBuf); + int ayear, amonth, aday, ahour, amin; + double asec; + int count = 0; + sscanf(acqDateTimeBuf, "%5d%2d%2d%3d%2d%lf%n", &ayear, &amonth, &aday, &ahour, &amin, &asec, &count); //CR 20170404 %lf not %f for double precision + //printf("-%02d-%02dT%02d:%02d:%02.6f\",\n", amonth, aday, ahour, amin, asec); + if (count) { // ISO 8601 specifies a sign must exist for distant years. + //report time of the day only format, https://www.cs.tut.fi/~jkorpela/iso8601.html + fprintf(fp, "\t\"AcquisitionTime\": \"%02d:%02d:%02.6f\",\n", ahour, amin, asec); + //report date and time together + if (!opts.isAnonymizeBIDS) { + fprintf(fp, "\t\"AcquisitionDateTime\": "); + fprintf(fp, (ayear >= 0 && ayear <= 9999) ? "\"%4d" : "\"%+4d", ayear); + fprintf(fp, "-%02d-%02dT%02d:%02d:%02.6f\",\n", amonth, aday, ahour, amin, asec); + } + } //if (count) + } //if acquisitionTime and acquisitionDate recorded + // if (d.acquisitionTime > 0.0) fprintf(fp, "\t\"AcquisitionTime\": %f,\n", d.acquisitionTime ); + // if (d.acquisitionDate > 0.0) fprintf(fp, "\t\"AcquisitionDate\": %8.0f,\n", d.acquisitionDate ); + if (d.acquNum > 0) + fprintf(fp, "\t\"AcquisitionNumber\": %d,\n", d.acquNum); + json_Str(fp, "\t\"ImageComments\": \"%s\",\n", d.imageComments); + json_Str(fp, "\t\"ConversionComments\": \"%s\",\n", opts.imageComments); + //if conditionals: the following values are required for DICOM MRI, but not available for CT + json_Float(fp, "\t\"TriggerDelayTime\": %g,\n", d.triggerDelayTime); + if (d.RWVScale != 0) { + fprintf(fp, "\t\"PhilipsRWVSlope\": %g,\n", d.RWVScale); + fprintf(fp, "\t\"PhilipsRWVIntercept\": %g,\n", d.RWVIntercept); + } + if ((!d.isScaleVariesEnh) && (d.intenScalePhilips != 0) && (d.manufacturer == kMANUFACTURER_PHILIPS)) { //for details, see PhilipsPrecise() + fprintf(fp, "\t\"PhilipsRescaleSlope\": %g,\n", d.intenScale); + fprintf(fp, "\t\"PhilipsRescaleIntercept\": %g,\n", d.intenIntercept); + fprintf(fp, "\t\"PhilipsScaleSlope\": %g,\n", d.intenScalePhilips); + fprintf(fp, "\t\"UsePhilipsFloatNotDisplayScaling\": %d,\n", opts.isPhilipsFloatNotDisplayScaling); + } + //https://bids-specification--622.org.readthedocs.build/en/622/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#case-3-direct-field-mapping + if ((d.isRealIsPhaseMapHz) && (d.isHasReal)) + fprintf(fp, "\t\"Units\": \"Hz\",\n"); // + //PET ISOTOPE MODULE ATTRIBUTES + json_Str(fp, "\t\"Radiopharmaceutical\": \"%s\",\n", d.radiopharmaceutical); + json_Float(fp, "\t\"RadionuclidePositronFraction\": %g,\n", d.radionuclidePositronFraction); + json_Float(fp, "\t\"RadionuclideTotalDose\": %g,\n", d.radionuclideTotalDose); + json_Float(fp, "\t\"RadionuclideHalfLife\": %g,\n", d.radionuclideHalfLife); + json_Float(fp, "\t\"DoseCalibrationFactor\": %g,\n", d.doseCalibrationFactor); + json_Float(fp, "\t\"IsotopeHalfLife\": %g,\n", d.ecat_isotope_halflife); + json_Float(fp, "\t\"Dosage\": %g,\n", d.ecat_dosage); + json_Str(fp, "\t\"ConvolutionKernel\": \"%s\",\n", d.convolutionKernel); + json_Str(fp, "\t\"Units\": \"%s\",\n", d.unitsPT); //https://github.com/bids-standard/bids-specification/pull/773 + json_Str(fp, "\t\"DecayCorrection\": \"%s\",\n", d.decayCorrection); + json_Str(fp, "\t\"AttenuationCorrectionMethod\": \"%s\",\n", d.attenuationCorrectionMethod); + json_Str(fp, "\t\"ReconstructionMethod\": \"%s\",\n", d.reconstructionMethod); + //json_Float(fp, "\t\"DecayFactor\": %g,\n", d.decayFactor); + if (dti4D->decayFactor[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + fprintf(fp, "\t\"DecayFactor\": [\n"); + for (int i = 0; i < h->dim[4]; i++) { + if (i != 0) + fprintf(fp, ",\n"); + if (dti4D->decayFactor[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->decayFactor[i]); + } + fprintf(fp, "\t],\n"); + } + if (dti4D->volumeOnsetTime[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + fprintf(fp, "\t\"FrameTimesStart\": [\n"); + for (int i = 0; i < h->dim[4]; i++) { + if (i != 0) + fprintf(fp, ",\n"); + if (dti4D->volumeOnsetTime[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->volumeOnsetTime[i]); + } + fprintf(fp, "\t],\n"); + } + if (dti4D->frameDuration[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + fprintf(fp, "\t\"FrameDuration\": [\n"); + for (int i = 0; i < h->dim[4]; i++) { + if (i != 0) + fprintf(fp, ",\n"); + if (dti4D->frameDuration[i] < 0) + break; + if ((isSameFloatGE(dti4D->frameDuration[i], 0.0)) && (d.TR > 0.0)) + fprintf(fp, "\t\t%g", d.TR / 1000.0); // from 0018,1242 ms -> sec + else + fprintf(fp, "\t\t%g", dti4D->frameDuration[i] / 1000.0); // from 0018,1242 ms -> sec + } + fprintf(fp, "\t],\n"); + } + //CT parameters + json_Float(fp, "\t\"ExposureTime\": %g,\n", d.exposureTimeMs / 1000.0); + json_Float(fp, "\t\"XRayTubeCurrent\": %g,\n", d.xRayTubeCurrent); + if ((d.TE > 0.0) && (d.isXRay)) + fprintf(fp, "\t\"XRayExposure\": %g,\n", d.TE); + //MRI parameters + if (!d.isXRay) { //with CT scans, slice thickness often varies + //beware, not used correctly by all vendors https://public.kitware.com/pipermail/insight-users/2005-September/014711.html + json_Float(fp, "\t\"SliceThickness\": %g,\n", d.zThick); + json_Float(fp, "\t\"SpacingBetweenSlices\": %g,\n", d.zSpacing); + } + json_Float(fp, "\t\"SAR\": %g,\n", d.SAR); + if (d.numberOfAverages > 1.0) + json_Float(fp, "\t\"NumberOfAverages\": %g,\n", d.numberOfAverages); + if ((d.echoNum > 1) || (d.isMultiEcho)) + fprintf(fp, "\t\"EchoNumber\": %d,\n", d.echoNum); + if ((d.TE > 0.0) && (!d.isXRay)) + fprintf(fp, "\t\"EchoTime\": %g,\n", d.TE / 1000.0); + //if ((d.TE2 > 0.0) && (!d.isXRay)) fprintf(fp, "\t\"EchoTime2\": %g,\n", d.TE2 / 1000.0 ); + if (dti4D->frameDuration[0] < 0.0) //e.g. PET scans can have variable TR + json_Float(fp, "\t\"RepetitionTime\": %g,\n", d.TR / 1000.0); + json_Float(fp, "\t\"RepetitionTimeExcitation\": %g,\n", dti4D->repetitionTimeExcitation); + json_Float(fp, "\t\"RepetitionTimeInversion\": %g,\n", dti4D->repetitionTimeInversion); + json_Bool(fp, "\t\"MTState\": %s,\n", d.mtState); // BIDS suggests 0018,9020 but Siemens V-series do not populate this, alternatives are CSA or (0018,0021) CS [SK\MTC\SP] + //SpoilingState + bool isSpoiled = (d.spoiling > kSPOILING_NONE); + if ((d.spoiling == kSPOILING_UNKOWN) && (strstr(d.sequenceVariant, "\\SP") != NULL)) //BIDS suggests 0018,9016 Siemens V-series do not populate this, (0018,0021) CS [SK\MTC\SP] + isSpoiled = true; + if (isSpoiled) + json_Bool(fp, "\t\"SpoilingState\": %s,\n", true); //Siemens reports SpoilingState but not SpoilingType + if (d.spoiling == kSPOILING_RF) + fprintf(fp, "\t\"SpoilingType\": \"RF\",\n"); + if (d.spoiling == kSPOILING_GRADIENT) + fprintf(fp, "\t\"SpoilingType\": \"GRADIENT\",\n"); + if (d.spoiling == kSPOILING_RF_AND_GRADIENT) + fprintf(fp, "\t\"SpoilingType\": \"COMBINED\",\n"); + json_Float(fp, "\t\"InversionTime\": %g,\n", d.TI / 1000.0); + json_Float(fp, "\t\"FlipAngle\": %g,\n", d.flipAngle); + bool interp = false; //2D interpolation + float phaseOversampling = 0.0; + //n.b. https://neurostars.org/t/getting-missing-ge-information-required-by-bids-for-common-preprocessing/1357/7 + json_Str(fp, "\t\"PhaseEncodingDirectionDisplayed\": \"%s\",\n", d.phaseEncodingDirectionDisplayedUIH); + if ((d.manufacturer == kMANUFACTURER_GE) && (d.phaseEncodingGE != kGE_PHASE_ENCODING_POLARITY_UNKNOWN)) { //only set for GE + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Unflipped\",\n"); + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) + fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Flipped\",\n"); + } + float delayTimeInTR = -0.01; + float repetitionTimePreparation = 0.0; +#ifdef myReadAsciiCsa + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.SeriesHeader_offset > 0) && (d.CSA.SeriesHeader_length > 0)) { + float pf = 1.0f; //partial fourier + float shimSetting[8]; + char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge]; + TCsaAscii csaAscii; + siemensCsaAscii(filename, &csaAscii, d.CSA.SeriesHeader_offset, d.CSA.SeriesHeader_length, shimSetting, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock); + if ((d.phaseEncodingLines < 1) && (csaAscii.phaseEncodingLines > 0)) + d.phaseEncodingLines = csaAscii.phaseEncodingLines; + //if (d.phaseEncodingLines != csaAscii.phaseEncodingLines) //e.g. phaseOversampling + // printWarning("PhaseEncodingLines reported in DICOM (%d) header does not match value CSA-ASCII (%d) %s\n", d.phaseEncodingLines, csaAscii.phaseEncodingLines, pathoutname); + delayTimeInTR = csaAscii.delayTimeInTR; + if ((d.isHasPhase) && (csaAscii.TE0 > 0.0) && (csaAscii.TE1 > 0.0)) { //issue400 + //https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html + json_Float(fp, "\t\"EchoTime1\": %g,\n", csaAscii.TE0 / 1000000.0); + json_Float(fp, "\t\"EchoTime2\": %g,\n", csaAscii.TE1 / 1000000.0); + } + if (csaAscii.dAveragesDouble > 1.0) //*spcR_44ns fractional and independent of (0018,0083) DS NumberOfAverages, e.g. 0018,0083=2, dAveragesDouble = 1.4? + json_Float(fp, "\t\"AveragesDouble\": %g,\n", csaAscii.dAveragesDouble); + phaseOversampling = csaAscii.phaseOversampling; + if (csaAscii.existUcImageNumb > 0) { + if (d.CSA.protocolSliceNumber1 < 2) { + printWarning("Assuming mosaics saved in reverse order due to 'sSliceArray.ucImageNumb'\n"); + //never seen such an image in the wild.... sliceDir may need to be reversed + } + d.CSA.protocolSliceNumber1 = 2; + } + //ultra-verbose output for deciphering adFree/alFree/alTI values: + /* + if (opts.isVerbose > 1) { + for (int i = 0; i < kMaxWipFree; i++) + print_FloatNotNan("adFree[%d]=\t%g\n",i, csaAscii.adFree[i]); + for (int i = 0; i < kMaxWipFree; i++) + print_FloatNotNan("alFree[%d]=\t%g\n",i, csaAscii.alFree[i]); + for (int i = 0; i < kMaxWipFree; i++) + print_FloatNotNan("alTI[%d]=\t%g\n",i, csaAscii.alTI[i]); + } //verbose + */ + bool isPCASL = false; + bool isPASL = false; + //ASL specific tags - 2D pCASL Danny J.J. Wang http://www.loft-lab.org + if ((strstr(pulseSequenceDetails, "ep2d_pcasl")) || (strstr(pulseSequenceDetails, "ep2d_pcasl_UI_PHC"))) { + isPCASL = true; + repetitionTimePreparation = d.TR; + json_FloatNotNan(fp, "\t\"LabelOffset\": %g,\n", csaAscii.adFree[1]); //mm + json_FloatNotNan(fp, "\t\"PostLabelDelay\": %g,\n", csaAscii.adFree[2] * (1.0 / 1000000.0)); //usec -> sec + json_FloatNotNan(fp, "\t\"NumRFBlocks\": %g,\n", csaAscii.adFree[3]); + json_FloatNotNan(fp, "\t\"RFGap\": %g,\n", csaAscii.adFree[4] * (1.0 / 1000000.0)); //usec -> sec + json_FloatNotNan(fp, "\t\"MeanGzx10\": %g,\n", csaAscii.adFree[10]); // mT/m + json_FloatNotNan(fp, "\t\"PhiAdjust\": %g,\n", csaAscii.adFree[11]); // percent + } + //ASL specific tags - 3D pCASL Danny J.J. Wang http://www.loft-lab.org + if (strstr(pulseSequenceDetails, "tgse_pcasl")) { + isPCASL = true; + repetitionTimePreparation = d.TR; + json_FloatNotNan(fp, "\t\"RFGap\": %g,\n", csaAscii.adFree[4] * (1.0 / 1000000.0)); //usec -> sec + json_FloatNotNan(fp, "\t\"MeanGzx10\": %g,\n", csaAscii.adFree[10]); // mT/m + json_FloatNotNan(fp, "\t\"T1\": %g,\n", csaAscii.adFree[12] * (1.0 / 1000000.0)); //usec -> sec + } + //ASL specific tags - 2D PASL Siemens Product + if (strstr(pulseSequenceDetails, "ep2d_pasl")) { + isPASL = true; + json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0 / 1000000.0)); //usec->sec + json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0 / 1000000.0)); //usec -> sec + } + //ASL specific tags - 3D PASL Siemens Product http://adni.loni.usc.edu/wp-content/uploads/2010/05/ADNI3_Basic_Siemens_Skyra_E11.pdf + if (strstr(pulseSequenceDetails, "tgse_pasl")) { + isPASL = true; + json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0 / 1000000.0)); //usec->sec + json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0 / 1000000.0)); //usec -> sec + //json_FloatNotNan(fp, "\t\"SaturationStopTime\": %g,\n", csaAscii.alTI[2] * (1.0/1000.0)); + } + //PASL http://www.pubmed.com/11746944 http://www.pubmed.com/21606572 + if (strstr(pulseSequenceDetails, "ep2d_fairest")) { + isPASL = true; + json_FloatNotNan(fp, "\t\"PostInversionDelay\": %g,\n", csaAscii.adFree[2] * (1.0 / 1000.0)); //usec->sec + json_FloatNotNan(fp, "\t\"PostLabelDelay\": %g,\n", csaAscii.adFree[4] * (1.0 / 1000.0)); //usec -> sec + } + //ASL specific tags - Oxford (Thomas OKell) + bool isOxfordASL = false; + if (strstr(pulseSequenceDetails, "to_ep2d_VEPCASL")) { //Oxford 2D pCASL + isOxfordASL = true; + isPCASL = true; + repetitionTimePreparation = d.TR; + json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0 / 1000000.0)); //ms->sec + json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0 / 1000000.0)); //usec -> sec + json_Float(fp, "\t\"TagRFFlipAngle\": %g,\n", csaAscii.alFree[4]); + json_Float(fp, "\t\"TagRFDuration\": %g,\n", csaAscii.alFree[5] / 1000000.0); //usec -> sec + json_Float(fp, "\t\"TagRFSeparation\": %g,\n", csaAscii.alFree[6] / 1000000.0); //usec -> sec + json_FloatNotNan(fp, "\t\"MeanTagGradient\": %g,\n", csaAscii.adFree[0]); //mTm + json_FloatNotNan(fp, "\t\"TagGradientAmplitude\": %g,\n", csaAscii.adFree[1]); //mTm + json_Float(fp, "\t\"TagDuration\": %g,\n", csaAscii.alFree[9] / 1000.0); //ms -> sec + json_Float(fp, "\t\"MaximumT1Opt\": %g,\n", csaAscii.alFree[10] / 1000.0); //ms -> sec + //report post label delay + int nPLD = 0; + bool isValid = true; //detect gaps in PLD array: If user sets PLD1=250, PLD2=0 PLD3=375 only PLD1 was acquired + for (int k = 11; k < 31; k++) { + if ((isnan(csaAscii.alFree[k])) || (csaAscii.alFree[k] <= 0.0)) + isValid = false; + if (isValid) + nPLD++; + } //for k + if (nPLD > 0) { // record PostLabelDelays, these are listed as "PLD0","PLD1",etc in PDF + fprintf(fp, "\t\"InitialPostLabelDelay\": [\n"); //https://docs.google.com/document/d/15tnn5F10KpgHypaQJNNGiNKsni9035GtDqJzWqkkP6c/edit# + for (int i = 0; i < nPLD; i++) { + if (i != 0) + fprintf(fp, ",\n"); + fprintf(fp, "\t\t%g", csaAscii.alFree[i + 11] / 1000.0); //ms -> sec + } + fprintf(fp, "\t],\n"); + } + /*isValid = true; //detect gaps in PLD array: If user sets PLD1=250, PLD2=0 PLD3=375 only PLD1 was acquired + for (int k = 11; k < 31; k++) { + if (isValid) { + char newstr[256]; + sprintf(newstr, "\t\"PLD%d\": %%g,\n", k-11); + json_Float(fp, newstr, csaAscii.alFree[k]/ 1000.0); //ms -> sec + if (csaAscii.alFree[k] <= 0.0) isValid = false; + }//isValid + } //for k */ + for (int k = 3; k < 11; k++) { //vessel locations + char newstr[256]; + sprintf(newstr, "\t\"sWipMemBlockAdFree%d\": %%g,\n", k); //issue483: sWipMemBlock.AdFree -> sWipMemBlockAdFree + json_FloatNotNan(fp, newstr, csaAscii.adFree[k]); + } + } + if (strstr(pulseSequenceDetails, "jw_tgse_VEPCASL")) { //Oxford 3D pCASL + isPCASL = true; + isOxfordASL = true; + json_Float(fp, "\t\"TagRFFlipAngle\": %g,\n", csaAscii.alFree[6]); + json_Float(fp, "\t\"TagRFDuration\": %g,\n", csaAscii.alFree[7] / 1000000.0); //usec -> sec + json_Float(fp, "\t\"TagRFSeparation\": %g,\n", csaAscii.alFree[8] / 1000000.0); //usec -> sec + json_Float(fp, "\t\"MaximumT1Opt\": %g,\n", csaAscii.alFree[9] / 1000.0); //ms -> sec + json_Float(fp, "\t\"Tag0\": %g,\n", csaAscii.alFree[10] / 1000.0); //DelayTimeInTR usec -> sec + json_Float(fp, "\t\"Tag1\": %g,\n", csaAscii.alFree[11] / 1000.0); //DelayTimeInTR usec -> sec + json_Float(fp, "\t\"Tag2\": %g,\n", csaAscii.alFree[12] / 1000.0); //DelayTimeInTR usec -> sec + json_Float(fp, "\t\"Tag3\": %g,\n", csaAscii.alFree[13] / 1000.0); //DelayTimeInTR usec -> sec + int nPLD = 0; + bool isValid = true; //detect gaps in PLD array: If user sets PLD1=250, PLD2=0 PLD3=375 only PLD1 was acquired + for (int k = 30; k < 38; k++) { + if ((isnan(csaAscii.alFree[k])) || (csaAscii.alFree[k] <= 0.0)) + isValid = false; + if (isValid) + nPLD++; + } //for k + if (nPLD > 0) { // record PostLabelDelays, these are listed as "PLD0","PLD1",etc in PDF + fprintf(fp, "\t\"InitialPostLabelDelay\": [\n"); //https://docs.google.com/document/d/15tnn5F10KpgHypaQJNNGiNKsni9035GtDqJzWqkkP6c/edit# + for (int i = 0; i < nPLD; i++) { + if (i != 0) + fprintf(fp, ",\n"); + fprintf(fp, "\t\t%g", csaAscii.alFree[i + 30] / 1000.0); //ms -> sec + } + fprintf(fp, "\t],\n"); + } + /* + json_Float(fp, "\t\"PLD0\": %g,\n", csaAscii.alFree[30]/1000.0); + json_Float(fp, "\t\"PLD1\": %g,\n", csaAscii.alFree[31]/1000.0); + json_Float(fp, "\t\"PLD2\": %g,\n", csaAscii.alFree[32]/1000.0); + json_Float(fp, "\t\"PLD3\": %g,\n", csaAscii.alFree[33]/1000.0); + json_Float(fp, "\t\"PLD4\": %g,\n", csaAscii.alFree[34]/1000.0); + json_Float(fp, "\t\"PLD5\": %g,\n", csaAscii.alFree[35]/1000.0); + */ + } + if (isOxfordASL) { //properties common to 2D and 3D ASL + //labelling plane + fprintf(fp, "\t\"TagPlaneDThickness\": %g,\n", csaAscii.dThickness); + fprintf(fp, "\t\"TagPlaneUlShape\": %g,\n", csaAscii.ulShape); + fprintf(fp, "\t\"TagPlaneSPositionDTra\": %g,\n", csaAscii.sPositionDTra); + fprintf(fp, "\t\"TagPlaneSNormalDTra\": %g,\n", csaAscii.sNormalDTra); + } + if (isPCASL) + fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PCASL\",\n"); + if (isPASL) + fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PASL\",\n"); + //general properties + if ((csaAscii.partialFourier > 0) && ((d.modality == kMODALITY_MR))) { //check MR, e.g. do not report for Siemens PET + //https://github.com/ismrmrd/siemens_to_ismrmrd/blob/master/parameter_maps/IsmrmrdParameterMap_Siemens_EPI_FLASHREF.xsl + if (csaAscii.partialFourier == 1) + pf = 0.5; // 4/8 + if (csaAscii.partialFourier == 2) + pf = 0.625; // 5/8 + if (csaAscii.partialFourier == 4) + pf = 0.75; + if (csaAscii.partialFourier == 8) + pf = 0.875; + fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); + } + if (csaAscii.interp > 0) { //in-plane interpolation + interp = true; + fprintf(fp, "\t\"Interpolation2D\": %d,\n", interp); + } + if (d.interp3D > 1) //through-plane interpolation e.g. GE ZIP2 through-plane http://mriquestions.com/zip.html + fprintf(fp, "\t\"Interpolation3D\": %d,\n", d.interp3D); + if (csaAscii.baseResolution > 0) + fprintf(fp, "\t\"BaseResolution\": %d,\n", csaAscii.baseResolution); + if (shimSetting[0] != 0.0) { + fprintf(fp, "\t\"ShimSetting\": [\n"); + for (int i = 0; i < 8; i++) { + if (i != 0) + fprintf(fp, ",\n"); + fprintf(fp, "\t\t%g", shimSetting[i]); + } + fprintf(fp, "\t],\n"); + } + if (d.CSA.numDti > 0) { // + if (csaAscii.difBipolar == 1) + fprintf(fp, "\t\"DiffusionScheme\": \"Bipolar\",\n"); + if (csaAscii.difBipolar == 2) + fprintf(fp, "\t\"DiffusionScheme\": \"Monopolar\",\n"); + } + //DelayTimeInTR + // https://groups.google.com/forum/#!topic/bids-discussion/nmg1BOVH1SU + // https://groups.google.com/forum/#!topic/bids-discussion/seD7AtJfaFE + json_Float(fp, "\t\"DelayTime\": %g,\n", delayTimeInTR / 1000000.0); //DelayTimeInTR usec -> sec + if (d.modality == kMODALITY_MR) + json_Float(fp, "\t\"TxRefAmp\": %g,\n", csaAscii.txRefAmp); + if (d.modality == kMODALITY_MR) + json_Float(fp, "\t\"PhaseResolution\": %g,\n", csaAscii.phaseResolution); + json_Float(fp, "\t\"PhaseOversampling\": %g,\n", phaseOversampling); + json_Float(fp, "\t\"VendorReportedEchoSpacing\": %g,\n", csaAscii.echoSpacing / 1000000.0); //usec -> sec + //ETD and epiFactor not useful/reliable https://github.com/rordenlab/dcm2niix/issues/127 + //if (echoTrainDuration > 0) fprintf(fp, "\t\"EchoTrainDuration\": %g,\n", echoTrainDuration / 1000000.0); //usec -> sec + //if (epiFactor > 0) fprintf(fp, "\t\"EPIFactor\": %d,\n", epiFactor); + json_Str(fp, "\t\"ReceiveCoilName\": \"%s\",\n", coilID); + if (d.modality == kMODALITY_MR) + json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", coilElements); + if (strcmp(coilElements, d.coilName) != 0) + json_Str(fp, "\t\"CoilString\": \"%s\",\n", d.coilName); + strcpy(d.coilName, ""); + json_Str(fp, "\t\"PulseSequenceDetails\": \"%s\",\n", pulseSequenceDetails); + json_Str(fp, "\t\"FmriExternalInfo\": \"%s\",\n", fmriExternalInfo); + json_Str(fp, "\t\"WipMemBlock\": \"%s\",\n", wipMemBlock); + if (strlen(d.protocolName) < 1) //insert protocol name if it exists in CSA but not DICOM header: https://github.com/nipy/heudiconv/issues/80 + json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", protocolName); + if (csaAscii.refLinesPE > 0) + fprintf(fp, "\t\"RefLinesPE\": %d,\n", csaAscii.refLinesPE); + //https://github.com/bids-standard/bids-specification/pull/681#issuecomment-861767213 + if (csaAscii.combineMode == 1) + fprintf(fp, "\t\"CoilCombinationMethod\": \"Sum of Squares\",\n"); + if (csaAscii.combineMode == 2) + fprintf(fp, "\t\"CoilCombinationMethod\": \"Adaptive Combine\",\n"); + if ((csaAscii.ucMTC == 1) && (d.mtState < 0)) //precedence for 0018,9020 over CSA + json_Bool(fp, "\t\"MTState\": %s,\n", 1); + json_Str(fp, "\t\"ConsistencyInfo\": \"%s\",\n", consistencyInfo); + if (csaAscii.accelFact3D > 1.01) json_Float(fp, "\t\"AccelFact3D\": %g,\n", csaAscii.accelFact3D); //see *spcR_44ns where "sPat.lAccelFactPE = 1", "sPat.lAccelFact3D = 2" (0051,1011) LO [p2], perhaps ParallelReductionFactorInPlane should be 1? + if (csaAscii.parallelReductionFactorInPlane > 0) { //AccelFactorPE -> phase encoding + if (csaAscii.patMode == 1) + fprintf(fp, "\t\"MatrixCoilMode\": \"SENSE\",\n"); + if (csaAscii.patMode == 2) + fprintf(fp, "\t\"MatrixCoilMode\": \"GRAPPA\",\n"); + if (d.accelFactPE < 1.0) { //value not found in DICOM header, but WAS found in CSA ascii + d.accelFactPE = csaAscii.parallelReductionFactorInPlane; //value found in ASCII but not in DICOM (0051,1011) + } + if ((csaAscii.accelFact3D < 1.01) && (csaAscii.parallelReductionFactorInPlane != (int)(d.accelFactPE))) + printWarning("ParallelReductionFactorInPlane reported in DICOM [0051,1011] (%d) does not match CSA series value %d\n", (int)(d.accelFactPE), csaAscii.parallelReductionFactorInPlane); + } + } else { //e.g. Siemens Vida does not have CSA header, but has many attributes + json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", d.coilElements); + if (strcmp(d.coilElements, d.coilName) != 0) + json_Str(fp, "\t\"CoilString\": \"%s\",\n", d.coilName); + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (!d.is3DAcq) && (d.phaseEncodingLines > d.echoTrainLength) && (d.echoTrainLength > 1)) { + //ETL is > 1, as some GE files list 1, as an example see series mr_0005 in dcm_qa_nih + float pf = (float)d.phaseEncodingLines; + if (d.accelFactPE > 1) + pf = (float)pf / (float)d.accelFactPE; //estimate: not sure if we round up or down + pf = (float)d.echoTrainLength / (float)pf; + if (pf < 1.0) //e.g. if difference between lines and echo length not all explained by iPAT (SENSE/GRAPPA) + fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); + } //compute partial Fourier: not reported in XA10, so infer + //printf("PhaseLines=%d EchoTrainLength=%d SENSE=%g\n", d.phaseEncodingLines, d.echoTrainLength, d.accelFactPE); //n.b. we can not distinguish pF from SENSE/GRAPPA for UIH + } +#endif + // https://neurostars.org/t/repetitiontime-parameters-what-are-they-and-where-to-find-them/20020/6 + json_Float(fp, "\t\"RepetitionTimePreparation\": %g,\n", repetitionTimePreparation); + //Philips ASL specific tags, issue533 + //Philips ASL issue 533 + /* + //see dcm_qa_philips_asl: this works for the mulit-PLD sequence, but not for other sequences. Also beware that value varies per slice, so incorrect values for descending + if ( (d.aslFlags != kASL_FLAG_NONE) && (dti4D->triggerDelayTime[0] >= 0.0)) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + bool isMultiPLD = false; + for (int i = 0; i < h->dim[4]; i++) + if (dti4D->triggerDelayTime[i] != dti4D->triggerDelayTime[0]) + isMultiPLD = true; + if (isMultiPLD) { + fprintf(fp, "\t\"InitialPostLabelDelay\": [\n"); + for (int i = 0; i < h->dim[4]; i++) { + if (i != 0) + fprintf(fp, ",\n"); + if (dti4D->triggerDelayTime[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->triggerDelayTime[i] / 1000.0); + } + fprintf(fp, "\t],\n"); + } else //if all delays are the same, write scalar + json_Float(fp, "\t\"InitialPostLabelDelay\": %g,\n", dti4D->triggerDelayTime[0] / 1000.0); + } + */ + //GE ASL specific tags + if (d.aslFlags & kASL_FLAG_GE_CONTINUOUS) + fprintf(fp, "\t\"ASLContrastTechnique\": \"CONTINUOUS\",\n"); + if (d.aslFlags & kASL_FLAG_GE_PSEUDOCONTINUOUS) + fprintf(fp, "\t\"ASLContrastTechnique\": \"PSEUDOCONTINUOUS\",\n"); + if (d.aslFlags & kASL_FLAG_GE_3DPCASL) + fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D pulsed continuous ASL technique\",\n"); + if (d.aslFlags & kASL_FLAG_GE_3DCASL) + fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D continuous ASL technique\",\n"); + if (d.durationLabelPulseGE > 0) { + json_Float(fp, "\t\"LabelingDuration\": %g,\n", d.durationLabelPulseGE / 1000.0); + json_Float(fp, "\t\"PostLabelingDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay + json_Float(fp, "\t\"NumberOfPointsPerArm\": %g,\n", d.numberOfPointsPerArm); + json_Float(fp, "\t\"NumberOfArms\": %g,\n", d.numberOfArms); + } + if (d.numberOfExcitations > 1) json_Float(fp, "\t\"NumberOfExcitations\": %g,\n", d.numberOfExcitations); + if ((d.CSA.multiBandFactor > 1) && (d.modality == kMODALITY_MR)) //AccelFactorSlice + fprintf(fp, "\t\"MultibandAccelerationFactor\": %d,\n", d.CSA.multiBandFactor); + json_Float(fp, "\t\"PercentPhaseFOV\": %g,\n", d.phaseFieldofView); + json_Float(fp, "\t\"PercentSampling\": %g,\n", d.percentSampling); + if (d.echoTrainLength > 1) //>1 as for Siemens EPI this is 1, Siemens uses EPI factor http://mriquestions.com/echo-planar-imaging.html + fprintf(fp, "\t\"EchoTrainLength\": %d,\n", d.echoTrainLength); //0018,0091 Combination of partial fourier and in-plane parallel imaging + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_PHASE) + fprintf(fp, "\t\"PartialFourierDirection\": \"PHASE\",\n"); + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_FREQUENCY) + fprintf(fp, "\t\"PartialFourierDirection\": \"FREQUENCY\",\n"); + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT) + fprintf(fp, "\t\"PartialFourierDirection\": \"SLICE_SELECT\",\n"); + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_COMBINATION) + fprintf(fp, "\t\"PartialFourierDirection\": \"COMBINATION\",\n"); + if ((d.phaseEncodingSteps > 0) && (d.isPartialFourier) && (d.manufacturer == kMANUFACTURER_PHILIPS)) { + //issue 377 + fprintf(fp, "\t\"PartialFourierEnabled\": \"YES\",\n"); + fprintf(fp, "\t\"PhaseEncodingStepsNoPartialFourier\": %d,\n", d.phaseEncodingSteps); + } else if (d.phaseEncodingSteps > 0) + fprintf(fp, "\t\"PhaseEncodingSteps\": %d,\n", d.phaseEncodingSteps); + if ((d.phaseEncodingLines > 0) && (d.modality == kMODALITY_MR)) + fprintf(fp, "\t\"AcquisitionMatrixPE\": %d,\n", d.phaseEncodingLines); + //Compute ReconMatrixPE + // Actual size of the *reconstructed* data in the PE dimension, which does NOT match + // phaseEncodingLines in the case of interpolation or phaseResolution < 100% + // We'll need this for generating a value for effectiveEchoSpacing that is consistent + // with the *reconstructed* data. + int reconMatrixPE = d.phaseEncodingLines; + if ((h->dim[2] > 0) && (h->dim[1] > 0)) { + if (h->dim[1] == h->dim[2]) //phase encoding does not matter + reconMatrixPE = h->dim[2]; + else if (d.phaseEncodingRC == 'C') + reconMatrixPE = h->dim[2]; //see dcm_qa: NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_200PFOV_AP_0034 + else if (d.phaseEncodingRC == 'R') + reconMatrixPE = h->dim[1]; + } + if ((d.modality == kMODALITY_MR) && (reconMatrixPE > 0)) + fprintf(fp, "\t\"ReconMatrixPE\": %d,\n", reconMatrixPE); + double bandwidthPerPixelPhaseEncode = d.bandwidthPerPixelPhaseEncode; + if (bandwidthPerPixelPhaseEncode == 0.0) + bandwidthPerPixelPhaseEncode = d.CSA.bandwidthPerPixelPhaseEncode; + json_Float(fp, "\t\"BandwidthPerPixelPhaseEncode\": %g,\n", bandwidthPerPixelPhaseEncode); + //if ((!d.is3DAcq) && (d.accelFactPE > 1.0)) fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); + if (d.accelFactPE > 1.0) + fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); + json_Str(fp, "\t\"ParallelAcquisitionTechnique\": \"%s\",\n", d.parallelAcquisitionTechnique); + //https://github.com/rordenlab/dcm2niix/issues/314 + if (d.accelFactOOP > 1.0) + fprintf(fp, "\t\"ParallelReductionOutOfPlane\": %g,\n", d.accelFactOOP); + //EffectiveEchoSpacing + // Siemens bandwidthPerPixelPhaseEncode already accounts for the effects of parallel imaging, + // interpolation, phaseOversampling, and phaseResolution, in the context of the size of the + // *reconstructed* data in the PE dimension + double effectiveEchoSpacing = 0.0; + //next: dicm2nii's method for determining effectiveEchoSpacing if bandwidthPerPixelPhaseEncode is unknown, see issue 315 + //if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode <= 0.0) && (d.CSA.sliceMeasurementDuration >= 0)) + // effectiveEchoSpacing = d.CSA.sliceMeasurementDuration / (reconMatrixPE * 1000.0); + if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) + effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); + json_Float(fp, "\t\"WaterFatShift\": %g,\n", d.waterFatShift); + if ((effectiveEchoSpacing == 0.0) && (d.imagingFrequency > 0.0) && (d.waterFatShift != 0.0) && (d.echoTrainLength > 0) && (reconMatrixPE > 1)) { + //in theory we could use either fieldStrength or imagingFrequency, but the former is typically provided with low precision + //https://github.com/rordenlab/dcm2niix/issues/377 + // EchoSpacing 1/BW/EPI_factor https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=ind1308&L=FSL&D=0&P=113520 + // this formula from https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth + // https://neurostars.org/t/consolidating-epi-echo-spacing-and-readout-time-for-philips-scanner/4406 + /* + ActualEchoSpacing = WaterFatShift / (ImagingFrequency * 3.4 * (EPI_Factor + 1)) + TotalReadoutTIme = ActualEchoSpacing * EPI_Factor + EffectiveEchoSpacing = TotalReadoutTime / (ReconMatrixPE - 1) + WaterFatShift = 2001,1022 + ImagingFrequency = 0018,0084 + EPI_Factor = 0018,0091 or 2001,1013 + ReconMatrixPE = 0028,0010 or 0028,0011 depending on 0018,1312 + */ + float actualEchoSpacing = d.waterFatShift / (d.imagingFrequency * 3.4 * (d.echoTrainLength + 1)); + float totalReadoutTime = actualEchoSpacing * d.echoTrainLength; + float effectiveEchoSpacingPhil = totalReadoutTime / (reconMatrixPE - 1); + json_Float(fp, "\t\"EstimatedEffectiveEchoSpacing\": %g,\n", effectiveEchoSpacingPhil); + fprintf(fp, "\t\"EstimatedTotalReadoutTime\": %g,\n", totalReadoutTime); + } + if (d.effectiveEchoSpacingGE > 0.0) { + //TotalReadoutTime = [ ceil (PE_AcquisitionMatrix / Asset_R_factor) - 1] * ESP + float roundFactor = 2.0; + if (d.isPartialFourier) + roundFactor = 4.0; + float totalReadoutTime = ((ceil(1 / roundFactor * d.phaseEncodingLines / d.accelFactPE) * roundFactor) - 1.0) * d.effectiveEchoSpacingGE * 0.000001; + //printf("ASSET= %g PE_AcquisitionMatrix= %d ESP= %d TotalReadoutTime= %g\n", d.accelFactPE, d.phaseEncodingLines, d.effectiveEchoSpacingGE, totalReadoutTime); + //json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", totalReadoutTime); + effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); + } + json_Float(fp, "\t\"EffectiveEchoSpacing\": %g,\n", effectiveEchoSpacing); + // Calculate true echo spacing (should match what Siemens reports on the console) + // i.e., should match "echoSpacing" extracted from the ASCII CSA header, when that exists + double trueESfactor = 1.0; + if (d.accelFactPE > 1.0) + trueESfactor /= d.accelFactPE; + if (phaseOversampling > 0.0) + trueESfactor *= (1.0 + phaseOversampling); + float derivedEchoSpacing = 0.0; + derivedEchoSpacing = bandwidthPerPixelPhaseEncode * trueESfactor * reconMatrixPE; + if (derivedEchoSpacing != 0) + derivedEchoSpacing = 1 / derivedEchoSpacing; + json_Float(fp, "\t\"DerivedVendorReportedEchoSpacing\": %g,\n", derivedEchoSpacing); + //TotalReadOutTime: Really should be called "EffectiveReadOutTime", by analogy with "EffectiveEchoSpacing". + // But BIDS spec calls it "TotalReadOutTime". + // So, we DO NOT USE EchoTrainLength, because not trying to compute the actual (physical) readout time. + // Rather, the point of computing "EffectiveEchoSpacing" properly is so that this + // "Total(Effective)ReadOutTime" can be computed straightforwardly as the product of the + // EffectiveEchoSpacing and the size of the *reconstructed* matrix in the PE direction. + // see https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide#A--datain + // FSL definition is start of first line until start of last line. + // Other than the use of (n-1), the value is basically just 1.0/bandwidthPerPixelPhaseEncode. + // https://github.com/rordenlab/dcm2niix/issues/130 + if ((d.manufacturer == kMANUFACTURER_UIH) && (effectiveEchoSpacing <= 0.0)) //issue225, issue531 + json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", d.acquisitionDuration / 1000.0); + else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0) ) + fprintf(fp, "\t\"TotalReadoutTime\": %g,\n", effectiveEchoSpacing * (reconMatrixPE - 1.0)); + json_Float(fp, "\t\"PixelBandwidth\": %g,\n", d.pixelBandwidth); + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.dwellTime > 0)) + fprintf(fp, "\t\"DwellTime\": %g,\n", d.dwellTime * 1E-9); + // Phase encoding polarity + int phPos = d.CSA.phaseEncodingDirectionPositive; + //next two conditionals updated: make GE match Siemens + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + phPos = 1; + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) + phPos = 0; + //if ((phPos >= 0) && (d.phaseEncodingRC == 'R') && (d.manufacturer == kMANUFACTURER_UIH)) phPos = 1 - phPos; //issue410 + bool isSkipPhaseEncodingAxis = d.is3DAcq; + if (d.echoTrainLength > 1) + isSkipPhaseEncodingAxis = false; //issue 371: ignore phaseEncoding for 3D MP-RAGE/SPACE, but report for 3D EPI + if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && (phPos < 0)) { + //when phase encoding axis is known but we do not know phase encoding polarity + // https://github.com/rordenlab/dcm2niix/issues/163 + // This will typically correspond with InPlanePhaseEncodingDirectionDICOM + if (d.phaseEncodingRC == 'C') //Values should be "R"ow, "C"olumn or "?"Unknown + fprintf(fp, "\t\"PhaseEncodingAxis\": \"j\",\n"); + else if (d.phaseEncodingRC == 'R') + fprintf(fp, "\t\"PhaseEncodingAxis\": \"i\",\n"); + } + if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && (phPos >= 0)) { + //printf("%ld %d %d %c %d\n", d.seriesNum, d.echoTrainLength, isSkipPhaseEncodingAxis, d.phaseEncodingRC, phPos); //test issue 371 + if (d.phaseEncodingRC == 'C') //Values should be "R"ow, "C"olumn or "?"Unknown + fprintf(fp, "\t\"PhaseEncodingDirection\": \"j"); + else if (d.phaseEncodingRC == 'R') + fprintf(fp, "\t\"PhaseEncodingDirection\": \"i"); + else + fprintf(fp, "\t\"PhaseEncodingDirection\": \"?"); + //phaseEncodingDirectionPositive has one of three values: UNKNOWN (-1), NEGATIVE (0), POSITIVE (1) + //However, DICOM and NIfTI are reversed in the j (ROW) direction + //Equivalent to dicm2nii's "if flp(iPhase), phPos = ~phPos; end" + //for samples see https://github.com/rordenlab/dcm2niix/issues/125 + if (phPos < 0) + fprintf(fp, "?"); //unknown + else if ((phPos == 0) && (d.phaseEncodingRC != 'C')) + fprintf(fp, "-"); + else if ((d.phaseEncodingRC == 'C') && (phPos == 1) && (opts.isFlipY)) + fprintf(fp, "-"); + else if ((d.phaseEncodingRC == 'C') && (phPos == 0) && (!opts.isFlipY)) + fprintf(fp, "-"); + fprintf(fp, "\",\n"); + } //only save PhaseEncodingDirection if BOTH direction and POLARITY are known + //Slice Timing UIH or GE >>>> + //in theory, we should also report XA10 slice times here, but see series 24 of https://github.com/rordenlab/dcm2niix/issues/236 + if ((d.modality != kMODALITY_CT) && (d.modality != kMODALITY_PT) && (!d.is3DAcq) && (d.CSA.sliceTiming[0] >= 0.0)) { + fprintf(fp, "\t\"SliceTiming\": [\n"); + for (int i = 0; i < h->dim[3]; i++) { + if (i != 0) + fprintf(fp, ",\n"); + fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0); + } + fprintf(fp, "\t],\n"); + } + //DICOM orientation and phase encoding: useful for 3D undistortion. Original DICOM values: DICOM not NIfTI space, ignores if 3D image re-oriented + float mxOrient = 0.0; + for (int i = 1; i < 7; i++) + mxOrient = max(mxOrient, fabs(d.orient[i])); + if (! isSameFloatGE(mxOrient, 0.0)) { //if set + fprintf(fp, "\t\"ImageOrientationPatientDICOM\": [\n"); + for (int i = 1; i < 7; i++) { + if (i != 1) + fprintf(fp, ",\n"); + fprintf(fp, "\t\t%g", d.orient[i]); + } + fprintf(fp, "\t],\n"); + } + json_Str(fp, "\t\"ImageOrientationText\": \"%s\",\n", d.imageOrientationText); + if (d.phaseEncodingRC == 'C') + fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"COL\",\n"); + if (d.phaseEncodingRC == 'R') + fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"ROW\",\n"); + // Finish up with info on the conversion tool + fprintf(fp, "\t\"ConversionSoftware\": \"dcm2niix\",\n"); + fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMdate); + //fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMvers );kDCMdate + fprintf(fp, "}\n"); + fclose(fp); +} // nii_SaveBIDSX() + +#ifndef USING_R + +void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { + //swap endian from big->little or little->big + // must be told which is native to detect datatype and number of voxels + // one could also auto-detect: hdr->sizeof_hdr==348 + if (!isNative) + swap_nifti_header(hdr, 1); + int nVox = 1; + for (int i = 1; i < 8; i++) + if (hdr->dim[i] > 1) + nVox = nVox * hdr->dim[i]; + int bitpix = hdr->bitpix; + int datatype = hdr->datatype; + if (isNative) + swap_nifti_header(hdr, 1); + if (datatype == DT_RGBA32) + return; + //n.b. do not swap 8-bit, 24-bit RGB, and 32-bit RGBA + if (bitpix == 16) + nifti_swap_2bytes(nVox, im); + if (bitpix == 32) + nifti_swap_4bytes(nVox, im); + if (bitpix == 64) + nifti_swap_8bytes(nVox, im); +} + +void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char *filename) { + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); + dti4D->sliceOrder[0] = -1; + dti4D->volumeOnsetTime[0] = -1; + dti4D->decayFactor[0] = -1; + dti4D->triggerDelayTime[0] = -1.0; + dti4D->intenScale[0] = 0.0; + dti4D->repetitionTimeExcitation = 0.0; + dti4D->repetitionTimeInversion = 0.0; + nii_SaveBIDSX(pathoutname, d, opts, h, filename, dti4D); + free(dti4D); +} // nii_SaveBIDSX() + +#endif + +unsigned char *removeADC(struct nifti_1_header *hdr, unsigned char *inImg, int numADC) { + //for speed we just clip the number of volumes, the realloc routine would be nice + // we do not want to copy input to a new smaller array since 4D DTI datasets can be huge + // and that would require almost twice as much RAM + if (numADC < 1) + return inImg; + hdr->dim[4] = hdr->dim[4] - numADC; + if (hdr->dim[4] < 2) + hdr->dim[0] = 3; //e.g. 4D 2-volume DWI+ADC becomes 3D DWI if ADC is removed + return inImg; +} //removeADC() + +//#define naive_reorder_vols //for simple, fast re-ordering that consumes a lot of RAM +#ifdef naive_reorder_vols +unsigned char *reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int *volOrderIndex) { + //reorder volumes to place ADC at end and (optionally) B=0 at start + // volOrderIndex[0] reports location of desired first volume + // naive solution creates an output buffer that doubles RAM usage (2 *numVol) + int numVol = hdr->dim[4]; + int numVolBytes = hdr->dim[1] * hdr->dim[2] * hdr->dim[3] * (hdr->bitpix / 8); + if ((!volOrderIndex) || (numVol < 1) || (numVolBytes < 1)) + return inImg; + unsigned char *outImg = (unsigned char *)malloc(numVolBytes * numVol); + int outPos = 0; + for (int i = 0; i < numVol; i++) { + memcpy(&outImg[outPos], &inImg[volOrderIndex[i] * numVolBytes], numVolBytes); // dest, src, bytes + outPos += numVolBytes; + } //for each volume + free(volOrderIndex); + free(inImg); + return outImg; +} //reorderVolumes() +#else // naive_reorder_vols +unsigned char *reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int *volOrderIndex) { + //reorder volumes to place ADC at end and (optionally) B=0 at start + // volOrderIndex[0] reports location of desired first volume + // complicated by fact that 4D DTI data is often huge + // simple solutions would create an output buffer that would double RAM usage (2 *numVol) + // here we bubble-sort volumes in place to use numVols+1 memory + int numVol = hdr->dim[4]; + int numVolBytes = hdr->dim[1] * hdr->dim[2] * hdr->dim[3] * (hdr->bitpix / 8); + int *inPos = (int *)malloc(numVol * sizeof(int)); + for (int i = 0; i < numVol; i++) + inPos[i] = i; + unsigned char *tempVol = (unsigned char *)malloc(numVolBytes); + int outPos = 0; + for (int o = 0; o < numVol; o++) { + int i = inPos[volOrderIndex[o]]; //input volume + if (i == o) + continue; //volume in correct order + memcpy(&tempVol[0], &inImg[o * numVolBytes], numVolBytes); //make temp + memcpy(&inImg[o * numVolBytes], &inImg[i * numVolBytes], numVolBytes); //copy volume to desire location dest, src, bytes + memcpy(&inImg[i * numVolBytes], &tempVol[0], numVolBytes); //copy unsorted volume + inPos[o] = i; + outPos += numVolBytes; + } //for each volume + free(inPos); + free(volOrderIndex); + free(tempVol); + return inImg; +} //reorderVolumes() +#endif // naive_reorder_vols + +float *bvals; //global variable for cmp_bvals +int cmp_bvals(const void *a, const void *b) { + int ia = *(int *)a; + int ib = *(int *)b; + //return bvals[ia] > bvals[ib] ? -1 : bvals[ia] < bvals[ib]; + return bvals[ia] < bvals[ib] ? -1 : bvals[ia] > bvals[ib]; +} // cmp_bvals() + +bool isAllZeroFloat(float v1, float v2, float v3) { + if (!isSameFloatGE(v1, 0.0)) + return false; + if (!isSameFloatGE(v2, 0.0)) + return false; + if (!isSameFloatGE(v3, 0.0)) + return false; + return true; +} + +int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TDCMopts opts, int sliceDir, struct TDTI4D *dti4D, int *numADC, int numVol) { + //reports non-zero if any volumes should be excluded (e.g. philip stores an ADC maps) + *numADC = 0; + if (opts.isOnlyBIDS) + return NULL; + uint64_t indx0 = dcmSort[0].indx; //first volume + int numDti = dcmList[indx0].CSA.numDti; +#ifdef USING_R + ImageList *images = (ImageList *)opts.imageList; +#endif + //https://github.com/rordenlab/dcm2niix/issues/352 + bool allB0 = dcmList[indx0].isDiffusion; + if (dcmList[indx0].isDerived) + allB0 = false; //e.g. FA map + if ((numDti == numVol) && (numDti > 1)) + allB0 = false; + if (numDti > 1) + allB0 = false; + if (nConvert > 1) + allB0 = false; + if ((numDti == 1) && (dti4D->S[0].V[0] > 50.0)) + allB0 = false; + if (allB0) { + if (opts.isVerbose) + printMessage("Diffusion image without gradients: assuming %d volume B=0 series\n", numVol); +#ifdef USING_R + // The image hasn't been created yet, so the attributes must be deferred + images->addDeferredAttribute("bValues", std::vector(numVol, 0.0)); + images->addDeferredAttribute("bVectors", std::vector(numVol * 3, 0.0), numVol, 3); +#else + char sep = '\t'; + if (opts.isCreateBIDS) + sep = ' '; + + if (opts.isVerbose) + printMessage("save bval and bvec for allB0 is true\n"); + + //save bval + char txtname[2048] = {""}; + strcpy(txtname, pathoutname); + strcat(txtname, ".bval"); + FILE *fp = fopen(txtname, "w"); + for (int i = 0; i < (numVol); i++) + fprintf(fp, "%d%c", 0, sep); + fprintf(fp, "\n"); + fclose(fp); + //save bvec + strcpy(txtname, pathoutname); + strcat(txtname, ".bvec"); + fp = fopen(txtname, "w"); + for (int v = 0; v < (3); v++) { + for (int i = 0; i < (numVol); i++) + fprintf(fp, "%d%c", 0, sep); + fprintf(fp, "\n"); + } + fclose(fp); +#endif + } + if (numDti < 1) + return NULL; + if ((numDti < 3) && (nConvert < 3)) + return NULL; + TDTI *vx = NULL; + if (numDti > 2) { + vx = (TDTI *)malloc(numDti * sizeof(TDTI)); + for (int i = 0; i < numDti; i++) //for each direction + for (int v = 0; v < 4; v++) //for each vector+B-value + vx[i].V[v] = dti4D->S[i].V[v]; + } else { //if (numDti == 1) {//extract DTI from different slices + vx = (TDTI *)malloc(nConvert * sizeof(TDTI)); + numDti = 0; + for (int i = 0; i < nConvert; i++) { //for each image + if ((dcmList[indx0].CSA.mosaicSlices > 1) || (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx]))) { + //if (numDti < kMaxDTIv) + for (int v = 0; v < 4; v++) //for each vector+B-value + vx[numDti].V[v] = dcmList[dcmSort[i].indx].CSA.dtiV[v]; //dcmList[indx0].CSA.dtiV[numDti][v] = dcmList[dcmSort[i].indx].CSA.dtiV[0][v]; + numDti++; + } //for slices with repeats + } //for each file + dcmList[indx0].CSA.numDti = numDti; //warning structure not changed outside scope! + } + bool bValueVaries = false; + for (int i = 1; i < numDti; i++) //check if all bvalues match first volume + if (vx[i].V[0] != vx[0].V[0]) + bValueVaries = true; + //optional: record b-values even without variability + float minBval = vx[0].V[0]; + for (int i = 1; i < numDti; i++) //check if all bvalues match first volume + if (vx[i].V[0] < minBval) + minBval = vx[i].V[0]; + if (minBval > 50.0) + bValueVaries = true; + //start issue394: experimental, single volume per series, Siemens XA + if (!isAllZeroFloat(vx[0].V[1], vx[0].V[2], vx[0].V[3])) + bValueVaries = true; + //end issue394 + if (!bValueVaries) { + bool bVecVaries = false; + for (int i = 1; i < numDti; i++) { //check if all bvalues match first volume + if (vx[i].V[1] != vx[0].V[1]) + bVecVaries = true; + if (vx[i].V[2] != vx[0].V[2]) + bVecVaries = true; + if (vx[i].V[3] != vx[0].V[3]) + bVecVaries = true; + } + if (!bVecVaries) { + free(vx); + return NULL; + } + if (opts.isVerbose) { + for (int i = 0; i < numDti; i++) + printMessage("bxyz %g %g %g %g\n", vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); + } + //Stutters XINAPSE7 seem to save B=0 as B=2000, but these are not derived? https://github.com/rordenlab/dcm2niix/issues/182 + bool bZeroBvec = false; + for (int i = 0; i < numDti; i++) { //check if all bvalues match first volume + if (isAllZeroFloat(vx[i].V[1], vx[i].V[2], vx[i].V[3])) { + vx[i].V[0] = 0; + //printWarning("volume %d might be B=0\n", i); + bZeroBvec = true; + } + } + if (bZeroBvec) + printWarning("Assuming volumes without gradients are actually B=0\n"); + else { + printWarning("No bvec/bval files created. Only one B-value reported for all volumes: %g\n", vx[0].V[0]); + free(vx); + return NULL; + } + } + //report values: + //for (int i = 1; i < numDti; i++) //check if all bvalues match first volume + // printMessage("%d bval= %g bvec= %g %g %g\n",i, vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); + int minB0idx = 0; + float minB0 = vx[0].V[0]; + for (int i = 0; i < numDti; i++) + if (vx[i].V[0] < minB0) { + minB0 = vx[i].V[0]; + minB0idx = i; + } + float maxB0 = vx[0].V[0]; + for (int i = 0; i < numDti; i++) + if (vx[i].V[0] > maxB0) + maxB0 = vx[i].V[0]; + //for CMRR sequences unweighted volumes are not actually B=0 but they have B near zero + if (minB0 > 50) + printWarning("This diffusion series does not have a B0 (reference) volume\n"); + if ((!opts.isSortDTIbyBVal) && (minB0idx > 0)) + printMessage("Note: B0 not the first volume in the series (FSL eddy reference volume is %d)\n", minB0idx); + float kADCval = maxB0 + 1; //mark as unusual + *numADC = 0; + bvals = (float *)malloc(numDti * sizeof(float)); + int numGEwarn = 0; + bool isGEADC = (dcmList[indx0].numberOfDiffusionDirectionGE == 0); + for (int i = 0; i < numDti; i++) { + bvals[i] = vx[i].V[0]; + //printMessage("---bxyz %g %g %g %g\n",vx[i].V[0],vx[i].V[1],vx[i].V[2],vx[i].V[3]); + //Philips includes derived isotropic images + //if (((dcmList[indx0].manufacturer == kMANUFACTURER_GE) || (dcmList[indx0].manufacturer == kMANUFACTURER_PHILIPS)) && (isADCnotDTI(vx[i]))) { + if (((dcmList[indx0].manufacturer == kMANUFACTURER_GE)) && (isADCnotDTI(vx[i]))) { + numGEwarn += 1; + if (isGEADC) { //e.g. GE Trace where bval=900, bvec=0,0,0 + *numADC = *numADC + 1; + //printWarning("GE ADC volume %d\n", i+1); + bvals[i] = kADCval; + } else + vx[i].V[0] = 0; //e.g. GE raw B=0 where bval=900, bvec=0,0,0 + } //see issue 245, however this does impact some anonymized files where bvec but not bval removed https://neurostars.org/t/dcm2bids-after-conversion-to-bids-bvals-are-zeros/20198/11 + if (((dcmList[indx0].manufacturer == kMANUFACTURER_PHILIPS)) && (isADCnotDTI(vx[i]))) { + *numADC = *numADC + 1; + bvals[i] = kADCval; + //printMessage("+++bxyz %d\n",i); + } + bvals[i] = bvals[i] + (0.5 * i / numDti); //add a small bias so ties are kept in sequential order + } + if (numGEwarn > 0) + printWarning("Some images had bval>0 but bvec=0 (either Trace or b=0, see issue 245)\n"); + /*if ((*numADC == numDti) || (numGEwarn == numDti)) { //issue 405: we now save bvals file for isotropic series + //all isotropic/ADC images - no valid bvecs + *numADC = 0; + free(bvals); + free(vx); + return NULL; + }*/ + bool isIsotropic = false; + if ((*numADC == numDti) || (numGEwarn == numDti)) //issue 405: we now save bvals file for isotropic series + isIsotropic = true; + if (*numADC > 0) { + // DWIs (i.e. short diffusion scans with too few directions to + // calculate tensors...they typically acquire b=0 + 3 b > 0 so + // the isotropic trace or MD can be calculated) often come as + // b=0 and trace pairs, with the b=0 and trace in either order, + // and often as "ORIGINAL", even though the trace is not. + // The bval file is needed for downstream processing to know + // * which is the b=0 and which is the trace, and + // * what b is for the trace, + // so dcm2niix should *always* write the bval and bvec files, + // AND include the b for the trace for DWIs. + // One hackish way to accomplish that is to set *numADC = 0 + // when *numADC == 1 && numDti == 2. + // - Rob Reid, 2017-11-29. + if ((*numADC == 1) && ((numDti - *numADC) < 2)) { + *numADC = 0; + printMessage("Note: this appears to be a b=0+trace DWI; ADC/trace removal has been disabled.\n"); + } else { + if ((numDti - *numADC) < 2) { + /*if (!dcmList[indx0].isDerived) //no need to warn if images are derived Trace/ND pair + printWarning("No bvec/bval files created: only single value after ADC excluded\n"); + *numADC = 0; + free(bvals); + free(vx); + return NULL;*/ + printMessage("Warning: Isotropic DWI series, all bvecs are zero (issue 405)\n"); + *numADC = 0; + } else + printMessage("Note: %d volumes appear to be ADC or trace images that will be removed to allow processing\n", *numADC); + } + } + //sort ALL including ADC + int *volOrderIndex = (int *)malloc(numDti * sizeof(int)); + for (int i = 0; i < numDti; i++) + volOrderIndex[i] = i; + if (opts.isSortDTIbyBVal) + qsort(volOrderIndex, numDti, sizeof(*volOrderIndex), cmp_bvals); + else if (*numADC > 0) { + int o = 0; + for (int i = 0; i < numDti; i++) { + if (bvals[i] < kADCval) { + volOrderIndex[o] = i; + o++; + } //if not ADC + } //for each volume + } //if sort else if has ADC + free(bvals); + //save VX as sorted + TDTI *vxOrig = (TDTI *)malloc(numDti * sizeof(TDTI)); + for (int i = 0; i < numDti; i++) + vxOrig[i] = vx[i]; + //remove ADC + numDti = numDti - *numADC; + free(vx); + vx = (TDTI *)malloc(numDti * sizeof(TDTI)); + for (int i = 0; i < numDti; i++) + vx[i] = vxOrig[volOrderIndex[i]]; + free(vxOrig); + //if no ADC or sequential, the is no need to re-order volumes + bool isSequential = true; + for (int i = 1; i < (numDti + *numADC); i++) + if (volOrderIndex[i] <= volOrderIndex[i - 1]) + isSequential = false; + if (isSequential) { + free(volOrderIndex); + volOrderIndex = NULL; + } + if (!isSequential) + printMessage("DTI volumes re-ordered by ascending b-value\n"); +#ifdef RAW_BVEC //save raw bvecs as reported by DICOM, not rotated to image space + char txtname[2048] = {""}; + strcpy(txtname, pathoutname); + strcat(txtname, ".rvec"); + FILE *fp = fopen(txtname, "w"); + for (int i = 0; i < numDti; i++) + fprintf(fp, "%g\t", vx[i].V[1]); + fprintf(fp, "\n"); + for (int i = 0; i < numDti; i++) + fprintf(fp, "%g\t", vx[i].V[2]); + fprintf(fp, "\n"); + for (int i = 0; i < numDti; i++) + fprintf(fp, "%g\t", vx[i].V[3]); + fprintf(fp, "\n"); + fclose(fp); +#endif + dcmList[indx0].CSA.numDti = numDti; //warning structure not changed outside scope! + geCorrectBvecs(&dcmList[indx0], sliceDir, vx, opts.isVerbose); + siemensPhilipsCorrectBvecs(&dcmList[indx0], sliceDir, vx, opts.isVerbose); + if (dcmList[indx0].CSA.numDti < 1) { //issue449 + free(vx); + return NULL; + } + if (!opts.isFlipY) { //!FLIP_Y&& (dcmList[indx0].CSA.mosaicSlices < 2) mosaics are always flipped in the Y direction + for (int i = 0; i < (numDti); i++) { + if (fabs(vx[i].V[2]) > FLT_EPSILON) + vx[i].V[2] = -vx[i].V[2]; + } //for each direction + } //if not a mosaic + if (opts.isVerbose) { + for (int i = 0; i < (numDti); i++) { + printMessage("%d\tB=\t%g\tVec=\t%g\t%g\t%g\n", i, vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); + } //for each direction + } + //printMessage("%f\t%f\t%f",dcmList[indx0].CSA.dtiV[1][1],dcmList[indx0].CSA.dtiV[1][2],dcmList[indx0].CSA.dtiV[1][3]); +#ifdef USING_R + std::vector bValues(numDti); + std::vector bVectors(numDti * 3); + for (int i = 0; i < numDti; i++) { + bValues[i] = vx[i].V[0]; + for (int j = 0; j < 3; j++) + bVectors[i + j * numDti] = vx[i].V[j + 1]; + } + // The image hasn't been created yet, so the attributes must be deferred + images->addDeferredAttribute("bValues", bValues); + images->addDeferredAttribute("bVectors", bVectors, numDti, 3); +#else + if (opts.saveFormat != kSaveFormatNIfTI) { + if (numDti < kMaxDTI4D) { + dcmList[indx0].CSA.numDti = numDti; + for (int i = 0; i < numDti; i++) //for each direction + for (int v = 0; v < 4; v++) //for each vector+B-value + dti4D->S[i].V[v] = vx[i].V[v]; + } +#ifdef MGH_FREESURFER + mrifsStruct.tdti = vx; + mrifsStruct.numDti = numDti; +#else + free(vx); +#endif + return volOrderIndex; + } + +#ifndef MGH_FREESURFER + 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); +#endif + if (isIsotropic) { //issue 405: ISOTROPIC images have bval but not bvec +#ifdef MGH_FREESURFER + mrifsStruct.tdti = vx; + mrifsStruct.numDti = numDti; +#else + free(vx); +#endif + return volOrderIndex; + } + +#ifndef MGH_FREESURFER + strcpy(txtname, pathoutname); + if (dcmList[indx0].isVectorFromBMatrix) + strcat(txtname, ".mvec"); + else + strcat(txtname, ".bvec"); + //printMessage("Saving DTI %s\n",txtname); + fp = fopen(txtname, "w"); + if (fp == NULL) { + free(vx); + return volOrderIndex; + } + for (int v = 1; v < 4; v++) { + for (int i = 0; i < (numDti - 1); i++) { + if (opts.isCreateBIDS) { + fprintf(fp, "%g ", vx[i].V[v]); + } else { + fprintf(fp, "%g\t", vx[i].V[v]); + } + } + fprintf(fp, "%g\n", vx[numDti - 1].V[v]); + } + fclose(fp); +#endif +#endif + +#ifdef MGH_FREESURFER + mrifsStruct.tdti = vx; + mrifsStruct.numDti = numDti; +#else + free(vx); +#endif + return volOrderIndex; +} // nii_saveDTI() + +float sqr(float v) { + return v * v; +} // sqr() + +#ifdef newTilt //see issue 254 +float vec3Length(vec3 v) { //normalize vector length + return sqrt((v.v[0] * v.v[0]) + (v.v[1] * v.v[1]) + (v.v[2] * v.v[2])); +} + +float vec3maxMag(vec3 v) { //return signed vector with maximum magnitude + float mx = v.v[0]; + if (fabs(v.v[1]) > fabs(mx)) + mx = v.v[1]; + if (fabs(v.v[2]) > fabs(mx)) + mx = v.v[2]; + return mx; +} + +vec3 makePositive(vec3 v) { + //we do not no order of cross product or order of instance number (e.g. head->foot, foot->head) + // this function matches the polarity of slice direction inferred from patient position and image orient + vec3 ret = v; + if (vec3maxMag(v) >= 0.0) + return ret; + ret.v[0] = -ret.v[0]; + ret.v[1] = -ret.v[1]; + ret.v[2] = -ret.v[2]; + return ret; +} + +void vecRep(vec3 v) { //normalize vector length + printMessage("[%g %g %g]\n", v.v[0], v.v[1], v.v[2]); +} + +//Precise method for determining gantry tilt +// rationale: +// gantry tilt (0018,1120) is optional +// some tools may correct gantry tilt but not reset 0018,1120 +// 0018,1120 might be saved at low precision (though patientPosition, orient might be as well) +//https://github.com/rordenlab/dcm2niix/issues/253 +float computeGantryTiltPrecise(struct TDICOMdata d1, struct TDICOMdata d2, int isVerbose) { + float ret = 0.0; + if (isNanPosition(d1)) + return ret; + vec3 slice_vector = setVec3(d2.patientPosition[1] - d1.patientPosition[1], + d2.patientPosition[2] - d1.patientPosition[2], + d2.patientPosition[3] - d1.patientPosition[3]); + float len = vec3Length(slice_vector); + if (isSameFloat(len, 0.0)) { + slice_vector = setVec3(d1.patientPositionLast[1] - d1.patientPosition[1], + d1.patientPositionLast[2] - d1.patientPosition[2], + d1.patientPositionLast[3] - d1.patientPosition[3]); + len = vec3Length(slice_vector); + if (isSameFloat(len, 0.0)) + return ret; + } + if (isnan(slice_vector.v[0])) + return ret; + slice_vector = makePositive(slice_vector); + vec3 read_vector = setVec3(d1.orient[1], d1.orient[2], d1.orient[3]); + vec3 phase_vector = setVec3(d1.orient[4], d1.orient[5], d1.orient[6]); + vec3 slice_vector90 = crossProduct(read_vector, phase_vector); //perpendicular + slice_vector90 = makePositive(slice_vector90); + float len90 = vec3Length(slice_vector90); + if (isSameFloat(len90, 0.0)) + return ret; + float dotX = dotProduct(slice_vector90, slice_vector); + float cosX = dotX / (len * len90); + float degX = acos(cosX) * (180.0 / M_PI); //arccos, radian -> degrees + if (!isSameFloat(cosX, 1.0)) + ret = degX; + if ((isSameFloat(ret, 0.0)) && (isSameFloat(ret, d1.gantryTilt))) + return 0.0; + //determine if gantry tilt is positive or negative + vec3 signv = crossProduct(slice_vector, slice_vector90); + float sign = vec3maxMag(signv); + if (isSameFloatGE(ret, 0.0)) + return 0.0; //parallel vectors + if (sign > 0.0) + ret = -ret; //the length of len90 was negative, negative gantry tilt + //while (ret >= 89.99) ret -= 90; + //while (ret <= -89.99) ret += 90; + if (isSameFloatGE(ret, 0.0)) + return 0.0; + if ((isVerbose) || (isnan(ret))) { + printMessage("Gantry Tilt Parameters (see issue 253)\n"); + printMessage(" Read ="); + vecRep(read_vector); + printMessage(" Phase ="); + vecRep(phase_vector); + printMessage(" CrossReadPhase ="); + vecRep(slice_vector90); + printMessage(" Slice ="); + vecRep(slice_vector); + } + printMessage("Gantry Tilt based on 0018,1120 %g, estimated from slice vector %g\n", d1.gantryTilt, ret); + return ret; +} +#endif //newTilt //see issue 254 + +float intersliceDistance(struct TDICOMdata d1, struct TDICOMdata d2) { + //some MRI scans have gaps between slices, some CT have overlapping slices. Comparing adjacent slices provides measure for dx between slices + if (isNanPosition(d1) || isNanPosition(d2)) + return d1.xyzMM[3]; + float tilt = 1.0; + //printMessage("0020,0032 %g %g %g -> %g %g %g\n",d1.patientPosition[1],d1.patientPosition[2],d1.patientPosition[3],d2.patientPosition[1],d2.patientPosition[2],d2.patientPosition[3]); + if (d1.gantryTilt != 0) + tilt = (float)cos(d1.gantryTilt * M_PI / 180); //for CT scans with gantry tilt, we need to compute distance between slices, not distance along bed + return tilt * sqrt(sqr(d1.patientPosition[1] - d2.patientPosition[1]) + + sqr(d1.patientPosition[2] - d2.patientPosition[2]) + + sqr(d1.patientPosition[3] - d2.patientPosition[3])); +} //intersliceDistance() + +//#define myInstanceNumberOrderIsNotSpatial +//instance number is virtually always ordered based on spatial position. +// interleaved/multi-band conversion will be disrupted if instance number refers to temporal order +// these functions reorder images based on spatial position +// this situation is exceptionally rare, and there is a performance penalty +// further, there may be unintended consequences. +// Therefore, use of myInstanceNumberOrderIsNotSpatial is NOT recommended +// a better solution is to fix the sequences that generated those files +// as such images will probably disrupt most tools. +// This option is only to salvage borked data. +// This code has also not been tested on data stored in TXYZ rather than XYZT order +//#ifdef myInstanceNumberOrderIsNotSpatial + +float intersliceDistanceSigned(struct TDICOMdata d1, struct TDICOMdata d2) { +// Compute the signed slice position on the through-slice axis +// https://nipy.org/nibabel/dicom/dicom_orientation.html#working-out-the-z-coordinates-for-a-set-of-slices +// https://itk.org/pipermail/insight-users/2003-September/004762.html +//reports distance between two slices, signed as 2nd slice can be in front or behind 1st + vec3 slice_vector = setVec3(d2.patientPosition[1] - d1.patientPosition[1], + d2.patientPosition[2] - d1.patientPosition[2], + d2.patientPosition[3] - d1.patientPosition[3]); + float len = vec3Length(slice_vector); + if (isSameFloat(len, 0.0)) + return len; + if (d1.gantryTilt != 0) + len = len * cos(d1.gantryTilt * M_PI / 180); + vec3 read_vector = setVec3(d1.orient[1], d1.orient[2], d1.orient[3]); + vec3 phase_vector = setVec3(d1.orient[4], d1.orient[5], d1.orient[6]); + vec3 slice_vector90 = crossProduct(read_vector, phase_vector); //perpendicular + float dot = dotProduct(slice_vector90, slice_vector); + if (dot < 0.0) + return -len; + return len; +} + +//https://stackoverflow.com/questions/36714030/c-sort-float-array-while-keeping-track-of-indices/36714204 +struct TFloatSort { + float position; + int volume, index; +}; + +int compareTFloatSort(const void *a, const void *b) { + struct TFloatSort *a1 = (struct TFloatSort *)a; + struct TFloatSort *a2 = (struct TFloatSort *)b; + if ((*a1).volume > (*a2).volume) + return 1; + if ((*a1).volume < (*a2).volume) + return -1; + if ((*a1).position > (*a2).position) + return 1; + if ((*a1).position < (*a2).position) + return -1; + //if value is tied, retain index order (useful for TXYZ images?) + if ((*a1).index > (*a2).index) + return 1; + if ((*a1).index < (*a2).index) + return -1; + return 0; +} // compareTFloatSort() + +bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], int verbose) { + //ensure slice position is sequential: either ascending [1 2 3] or descending [3 2 1], not [1 3 2], [3 1 2] etc. + //n.b. as currently designed, this will force swapDim3Dim4() for 4D data + int nConvert = d3 * d4; + if (d3 < 3) + return true; //always consistent + float dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + bool isConsistent = !isSameFloatGE(dx, 0.0); //slice distance of zero is not consistent with XYZT order (perhaps XYTZ) + bool isAscending1 = (dx > 0); + for (int v = 0; v < d4; v++) { + int volStart = v * d3; + if (!isSameFloatGE(intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[volStart].indx]), 0.0)) + isConsistent = false; //XYZT requires first slice of each volume is at same position + for (int i = 1; i < d3; i++) { + dx = intersliceDistanceSigned(dcmList[dcmSort[volStart + i - 1].indx], dcmList[dcmSort[volStart + i].indx]); + bool isAscending = (dx > 0); + //printf("volume %d slice %d distanceFromSlice1 %g DICOMvolume %d\n", v, i+1, dx, dcmList[dcmSort[volStart + i].indx].rawDataRunNumber); + if (isAscending != isAscending1) + isConsistent = false; //direction reverses + } + } + //if (isConsistent) + // return true; + TFloatSort *floatSort = (TFloatSort *)malloc(nConvert * sizeof(TFloatSort)); + int minVol = dcmList[dcmSort[0].indx].rawDataRunNumber; + int maxVol = minVol; + int maxVolNotADC = -1; + int minInstance = dcmList[dcmSort[0].indx].imageNum; + int maxInstance = minInstance; + int maxPhase = 1; //Philips Multi-Phase + for (int i = 0; i < nConvert; i++) { + int vol = dcmList[dcmSort[i].indx].rawDataRunNumber; + minVol = min(minVol, vol); + maxVol = max(maxVol, vol); + if (vol < kMaxDTI4D) + maxVolNotADC = max(maxVolNotADC, vol); + int instance = dcmList[dcmSort[i].indx].imageNum; + minInstance = min(minInstance, instance); + maxInstance = max(maxInstance, instance); + maxPhase = max(maxPhase, dcmList[dcmSort[i].indx].phaseNumber); + } + bool isUseInstanceNumberForVolume = false; + if ((d4 > 1) && (maxPhase == 1) && (minVol == maxVolNotADC) && (minInstance < maxInstance)) { + printWarning("Volume number does not vary (0019,10A2; 0020,0100; 2005,1063; 2005,1413), assuming meaningful instance number (0020,0013).\n"); + isUseInstanceNumberForVolume = true; + } + bool isVerbose = (verbose > 1); //issue533 + if (isVerbose) + printMessage("Ranges volume %d..%d instance %d..%d\n", minVol, maxVolNotADC, minInstance, maxInstance); //TODO + bool isASL = (dcmList[dcmSort[0].indx].aslFlags != kASL_FLAG_NONE); + //we will renumber volumes for Philips ASL (Contrast/Label, phase) and DWI (derived trace) + int minVolOut = kMaxDTI4D + 1; + int maxVolOut = -1; + bool isUsePhaseForVol = false; + if ((!isASL) && (minVol == maxVol) && (maxPhase > 1)) isUsePhaseForVol = true;//e.g. TurboQUASAR + bool isPhaseIsBValNumber =false; + if ((!isASL) && (minVol < maxVol) && (maxPhase > 1)) isPhaseIsBValNumber = true;//DWI track both gradient number and vector number issue 546 + if (isVerbose) + printMessage("InstanceNumber\tPosition\tVolume\tRepeat\tASLlabel\tPhase\tTriggerTime\n"); + for (int i = 0; i < nConvert; i++) { + int vol = dcmList[dcmSort[i].indx].rawDataRunNumber; + int rawvol = vol; + int instance = dcmList[dcmSort[i].indx].imageNum; + int phase = max(1, dcmList[dcmSort[i].indx].phaseNumber); + if (isUsePhaseForVol) vol = phase; + if (isPhaseIsBValNumber) vol += phase * maxVol; + int isAslLabel = dcmList[dcmSort[i].indx].aslFlags == kASL_FLAG_PHILIPS_LABEL; + dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); + if (isASL) { + #ifdef myMatchEnhanced00209157 //issue533: make classic DICOMs match enhanced DICOM volume order + //disk order: slice < repeat < phase < label/control + vol += (phase - 1) * maxVol; + if (isAslLabel) + vol += maxPhase * maxVol; + #else + //"temporal" disk order: slice < phase < label/control < repeat : should match instance number + vol = phase; + if (isAslLabel) + vol += maxPhase; + vol += (rawvol - 1) * (2 * maxPhase); + #endif + } + if (isUseInstanceNumberForVolume) + vol = instance; + if (isVerbose) //only report slice data for logorrheic verbosity + printMessage("%d\t%g\t%d\t%d\t%d\t%d\t%g\n", instance, dx, vol, rawvol, isAslLabel, phase, dcmList[dcmSort[i].indx].triggerDelayTime); + if (vol > kMaxDTI4D) //issue529 Philips derived Trace/ADC embedded into DWI + vol = maxVol + 1; + minVolOut = min(minVolOut, vol); + maxVolOut = max(maxVolOut, vol); + floatSort[i].volume = vol; + floatSort[i].position = dx; + floatSort[i].index = i; + } + //n.b. should we change dim[3] and dim[4] if number of volumes = dim[3]? + if ((!isPhaseIsBValNumber) && ((maxVolOut-minVolOut+1) != d4)) + printError("Check sorted order: 4D dataset has %d volumes, but volume index ranges from %d..%d\n", d4, minVolOut, maxVolOut); + //printf("dx = %g instance %d %d\n", intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]), dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[1].indx].imageNum); + TDCMsort *dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); + for (int i = 0; i < nConvert; i++) + dcmSortIn[i] = dcmSort[i]; + qsort(floatSort, nConvert, sizeof(struct TFloatSort), compareTFloatSort); //sort based on series and image numbers.... + for (int i = 0; i < nConvert; i++) + dcmSort[i] = dcmSortIn[floatSort[i].index]; + free(floatSort); + free(dcmSortIn); + //printf("dx = %g instance %d %d\n", intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]), dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[1].indx].imageNum); + return false; +} // ensureSequentialSlicePositions() + + +void swapDim3Dim4(int d3, int d4, struct TDCMsort dcmSort[]) { + //swap space and time: input A0,A1...An,B0,B1...Bn output A0,B0,A1,B1,... + int nConvert = d3 * d4; + TDCMsort *dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); + for (int i = 0; i < nConvert; i++) + dcmSortIn[i] = dcmSort[i]; + int i = 0; + for (int b = 0; b < d3; b++) + for (int a = 0; a < d4; a++) { + int k = (a * d3) + b; + //printMessage("%d -> %d %d ->%d\n",i,a, b, k); + dcmSort[k] = dcmSortIn[i]; + i++; + } + free(dcmSortIn); +} //swapDim3Dim4() + +bool intensityScaleVaries(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[]) { + //detect whether some DICOM images report different intensity scaling + //some Siemens PET scanners generate 16-bit images where slice has its own scaling factor. + // since NIfTI provides a single scaling factor for each file, these images require special consideration + if (nConvert < 2) + return false; + int dt = dcmList[dcmSort[0].indx].bitsAllocated; + float iScale = dcmList[dcmSort[0].indx].intenScale; + float iInter = dcmList[dcmSort[0].indx].intenIntercept; + for (int i = 1; i < nConvert; i++) { //stack additional images + uint64_t indx = dcmSort[i].indx; + if (dcmList[indx].bitsAllocated != dt) + return true; + if (fabs(dcmList[indx].intenScale - iScale) > FLT_EPSILON) + return true; + if (fabs(dcmList[indx].intenIntercept - iInter) > FLT_EPSILON) + return true; + } + return false; +} //intensityScaleVaries() + +/*unsigned char * nii_bgr2rgb(unsigned char* bImg, struct nifti_1_header *hdr) { + //DICOM planarappears to be BBB..B,GGG..G,RRR..R, NIfTI RGB saved in planes RRR..RGGG..GBBBB..B + // see http://www.barre.nom.fr/medical/samples/index.html US-RGB-8-epicard + if (hdr->datatype != DT_RGB24) return bImg; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; + int sliceBytes24 = hdr->dim[1]*hdr->dim[2] * hdr->bitpix/8; + int sliceBytes8 = hdr->dim[1]*hdr->dim[2]; + //Byte bImg[ bSz ]; + //[img getBytes:&bImg length:bSz]; + unsigned char slice24[sliceBytes24]; + int sliceOffsetR = 0; + for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice + memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24); + memcpy( &bImg[sliceOffsetR], &slice24[sliceBytes8*2], sliceBytes8); + sliceOffsetR += sliceBytes8; + memcpy( &bImg[sliceOffsetR], &slice24[sliceBytes8], sliceBytes8); + sliceOffsetR += sliceBytes8; + memcpy( &bImg[sliceOffsetR], &slice24[0], sliceBytes8); + sliceOffsetR += sliceBytes8; + } //for each slice + return bImg; + } */ + +void niiDeleteFnm(const char *outname, const char *ext) { + char niiname[2048] = {""}; + strcat(niiname, outname); + strcat(niiname, ext); + if (is_fileexists(niiname)) + remove(niiname); +} + +void niiDelete(const char *niiname) { + //for niiname "~/d/img" delete img.nii, img.bvec, img.bval, img.json + niiDeleteFnm(niiname, ".nii"); + niiDeleteFnm(niiname, ".nii.gz"); + niiDeleteFnm(niiname, ".nrrd"); + niiDeleteFnm(niiname, ".nhdr"); + niiDeleteFnm(niiname, ".raw.gz"); + niiDeleteFnm(niiname, ".json"); + niiDeleteFnm(niiname, ".bval"); + niiDeleteFnm(niiname, ".bvec"); +} + +bool niiExists(const char *pathoutname) { + char niiname[2048] = {""}; + strcat(niiname, pathoutname); + strcat(niiname, ".nii"); + if (is_fileexists(niiname)) + return true; + char gzname[2048] = {""}; + strcat(gzname, pathoutname); + strcat(gzname, ".nii.gz"); + if (is_fileexists(gzname)) + return true; + strcpy(niiname, pathoutname); + strcat(niiname, ".nrrd"); + if (is_fileexists(niiname)) + return true; + strcpy(niiname, pathoutname); + strcat(niiname, ".nhdr"); + if (is_fileexists(niiname)) + return true; + return false; +} //niiExists() + +#ifndef W_OK +#define W_OK 2 /* write mode check */ +#endif + +int strcicmp(char const *a, char const *b) //case insensitive compare +{ + for (;; a++, b++) { + int d = tolower(*a) - tolower(*b); + if (d != 0 || !*a) + return d; + } +} // strcicmp() + +bool isExt(char *file_name, const char *ext) { + char *p_extension; + if ((p_extension = strrchr(file_name, '.')) != NULL) + if (strcicmp(p_extension, ext) == 0) + return true; + //if(strcmp(p_extension,ext) == 0) return true; + return false; +} // isExt() + +void cleanISO8859(char *cString) { + int len = strlen(cString); + if (len < 1) + return; + for (int i = 0; i < len; i++) + //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 + if (cString[i] < 1) { + unsigned char c = (unsigned char)cString[i]; + if ((c >= 192) && (c <= 198)) + cString[i] = 'A'; + if (c == 199) + cString[i] = 'C'; + if ((c >= 200) && (c <= 203)) + cString[i] = 'E'; + if ((c >= 204) && (c <= 207)) + cString[i] = 'I'; + if (c == 208) + cString[i] = 'D'; + if (c == 209) + cString[i] = 'N'; + if ((c >= 210) && (c <= 214)) + cString[i] = 'O'; + if (c == 215) + cString[i] = 'x'; + if (c == 216) + cString[i] = 'O'; + if ((c >= 217) && (c <= 220)) + cString[i] = 'O'; + if (c == 221) + cString[i] = 'Y'; + if ((c >= 224) && (c <= 230)) + cString[i] = 'a'; + if (c == 231) + cString[i] = 'c'; + if ((c >= 232) && (c <= 235)) + cString[i] = 'e'; + if ((c >= 236) && (c <= 239)) + cString[i] = 'i'; + if (c == 240) + cString[i] = 'o'; + if (c == 241) + cString[i] = 'n'; + if ((c >= 242) && (c <= 246)) + cString[i] = 'o'; + if (c == 248) + cString[i] = 'o'; + if ((c >= 249) && (c <= 252)) + cString[i] = 'u'; + if (c == 253) + cString[i] = 'y'; + if (c == 255) + cString[i] = 'y'; + } +} +int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts opts) { + char pth[PATH_MAX] = {""}; + if (strlen(opts.outdir) > 0) { + strcpy(pth, opts.outdir); + int w = access(pth, W_OK); + if (w != 0) { + //should never happen except with "-b i": see kEXIT_OUTPUT_FOLDER_READ_ONLY for early termination + // with "-b i" the code below generates a warning but no files are created + if (getcwd(pth, sizeof(pth)) != NULL) { +#ifdef USE_CWD_IF_OUTDIR_NO_WRITE //optional: fall back to current working directory + w = access(pth, W_OK); + if (w != 0) { + printError("You do not have write permissions for the directory %s\n", opts.outdir); + return EXIT_FAILURE; + } + printWarning("%s write permission denied. Saving to working directory %s \n", opts.outdir, pth); +#else + printError("You do not have write permissions for the directory %s\n", opts.outdir); + return EXIT_FAILURE; +#endif + } + } + } + char inname[PATH_MAX] = {""}; //{"test%t_%av"}; //% a = acquisition, %n patient name, %t time + strcpy(inname, opts.filename); + bool isDcmExt = isExt(inname, ".dcm"); // "%r.dcm" with multi-echo should generate "1.dcm", "1e2.dcm" + if (isDcmExt) { + inname[strlen(inname) - 4] = '\0'; + } + char outname[PATH_MAX] = {""}; + char newstr[256]; + if (strlen(inname) < 1) { + strcpy(inname, "T%t_N%n_S%s"); + } + const char kTempPathSeparator = '\a'; + for (size_t pos = 0; pos < strlen(inname); pos++) + if ((inname[pos] == '\\') || (inname[pos] == '/')) + inname[pos] = kTempPathSeparator; + + size_t start = 0; + size_t pos = 0; + bool isAddNamePostFixes = opts.isAddNamePostFixes; + bool isCoilReported = false; + bool isEchoReported = false; + bool isSeriesReported = false; + //bool isAcquisitionReported = false; + bool isImageNumReported = false; + while (pos < strlen(inname)) { + if (inname[pos] == '%') { + if (pos > start) { + strncpy(&newstr[0], &inname[0] + start, pos - start); + newstr[pos - start] = '\0'; + strcat(outname, newstr); + } + pos++; //extra increment: skip both % and following character + char f = 'P'; + if (pos < strlen(inname)) + f = toupper(inname[pos]); + if (f == 'A') { + isCoilReported = true; + strcat(outname, dcm.coilName); + } + if (f == 'B') + strcat(outname, dcm.imageBaseName); + if (f == 'C') + strcat(outname, dcm.imageComments); + if (f == 'D') + strcat(outname, dcm.seriesDescription); + if (f == 'E') { + isEchoReported = true; + sprintf(newstr, "%d", dcm.echoNum); + strcat(outname, newstr); + } + if (f == 'F') + strcat(outname, opts.indirParent); + if (f == 'G') + strcat(outname, dcm.accessionNumber); + if (f == 'I') + strcat(outname, dcm.patientID); + if (f == 'J') + strcat(outname, dcm.seriesInstanceUID); + if (f == 'K') + strcat(outname, dcm.studyInstanceUID); + if (f == 'L') //"L"ocal Institution-generated description or classification of the Procedure Step that was performed. + strcat(outname, dcm.procedureStepDescription); + if (f == 'M') { + if (dcm.manufacturer == kMANUFACTURER_BRUKER) + strcat(outname, "Br"); + else if (dcm.manufacturer == kMANUFACTURER_GE) + strcat(outname, "GE"); + else if (dcm.manufacturer == kMANUFACTURER_TOSHIBA) + strcat(outname, "To"); + else if (dcm.manufacturer == kMANUFACTURER_CANON) + strcat(outname, "Ca"); + else if (dcm.manufacturer == kMANUFACTURER_UIH) + strcat(outname, "UI"); + else if (dcm.manufacturer == kMANUFACTURER_PHILIPS) + strcat(outname, "Ph"); + else if (dcm.manufacturer == kMANUFACTURER_SIEMENS) + strcat(outname, "Si"); + else + strcat(outname, "NA"); //manufacturer name not available + } + if (f == 'N') + strcat(outname, dcm.patientName); + if (f == 'O') { + strcat(outname, dcm.instanceUID); + if (strlen(dcm.instanceUID) > 0) + isAddNamePostFixes = false; //should be unique, so no need to post-fix + } + if (f == 'P') { + strcat(outname, dcm.protocolName); + if (strlen(dcm.protocolName) < 1) + printWarning("Unable to append protocol name (0018,1030) to filename (it is empty).\n"); + } + if (f == 'R') { + sprintf(newstr, "%d", dcm.imageNum); + strcat(outname, newstr); + isImageNumReported = true; + } + if (f == 'Q') + strcat(outname, dcm.scanningSequence); + if (f == 'S') { + sprintf(newstr, "%ld", dcm.seriesNum); + strcat(outname, newstr); + isSeriesReported = true; + } + if (f == 'T') { + sprintf(newstr, "%0.0f", dcm.dateTime); + strcat(outname, newstr); + } + if (f == 'U') { + if (opts.isRenameNotConvert) { + sprintf(newstr, "%d", dcm.acquNum); + strcat(outname, newstr); + //isAcquisitionReported = true; + } else { + sprintf(newstr, "%d", dcm.acquNum); + strcat(outname, newstr); +#ifdef mySegmentByAcq + //isAcquisitionReported = true; +#else + printWarning("'%%u' in output filename can be misleading (issue 526)\n"); +#endif + } + } + if (f == 'V') { + if (dcm.manufacturer == kMANUFACTURER_BRUKER) + strcat(outname, "Bruker"); + else if (dcm.manufacturer == kMANUFACTURER_GE) + strcat(outname, "GE"); + else if (dcm.manufacturer == kMANUFACTURER_PHILIPS) + strcat(outname, "Philips"); + else if (dcm.manufacturer == kMANUFACTURER_SIEMENS) + strcat(outname, "Siemens"); + else if (dcm.manufacturer == kMANUFACTURER_TOSHIBA) + strcat(outname, "Toshiba"); + else if (dcm.manufacturer == kMANUFACTURER_CANON) + strcat(outname, "Canon"); + else if (dcm.manufacturer == kMANUFACTURER_UIH) + strcat(outname, "UIH"); + else + strcat(outname, "NA"); + } + if (f == 'X') + strcat(outname, dcm.studyID); + if ((f == 'Y') && (dcm.rawDataRunNumber >= 0)) { + sprintf(newstr, "%d", dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100) + strcat(outname, newstr); + } + if (f == 'Z') + strcat(outname, dcm.sequenceName); + if ((f >= '0') && (f <= '9')) { + if ((pos < strlen(inname)) && (toupper(inname[pos + 1]) == 'S')) { + char zeroPad[12] = {""}; + sprintf(zeroPad, "%%0%dd", f - '0'); + sprintf(newstr, zeroPad, dcm.seriesNum); + strcat(outname, newstr); + pos++; // e.g. %3f requires extra increment: skip both number and following character + } + if ((pos < strlen(inname)) && (toupper(inname[pos + 1]) == 'R')) { + char zeroPad[12] = {""}; + sprintf(zeroPad, "%%0%dd", f - '0'); + sprintf(newstr, zeroPad, dcm.imageNum); + isImageNumReported = true; + strcat(outname, newstr); + pos++; // e.g. %3f requires extra increment: skip both number and following character + } + if ((pos < strlen(inname)) && (toupper(inname[pos + 1]) == 'Y') && (dcm.rawDataRunNumber >= 0)) { + char zeroPad[12] = {""}; + sprintf(zeroPad, "%%0%dd", f - '0'); + sprintf(newstr, zeroPad, dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100) + strcat(outname, newstr); + pos++; // e.g. %3f requires extra increment: skip both number and following character + } + } + start = pos + 1; + } //found a % character + pos++; + } //for each character in input + if (pos > start) { //append any trailing characters + strncpy(&newstr[0], &inname[0] + start, pos - start); + newstr[pos - start] = '\0'; + strcat(outname, newstr); + } + if ((isAddNamePostFixes) && (!isCoilReported) && (dcm.isCoilVaries)) { + //sprintf(newstr, "_c%d", dcm.coilNum); + //strcat (outname,newstr); + strcat(outname, "_c"); + strcat(outname, dcm.coilName); + } +// myMultiEchoFilenameSkipEcho1 https://github.com/rordenlab/dcm2niix/issues/237 +#ifdef myMultiEchoFilenameSkipEcho1 + if ((isAddNamePostFixes) && (!isEchoReported) && (dcm.isMultiEcho) && (dcm.echoNum >= 1)) { //multiple echoes saved as same series +#else + if ((isAddNamePostFixes) && (!isEchoReported) && ((dcm.isMultiEcho) || (dcm.echoNum > 1))) { //multiple echoes saved as same series +#endif + sprintf(newstr, "_e%d", dcm.echoNum); + strcat(outname, newstr); + isEchoReported = true; + } + if ((isAddNamePostFixes) && (!isSeriesReported) && (!isEchoReported) && (dcm.echoNum > 1)) { //last resort: user provided no method to disambiguate echo number in filename + sprintf(newstr, "_e%d", dcm.echoNum); + strcat(outname, newstr); + isEchoReported = true; + } + if ((dcm.isNonParallelSlices) && (!isImageNumReported)) { + sprintf(newstr, "_i%05d", dcm.imageNum); + strcat(outname, newstr); + } + /*if (dcm.maxGradDynVol > 0) { //Philips segmented + sprintf(newstr, "_v%04d", dcm.gradDynVol+1); //+1 as indexed from zero + strcat (outname,newstr); + }*/ + if ((isAddNamePostFixes) && (dcm.isHasImaginary)) { + strcat(outname, "_imaginary"); //has phase map + } + if ((isAddNamePostFixes) && (dcm.isHasReal) && (dcm.isRealIsPhaseMapHz)) { + strcat(outname, "_fieldmaphz"); //has field map + } + if ((isAddNamePostFixes) && (dcm.isHasReal) && (!dcm.isRealIsPhaseMapHz)) { + strcat(outname, "_real"); //has phase map + } + if ((isAddNamePostFixes) && (dcm.isHasPhase)) { + strcat(outname, "_ph"); //has phase map + if (dcm.isHasMagnitude) + strcat(outname, "Mag"); //Philips enhanced with BOTH phase and Magnitude in single file + } + if ((isAddNamePostFixes) && (dcm.aslFlags == kASL_FLAG_NONE) && (dcm.triggerDelayTime >= 1) && (dcm.manufacturer != kMANUFACTURER_GE)) { //issue 336 GE uses this for slice timing + sprintf(newstr, "_t%d", (int)roundf(dcm.triggerDelayTime)); + strcat(outname, newstr); + } + //could add (isAddNamePostFixes) to these next two, but consequences could be catastrophic + if (dcm.isRawDataStorage) //avoid name clash for Philips XX_ files + strcat(outname, "_Raw"); + if (dcm.isGrayscaleSoftcopyPresentationState) //avoid name clash for Philips PS_ files + strcat(outname, "_PS"); + if (isDcmExt) + strcat(outname, ".dcm"); + if (strlen(outname) < 1) + strcpy(outname, "dcm2nii_invalidName"); + if (outname[0] == '.') + outname[0] = '_'; //make sure not a hidden file +//eliminate illegal characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx +// https://github.com/rordenlab/dcm2niix/issues/237 +#ifdef myOsSpecificFilenameMask +#define kMASK_WINDOWS_SPECIAL_CHARACTERS 0 +#else +#define kMASK_WINDOWS_SPECIAL_CHARACTERS 1 +#endif +#if defined(_WIN64) || defined(_WIN32) || defined(kMASK_WINDOWS_SPECIAL_CHARACTERS) //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names + for (size_t pos = 0; pos < strlen(outname); pos++) + if ((outname[pos] == '\\') || (outname[pos] == '/') || (outname[pos] == ' ') || (outname[pos] == '<') || (outname[pos] == '>') || (outname[pos] == ':') || (outname[pos] == ';') || (outname[pos] == '"') // || (outname[pos] == '/') || (outname[pos] == '\\') + //|| (outname[pos] == '^') issue398 + || (outname[pos] == '*') || (outname[pos] == '|') || (outname[pos] == '?')) + outname[pos] = '_'; +#else + for (size_t pos = 0; pos < strlen(outname); pos++) + if (outname[pos] == ':') //not allowed by MacOS + outname[pos] = '_'; +#endif + cleanISO8859(outname); + //re-insert explicit path separators: -f %t/%s_%p will have folder for time, but will not segment a protocol named "fMRI\bold" + for (int pos = 0; pos < strlen(outname); pos++) { + if (outname[pos] == kTempPathSeparator) + outname[pos] = kPathSeparator; //e.g. for Windows, convert "/" to "\" + if (outname[pos] < 32) //https://en.wikipedia.org/wiki/ASCII#Control_characters + outname[pos] = '_'; + } + char baseoutname[2048] = {""}; + strcat(baseoutname, pth); + char appendChar[2] = {"a"}; + appendChar[0] = kPathSeparator; + if ((strlen(pth) > 0) && (pth[strlen(pth) - 1] != kPathSeparator) && (outname[0] != kPathSeparator)) + strcat(baseoutname, appendChar); + //remove redundant underscores + int len = strlen(outname); + int outpos = 0; + for (int inpos = 0; inpos < len; inpos++) { + if ((outpos > 0) && (outname[inpos] == '_') && (outname[outpos - 1] == '_')) + continue; + outname[outpos] = outname[inpos]; + outpos++; + } + outname[outpos] = 0; + //Allow user to specify new folders, e.g. "-f dir/%p" or "-f %s/%p/%m" + // These folders are created if they do not exist + char *sep = strchr(outname, kPathSeparator); +#if defined(USING_R) && (defined(_WIN64) || defined(_WIN32)) + // R also uses forward slash on Windows, so allow it here + if (!sep) + sep = strchr(outname, '/'); +#endif + if (sep) { + char newdir[2048] = {""}; + strcat(newdir, baseoutname); + //struct stat st = {0}; + for (size_t pos = 0; pos < strlen(outname); pos++) { + if (outname[pos] == kPathSeparator) { + //if (stat(newdir, &st) == -1) + if (!is_dir(newdir, true)) +#if defined(_WIN64) || defined(_WIN32) + mkdir(newdir); +#else + mkdir(newdir, 0700); +#endif + } + char ch[12] = {""}; + sprintf(ch, "%c", outname[pos]); + strcat(newdir, ch); + } + } + //printMessage("path='%s' name='%s'\n", pathoutname, outname); + //make sure outname is unique + strcat(baseoutname, outname); + char pathoutname[2048] = {""}; + strcat(pathoutname, baseoutname); + if ((niiExists(pathoutname)) && (opts.nameConflictBehavior == kNAME_CONFLICT_SKIP)) { + printWarning("Skipping existing file named %s\n", pathoutname); + return EXIT_FAILURE; + } + if ((niiExists(pathoutname)) && (opts.nameConflictBehavior == kNAME_CONFLICT_OVERWRITE)) { + printWarning("Overwriting existing file with the name %s\n", pathoutname); + niiDelete(pathoutname); + strcpy(niiFilename, pathoutname); + return EXIT_SUCCESS; + } + int i = 0; + while (niiExists(pathoutname) && (i < 26)) { + strcpy(pathoutname, baseoutname); + appendChar[0] = 'a' + i; + strcat(pathoutname, appendChar); + i++; + } + if (i >= 26) { + printError("Too many NIFTI images with the name %s\n", baseoutname); + return EXIT_FAILURE; + } + //printMessage("-->%s\n",pathoutname); return EXIT_SUCCESS; + //printMessage("outname=%s\n", pathoutname); + strcpy(niiFilename, pathoutname); + return EXIT_SUCCESS; +} //nii_createFilename() + +void nii_createDummyFilename(char *niiFilename, struct TDCMopts opts) { + //generate string that illustrating sample of filename + struct TDICOMdata d = clear_dicom_data(); + strcpy(d.patientName, "John_Doe"); + strcpy(d.patientID, "ID123"); + strcpy(d.accessionNumber, "ID123"); + strcpy(d.imageType, "ORIGINAL"); + strcpy(d.imageComments, "imgComments"); + strcpy(d.studyDate, "1/1/1977"); + strcpy(d.studyTime, "11:11:11"); + strcpy(d.protocolName, "MPRAGE"); + strcpy(d.seriesDescription, "T1_mprage"); + strcpy(d.sequenceName, "T1"); + strcpy(d.scanningSequence, "tfl3d1_ns"); + strcpy(d.sequenceVariant, "tfl3d1_ns"); + strcpy(d.manufacturersModelName, "N/A"); + strcpy(d.procedureStepDescription, ""); + strcpy(d.seriesInstanceUID, ""); + strcpy(d.studyInstanceUID, ""); + strcpy(d.bodyPartExamined, ""); + strcpy(opts.indirParent, "myFolder"); + char niiFilenameBase[PATH_MAX] = {"/usr/myFolder/dicom.dcm"}; + nii_createFilename(d, niiFilenameBase, opts); + strcpy(niiFilename, "Example output filename: '"); + strcat(niiFilename, niiFilenameBase); + if (opts.saveFormat != kSaveFormatNIfTI) { + if (opts.isGz) + strcat(niiFilename, ".nhdr'"); + else + strcat(niiFilename, ".nrrd'"); + } else { + if (opts.isGz) + strcat(niiFilename, ".nii.gz'"); + else + strcat(niiFilename, ".nii'"); + } +} // nii_createDummyFilename() + +#ifndef myDisableZLib + +#ifndef MiniZ +unsigned long mz_compressBound(unsigned long source_len) { + return compressBound(source_len); +} + +unsigned long mz_crc32(unsigned long crc, const unsigned char *ptr, size_t buf_len) { + return crc32(crc, ptr, (uInt)buf_len); +} +#endif + +#ifndef MZ_UBER_COMPRESSION //defined in miniz, not defined in zlib +#define MZ_UBER_COMPRESSION 9 +#endif + +#ifndef MZ_DEFAULT_LEVEL +#define MZ_DEFAULT_LEVEL 6 +#endif + +void writeNiiGz(char *baseName, struct nifti_1_header hdr, unsigned char *src_buffer, unsigned long src_len, int gzLevel, bool isSkipHeader) { + //create gz file in RAM, save to disk http://www.zlib.net/zlib_how.html + // in general this single-threaded approach is slower than PIGZ but is useful for slow (network attached) disk drives + char fname[2048] = {""}; + strcpy(fname, baseName); + if (!isSkipHeader) + strcat(fname, ".nii.gz"); + unsigned long hdrPadBytes = sizeof(hdr) + 4; //348 byte header + 4 byte pad + if (isSkipHeader) + hdrPadBytes = 0; + unsigned long cmp_len = mz_compressBound(src_len + hdrPadBytes); + unsigned char *pCmp = (unsigned char *)malloc(cmp_len); + z_stream strm; + strm.total_in = 0; + strm.total_out = 0; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_out = pCmp; // output char array + strm.avail_out = (unsigned int)cmp_len; // size of output + int zLevel = MZ_DEFAULT_LEVEL; //Z_DEFAULT_COMPRESSION; + if ((gzLevel > 0) && (gzLevel < 11)) + zLevel = gzLevel; + if (zLevel > MZ_UBER_COMPRESSION) + zLevel = MZ_UBER_COMPRESSION; + if (deflateInit(&strm, zLevel) != Z_OK) { + free(pCmp); + return; + } + unsigned char *pHdr; + if (!isSkipHeader) { + //add header + pHdr = (unsigned char *)malloc(hdrPadBytes); + pHdr[hdrPadBytes - 1] = 0; + pHdr[hdrPadBytes - 2] = 0; + pHdr[hdrPadBytes - 3] = 0; + pHdr[hdrPadBytes - 4] = 0; + memcpy(pHdr, &hdr, sizeof(hdr)); + strm.avail_in = (unsigned int)hdrPadBytes; // size of input + strm.next_in = (uint8_t *)pHdr; // input header -- TPX strm.next_in = (Bytef *)pHdr; uint32_t + deflate(&strm, Z_NO_FLUSH); + } + //add image + strm.avail_in = (unsigned int)src_len; // size of input + strm.next_in = (uint8_t *)src_buffer; // input image -- TPX strm.next_in = (Bytef *)src_buffer; + deflate(&strm, Z_FINISH); //Z_NO_FLUSH; + //finish up + deflateEnd(&strm); + unsigned long file_crc32 = mz_crc32(0L, Z_NULL, 0); + if (!isSkipHeader) + file_crc32 = mz_crc32(file_crc32, pHdr, (unsigned int)hdrPadBytes); + file_crc32 = mz_crc32(file_crc32, src_buffer, (unsigned int)src_len); + cmp_len = strm.total_out; + if (cmp_len <= 0) { + free(pCmp); + free(src_buffer); + return; + } + FILE *fileGz = fopen(fname, "wb"); + if (!fileGz) { + free(pCmp); + free(src_buffer); + return; + } + //write header http://www.gzip.org/zlib/rfc-gzip.html + fputc((char)0x1f, fileGz); //ID1 + fputc((char)0x8b, fileGz); //ID2 + fputc((char)0x08, fileGz); //CM - use deflate compression method + fputc((char)0x00, fileGz); //FLG - no addition fields + fputc((char)0x00, fileGz); //MTIME0 + fputc((char)0x00, fileGz); //MTIME1 + fputc((char)0x00, fileGz); //MTIME2 + fputc((char)0x00, fileGz); //MTIME2 + fputc((char)0x00, fileGz); //XFL + fputc((char)0xff, fileGz); //OS + //write Z-compressed data + fwrite(&pCmp[2], sizeof(char), cmp_len - 6, fileGz); //-6 as LZ78 format has 2 bytes header (typically 0x789C) and 4 bytes tail (ADLER 32) + //write tail: write redundancy check and uncompressed size as bytes to ensure LITTLE-ENDIAN order + fputc((unsigned char)(file_crc32), fileGz); + fputc((unsigned char)(file_crc32 >> 8), fileGz); + fputc((unsigned char)(file_crc32 >> 16), fileGz); + fputc((unsigned char)(file_crc32 >> 24), fileGz); + fputc((unsigned char)(strm.total_in), fileGz); + fputc((unsigned char)(strm.total_in >> 8), fileGz); + fputc((unsigned char)(strm.total_in >> 16), fileGz); + fputc((unsigned char)(strm.total_in >> 24), fileGz); + fclose(fileGz); + free(pCmp); + if (!isSkipHeader) + free(pHdr); +} //writeNiiGz() +#endif + +#ifdef USING_R + +// Version of nii_saveNII() for R/divest: create nifti_image pointer and push onto stack +int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { + hdr.vox_offset = 352; + // Extract the basename from the full file path + char *start = niiFilename + strlen(niiFilename); + while (start >= niiFilename && *start != '/' && *start != kPathSeparator) + start--; + std::string name(++start); + nifti_image *image = nifti_convert_nhdr2nim(hdr, niiFilename); + if (image == NULL) + return EXIT_FAILURE; + image->data = (void *)im; + ImageList *images = (ImageList *)opts.imageList; + images->append(image, name); + free(image); + return EXIT_SUCCESS; +} + +void nii_saveAttributes(struct TDICOMdata &data, struct nifti_1_header &header, struct TDCMopts &opts, const char *filename) { + ImageList *images = (ImageList *)opts.imageList; + switch (data.modality) { + case kMODALITY_CR: + images->addAttribute("modality", "CR"); + break; + case kMODALITY_CT: + images->addAttribute("modality", "CT"); + break; + case kMODALITY_MR: + images->addAttribute("modality", "MR"); + break; + case kMODALITY_PT: + images->addAttribute("modality", "PT"); + break; + case kMODALITY_US: + images->addAttribute("modality", "US"); + break; + } + switch (data.manufacturer) { + case kMANUFACTURER_SIEMENS: + images->addAttribute("manufacturer", "Siemens"); + break; + case kMANUFACTURER_GE: + images->addAttribute("manufacturer", "GE"); + break; + case kMANUFACTURER_MEDISO: + images->addAttribute("manufacturer", "Mediso"); + break; + case kMANUFACTURER_PHILIPS: + images->addAttribute("manufacturer", "Philips"); + break; + case kMANUFACTURER_TOSHIBA: + images->addAttribute("manufacturer", "Toshiba"); + break; + case kMANUFACTURER_UIH: + images->addAttribute("manufacturer", "UIH"); + break; + case kMANUFACTURER_BRUKER: + images->addAttribute("manufacturer", "Bruker"); + break; + case kMANUFACTURER_HITACHI: + images->addAttribute("manufacturer", "Hitachi"); + break; + case kMANUFACTURER_CANON: + images->addAttribute("manufacturer", "Canon"); + break; + } + images->addAttribute("scannerModelName", data.manufacturersModelName); + images->addAttribute("imageType", data.imageType); + if (data.seriesNum > 0) + images->addAttribute("seriesNumber", int(data.seriesNum)); + images->addAttribute("seriesDescription", data.seriesDescription); + images->addAttribute("sequenceName", data.sequenceName); + images->addAttribute("protocolName", data.protocolName); + images->addDateAttribute("studyDate", data.studyDate); + images->addTimeAttribute("studyTime", data.studyTime); + images->addAttribute("fieldStrength", data.fieldStrength); + images->addAttribute("flipAngle", data.flipAngle); + images->addAttribute("echoTime", data.TE); + images->addAttribute("repetitionTime", data.TR); + images->addAttribute("inversionTime", data.TI); + if (!data.isXRay) { + images->addAttribute("sliceThickness", data.zThick); + images->addAttribute("sliceSpacing", data.zSpacing); + } + if (data.CSA.multiBandFactor > 1) + images->addAttribute("multibandFactor", data.CSA.multiBandFactor); + if (data.phaseEncodingSteps > 0) + images->addAttribute("phaseEncodingSteps", data.phaseEncodingSteps); + if (data.phaseEncodingLines > 0) + images->addAttribute("phaseEncodingLines", data.phaseEncodingLines); + // Calculations relating to the reconstruction in the phase encode direction, + // which are needed to derive effective echo spacing and readout time below. + // See the nii_SaveBIDS() function for details + int reconMatrixPE = data.phaseEncodingLines; + if ((header.dim[2] > 0) && (header.dim[1] > 0)) { + if (header.dim[1] == header.dim[2]) //phase encoding does not matter + reconMatrixPE = header.dim[2]; + else if (data.phaseEncodingRC == 'C') + reconMatrixPE = header.dim[2]; + else if (data.phaseEncodingRC == 'R') + reconMatrixPE = header.dim[1]; + } + double bandwidthPerPixelPhaseEncode = data.bandwidthPerPixelPhaseEncode; + if (bandwidthPerPixelPhaseEncode == 0.0) + bandwidthPerPixelPhaseEncode = data.CSA.bandwidthPerPixelPhaseEncode; + double effectiveEchoSpacing = 0.0; + if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) + effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); + if (data.effectiveEchoSpacingGE > 0.0) { + double roundFactor = data.isPartialFourier ? 4.0 : 2.0; + double totalReadoutTime = ((ceil(1.0 / roundFactor * data.phaseEncodingLines / data.accelFactPE) * roundFactor) - 1.0) * data.effectiveEchoSpacingGE * 0.000001; + effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); + } + images->addAttribute("effectiveEchoSpacing", effectiveEchoSpacing); + if (data.manufacturer == kMANUFACTURER_UIH) + images->addAttribute("effectiveReadoutTime", data.acquisitionDuration / 1000.0); + else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0)) + images->addAttribute("effectiveReadoutTime", effectiveEchoSpacing * (reconMatrixPE - 1.0)); + images->addAttribute("pixelBandwidth", data.pixelBandwidth); + if ((data.manufacturer == kMANUFACTURER_SIEMENS) && (data.dwellTime > 0)) + images->addAttribute("dwellTime", data.dwellTime * 1e-9); + // Phase encoding polarity + // We only save these attributes if both direction and polarity are known + bool isSkipPhaseEncodingAxis = data.is3DAcq; + if (data.echoTrainLength > 1) + isSkipPhaseEncodingAxis = false; //issue 371: ignore phaseEncoding for 3D MP-RAGE/SPACE, but report for 3D EPI + if (((data.phaseEncodingRC == 'R') || (data.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && ((data.CSA.phaseEncodingDirectionPositive == 1) || (data.CSA.phaseEncodingDirectionPositive == 0))) { + if (data.phaseEncodingRC == 'C') { + images->addAttribute("phaseEncodingDirection", "j"); + // Notice the XOR (^): the sense of phaseEncodingDirectionPositive + // is reversed if we are flipping the y-axis + images->addAttribute("phaseEncodingSign", ((data.CSA.phaseEncodingDirectionPositive == 0) ^ opts.isFlipY) ? -1 : 1); + } else if (data.phaseEncodingRC == 'R') { + images->addAttribute("phaseEncodingDirection", "i"); + images->addAttribute("phaseEncodingSign", data.CSA.phaseEncodingDirectionPositive == 0 ? -1 : 1); + } + } + // Slice timing (stored in seconds) + if (data.CSA.sliceTiming[0] >= 0.0 && (data.manufacturer == kMANUFACTURER_UIH || data.manufacturer == kMANUFACTURER_GE || (data.manufacturer == kMANUFACTURER_SIEMENS && !data.isXA10A))) { + std::vector sliceTimes; + for (int i = 0; i < header.dim[3]; i++) { + if (data.CSA.sliceTiming[i] < 0.0) + break; + sliceTimes.push_back(data.CSA.sliceTiming[i] / 1000.0); + } + images->addAttribute("sliceTiming", sliceTimes); + } + images->addAttribute("patientIdentifier", data.patientID); + images->addAttribute("patientName", data.patientName); + images->addDateAttribute("patientBirthDate", data.patientBirthDate); + if (strlen(data.patientAge) > 0 && strcmp(data.patientAge, "000Y") != 0) + images->addAttribute("patientAge", data.patientAge); + if (data.patientSex == 'F') + images->addAttribute("patientSex", "F"); + else if (data.patientSex == 'M') + images->addAttribute("patientSex", "M"); + images->addAttribute("patientWeight", data.patientWeight); + images->addAttribute("comments", data.imageComments); +} + +#else + +int pigz_File(char *fname, struct TDCMopts opts, size_t imgsz) { + //given "/dir/file.nii" creates "/dir/file.nii.gz" + char blockSize[768]; + strcpy(blockSize, ""); + //-b 960 increases block size from 128 to 960: each block has 32kb lead in... so less redundancy + if (imgsz > 1000000) + strcpy(blockSize, " -b 960"); + char command[768]; + strcpy(command, "\""); + strcat(command, opts.pigzname); + if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { + char newstr[256]; + sprintf(newstr, "\"%s -n -f -%d \"", blockSize, opts.gzLevel); + strcat(command, newstr); + } else { + char newstr[256]; + sprintf(newstr, "\"%s -n \"", blockSize); + strcat(command, newstr); + } + strcat(command, fname); + strcat(command, "\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' +#if defined(_WIN64) || defined(_WIN32) //using CreateProcess instead of system to run in background (avoids screen flicker) + DWORD exitCode; + PROCESS_INFORMATION ProcessInfo = {0}; + STARTUPINFO startupInfo = {0}; + startupInfo.cb = sizeof(startupInfo); + //StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field + if (CreateProcess(NULL, command, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &ProcessInfo)) { + //printMessage("compression --- %s\n",command); + WaitForSingleObject(ProcessInfo.hProcess, INFINITE); + CloseHandle(ProcessInfo.hThread); + CloseHandle(ProcessInfo.hProcess); + } else + printMessage("Compression failed %s\n", command); +#else //if win else linux + int ret = system(command); + if (ret == -1) + printWarning("Failed to execute: %s\n", command); +#endif //else linux + printMessage("Compress: %s\n", command); + return EXIT_SUCCESS; +} // pigz_File() + +#define kMGHpad 97 + +#ifdef __GNUC__ +#define PACKD(...) __VA_ARGS__ __attribute__((__packed__)) +#else +#define PACKD(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop)) +#endif + +PACKD(typedef struct { + int32_t version, width,height,depth,nframes,type,dof; + int16_t goodRASFlag; + float spacingX,spacingY,spacingZ,xr,xa,xs,yr,ya,ys,zr,za,zs,cr,ca,cs; + int16_t pad[kMGHpad]; +}) Tmgh; + +PACKD(typedef struct { + float TR, FlipAngle, TE, TI; +}) TmghFooter; + +void writeMghGz(char *baseName, Tmgh hdr, TmghFooter footer, unsigned char *src_buffer, unsigned long src_len, int gzLevel) { + //create gz file in RAM, save to disk http://www.zlib.net/zlib_how.html + // in general this single-threaded approach is slower than PIGZ but is useful for slow (network attached) disk drives + char fname[2048] = {""}; + strcpy(fname, baseName); + unsigned long hdrPadBytes = sizeof(hdr); //348 byte header + 4 byte pad + unsigned long cmp_len = mz_compressBound(src_len + hdrPadBytes); + unsigned char *pCmp = (unsigned char *)malloc(cmp_len); + z_stream strm; + strm.total_in = 0; + strm.total_out = 0; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_out = pCmp; // output char array + strm.avail_out = (unsigned int)cmp_len; // size of output + int zLevel = MZ_DEFAULT_LEVEL; //Z_DEFAULT_COMPRESSION; + if ((gzLevel > 0) && (gzLevel < 11)) + zLevel = gzLevel; + if (zLevel > MZ_UBER_COMPRESSION) + zLevel = MZ_UBER_COMPRESSION; + if (deflateInit(&strm, zLevel) != Z_OK) { + free(pCmp); + return; + } + unsigned char *pHdr; + //add header + strm.avail_in = (unsigned int)sizeof(hdr); // size of input + strm.next_in = (uint8_t *) &hdr.version; + deflate(&strm, Z_NO_FLUSH); + //add image + strm.avail_in = (unsigned int)src_len; // size of input + strm.next_in = (uint8_t *)src_buffer; // input image -- TPX strm.next_in = (Bytef *)src_buffer; + deflate(&strm, Z_FINISH); + //add footer + strm.avail_in = (unsigned int)sizeof(footer); // size of input + strm.next_in = (uint8_t *) &footer.TR; + deflate(&strm, Z_NO_FLUSH); + //finish up + deflateEnd(&strm); + unsigned long file_crc32 = mz_crc32(0L, Z_NULL, 0); + file_crc32 = mz_crc32(file_crc32, (uint8_t *) &hdr.version, (unsigned int)sizeof(hdr)); + file_crc32 = mz_crc32(file_crc32, src_buffer, (unsigned int)src_len); + file_crc32 = mz_crc32(file_crc32, (uint8_t *) &footer.TR, (unsigned int)sizeof(footer)); + cmp_len = strm.total_out; + if (cmp_len <= 0) { + free(pCmp); + free(src_buffer); + return; + } + FILE *fileGz = fopen(fname, "wb"); + if (!fileGz) { + free(pCmp); + free(src_buffer); + return; + } + //write header http://www.gzip.org/zlib/rfc-gzip.html + fputc((char)0x1f, fileGz); //ID1 + fputc((char)0x8b, fileGz); //ID2 + fputc((char)0x08, fileGz); //CM - use deflate compression method + fputc((char)0x00, fileGz); //FLG - no addition fields + fputc((char)0x00, fileGz); //MTIME0 + fputc((char)0x00, fileGz); //MTIME1 + fputc((char)0x00, fileGz); //MTIME2 + fputc((char)0x00, fileGz); //MTIME2 + fputc((char)0x00, fileGz); //XFL + fputc((char)0xff, fileGz); //OS + //write Z-compressed data + fwrite(&pCmp[2], sizeof(char), cmp_len - 6, fileGz); //-6 as LZ78 format has 2 bytes header (typically 0x789C) and 4 bytes tail (ADLER 32) + //write tail: write redundancy check and uncompressed size as bytes to ensure LITTLE-ENDIAN order + fputc((unsigned char)(file_crc32), fileGz); + fputc((unsigned char)(file_crc32 >> 8), fileGz); + fputc((unsigned char)(file_crc32 >> 16), fileGz); + fputc((unsigned char)(file_crc32 >> 24), fileGz); + fputc((unsigned char)(strm.total_in), fileGz); + fputc((unsigned char)(strm.total_in >> 8), fileGz); + fputc((unsigned char)(strm.total_in >> 16), fileGz); + fputc((unsigned char)(strm.total_in >> 24), fileGz); + fclose(fileGz); + free(pCmp); +} //writeMghGz() + +int nii_saveMGH(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { +// FreeeSurfer does not use a permissive license, so we must reverse engineer code +// https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat + int n, nDim = hdr.dim[0]; + //printMessage("NRRD writer is experimental\n"); + if (nDim < 1) + return EXIT_FAILURE; + bool isGz = opts.isGz; + size_t imgsz = nii_ImgBytes(hdr); + if ((isGz) && (imgsz >= 2147483647)) { + printWarning("Saving huge image uncompressed (many GZip tools have 2 Gb limit).\n"); + isGz = false; + } + //fill the footer + TmghFooter footer; + footer.TR = d.TR; + footer.FlipAngle = d.flipAngle; + footer.TE = d.TE; + footer.TI = d.TI; + #ifdef __LITTLE_ENDIAN__ //mgh data ALWAYS big endian! + nifti_swap_4bytes(4, &footer.TR); + #endif + //fill the header + Tmgh mgh; + mgh.version = 1; + mgh.width = hdr.dim[1]; + mgh.height = hdr.dim[2]; + mgh.depth = hdr.dim[3]; + mgh.nframes = max(hdr.dim[4],1); + if (hdr.datatype == DT_UINT8) + mgh.type = 0; + else if (hdr.datatype == DT_INT16) + mgh.type = 4; + else if (hdr.datatype == DT_INT32) + mgh.type = 1; + else if (hdr.datatype == DT_FLOAT32) + mgh.type = 3; + else { + printError("MGH format does not support NIfTI datatype %d\n", hdr.datatype); + return EXIT_FAILURE; + } + mgh.dof = 0; + mgh.goodRASFlag = 1; + float xmm = hdr.pixdim[1]; + float ymm = hdr.pixdim[2]; + float zmm = hdr.pixdim[3]; + //avoid divide by zero errors: + if (xmm <= 0.0) xmm = 1.0; + if (ymm <= 0.0) ymm = 1.0; + if (zmm <= 0.0) zmm = 1.0; + mgh.spacingX = xmm; + mgh.spacingY = ymm; + mgh.spacingZ = zmm; + mgh.xr = hdr.srow_x[0] / xmm; + mgh.xa = hdr.srow_y[0] / xmm; + mgh.xs = hdr.srow_z[0] / xmm; + mgh.yr = hdr.srow_x[1] / ymm; + mgh.ya = hdr.srow_y[1] / ymm; + mgh.ys = hdr.srow_z[1] / ymm; + mgh.zr = hdr.srow_x[2] / zmm; + mgh.za = hdr.srow_y[2] / zmm; + mgh.zs = hdr.srow_z[2] / zmm; + float vec[3]; + vec[0] = hdr.dim[1] * 0.5; + vec[1] = hdr.dim[2] * 0.5; + vec[2] = hdr.dim[3] * 0.5; + mgh.cr = hdr.srow_x[0]*vec[0] + hdr.srow_x[1]*vec[1] + hdr.srow_x[2]*vec[2] + hdr.srow_x[3]; + mgh.ca = hdr.srow_y[0]*vec[0] + hdr.srow_y[1]*vec[1] + hdr.srow_y[2]*vec[2] + hdr.srow_y[3]; + mgh.cs = hdr.srow_z[0]*vec[0] + hdr.srow_z[1]*vec[1] + hdr.srow_z[2]*vec[2] + hdr.srow_z[3]; + #ifdef __LITTLE_ENDIAN__ //mgh data ALWAYS big endian! + nifti_swap_4bytes(7, &mgh.version); + nifti_swap_2bytes(1, &mgh.goodRASFlag); + nifti_swap_4bytes(15, &mgh.spacingX); + #endif + for (int i = 0; i < kMGHpad; i++) + mgh.pad[i] = 0; + //write the data + char fname[2048] = {""}; + strcpy(fname, niiFilename); + #ifdef __LITTLE_ENDIAN__ //mgh data ALWAYS big endian! + swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) + #endif + if (isGz) { + strcat(fname, ".mgz"); + writeMghGz(fname, mgh, footer, im, imgsz, opts.gzLevel); + } else { + strcat(fname, ".mgh"); + FILE *fp = fopen(fname, "wb"); + if (!fp) + return EXIT_FAILURE; + fwrite(&mgh, sizeof(Tmgh), 1, fp); + fwrite(&im[0], imgsz, 1, fp); + fwrite(&footer, sizeof(TmghFooter), 1, fp); + fclose(fp); + } + #ifdef __LITTLE_ENDIAN__ //mgh data ALWAYS big endian! + swapEndian(&hdr, im, false); //byte-swap endian (e.g. little->big) + #endif + return EXIT_SUCCESS; +} // nii_saveMGH() + +int nii_saveNRRD(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { + int n, nDim = hdr.dim[0]; + //printMessage("NRRD writer is experimental\n"); + if (nDim < 1) + return EXIT_FAILURE; + bool isGz = opts.isGz; + size_t imgsz = nii_ImgBytes(hdr); + if ((isGz) && (imgsz >= 2147483647)) { + printWarning("Saving huge image uncompressed (many GZip tools have 2 Gb limit).\n"); + isGz = false; + } + char fname[2048] = {""}; + strcpy(fname, niiFilename); + if (isGz) + strcat(fname, ".nhdr"); //nrrd or nhdr + else + strcat(fname, ".nrrd"); //nrrd or nhdr + FILE *fp = fopen(fname, "w"); + fprintf(fp, "NRRD0005\n"); + fprintf(fp, "# Complete NRRD file format specification at:\n"); + fprintf(fp, "# http://teem.sourceforge.net/nrrd/format.html\n"); + fprintf(fp, "# dcm2niix %s NRRD export transforms by Tashrif Billah\n", kDCMdate); + char rgbNoneStr[10] = {""}; + //type tag + switch (hdr.datatype) { + case DT_RGB24: + fprintf(fp, "type: uint8\n"); + strcpy(rgbNoneStr, " none"); + break; + case DT_UINT8: + fprintf(fp, "type: uint8\n"); + break; + case DT_INT16: + fprintf(fp, "type: int16\n"); + break; + case DT_UINT16: + fprintf(fp, "type: uint16\n"); + break; + case DT_FLOAT32: + fprintf(fp, "type: float\n"); + break; + case DT_INT32: + fprintf(fp, "type: int32\n"); + break; + case DT_FLOAT64: + fprintf(fp, "type: double\n"); + break; + default: + printError("Unknown NRRD datatype %d\n", hdr.datatype); + fclose(fp); + return EXIT_FAILURE; + } + //dimension tag + if (hdr.datatype == DT_RGB24) + fprintf(fp, "dimension: %d\n", nDim + 1); //RGB is first dimension + else + fprintf(fp, "dimension: %d\n", nDim); + //space tag + fprintf(fp, "space: right-anterior-superior\n"); + //sizes tag + fprintf(fp, "sizes:"); + if (hdr.datatype == DT_RGB24) + fprintf(fp, " 3"); + for (int i = 1; i <= hdr.dim[0]; i++) + fprintf(fp, " %d", hdr.dim[i]); + fprintf(fp, "\n"); + //thicknesses + if ((d.zThick > 0.0) && (nDim >= 3)) { + fprintf(fp, "thicknesses: NaN NaN %g", d.zThick); + int n = 3; + while (n < nDim) { + fprintf(fp, " NaN"); + n++; + } + fprintf(fp, "\n"); + } + //byteskip only for .nhdr, not .nrrd + if (littleEndianPlatform()) //raw data in native format + fprintf(fp, "endian: little\n"); + else + fprintf(fp, "endian: big\n"); + if (isGz) { + fprintf(fp, "encoding: gzip\n"); + strcpy(fname, niiFilename); + strcat(fname, ".raw.gz"); + char basefname[2048] = {""}; + getFileNameX(basefname, fname, 2048); + fprintf(fp, "data file: %s\n", basefname); + } else + fprintf(fp, "encoding: raw\n"); + fprintf(fp, "space units: \"mm\" \"mm\" \"mm\"\n"); + //origin + fprintf(fp, "space origin: (%g,%g,%g)\n", hdr.srow_x[3], hdr.srow_y[3], hdr.srow_z[3]); + //space directions: + fprintf(fp, "space directions:%s (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)", rgbNoneStr, hdr.srow_x[0], hdr.srow_y[0], hdr.srow_z[0], + hdr.srow_x[1], hdr.srow_y[1], hdr.srow_z[1], hdr.srow_x[2], hdr.srow_y[2], hdr.srow_z[2]); + n = 3; + while (n < nDim) { + fprintf(fp, " none"); + n++; + } + fprintf(fp, "\n"); + //centerings tag + if (hdr.dim[0] < 4) //*check RGB, more dims + fprintf(fp, "centerings:%s cell cell cell\n", rgbNoneStr); + else + fprintf(fp, "centerings:%s cell cell cell ???\n", rgbNoneStr); + //kinds tag + fprintf(fp, "kinds:"); + if (hdr.datatype == DT_RGB24) + fprintf(fp, " RGB-color"); + n = 0; + while ((n < nDim) && (n < 3)) { + fprintf(fp, " space"); //dims 1..3 + n++; + } + while (n < nDim) { + fprintf(fp, " list"); //dims 4..7 + n++; + } + fprintf(fp, "\n"); + //http://teem.sourceforge.net/nrrd/format.html + bool isFloat = (hdr.datatype == DT_FLOAT64) || (hdr.datatype == DT_FLOAT32); + if (((!isSameFloat(hdr.scl_inter, 0.0)) || (!isSameFloat(hdr.scl_slope, 1.0))) && (!isFloat)) { + //http://teem.sourceforge.net/nrrd/format.html + double dtMin = 0.0; //DT_UINT8, DT_RGB24, DT_UINT16 + if (hdr.datatype == DT_INT16) + dtMin = -32768.0; + if (hdr.datatype == DT_INT32) + dtMin = -2147483648.0; + fprintf(fp, "oldmin: %8.8f\n", (dtMin * hdr.scl_slope) + hdr.scl_inter); + double dtMax = 255.00; //DT_UINT8, DT_RGB24 + if (hdr.datatype == DT_INT16) + dtMax = 32767.0; + if (hdr.datatype == DT_UINT16) + dtMax = 65535.0; + if (hdr.datatype == DT_INT32) + dtMax = 2147483647.0; + fprintf(fp, "oldmax: %8.8f\n", (dtMax * hdr.scl_slope) + hdr.scl_inter); + } + //Slicer DWIconvert values + if (d.modality == kMODALITY_MR) + fprintf(fp, "DICOM_0008_0060_Modality:=MR\n"); + if (d.modality == kMODALITY_CT) + fprintf(fp, "DICOM_0008_0060_Modality:=CT\n"); + if (d.manufacturer == kMANUFACTURER_SIEMENS) + fprintf(fp, "DICOM_0008_0070_Manufacturer:=SIEMENS\n"); + if (d.manufacturer == kMANUFACTURER_PHILIPS) + fprintf(fp, "DICOM_0008_0070_Manufacturer:=Philips Medical Systems\n"); + if (d.manufacturer == kMANUFACTURER_GE) + fprintf(fp, "DICOM_0008_0070_Manufacturer:=GE MEDICAL SYSTEMS\n"); + if (strlen(d.manufacturersModelName) > 0) + fprintf(fp, "DICOM_0008_1090_ManufacturerModelName:=%s\n", d.manufacturersModelName); + if (strlen(d.scanOptions) > 0) + fprintf(fp, "DICOM_0018_0022_ScanOptions:=%s\n", d.scanOptions); + if (d.is2DAcq) + fprintf(fp, "DICOM_0018_0023_MRAcquisitionType:=2D\n"); + if (d.is3DAcq) + fprintf(fp, "DICOM_0018_0023_MRAcquisitionType:=3D\n"); + //if (strlen(d.mrAcquisitionType) > 0) fprintf(fp,"DICOM_0018_0023_MRAcquisitionType:=%s\n",d.mrAcquisitionType); + if (d.TR > 0.0) + fprintf(fp, "DICOM_0018_0080_RepetitionTime:=%g\n", d.TR); + if ((d.TE > 0.0) && (!d.isXRay)) + fprintf(fp, "DICOM_0018_0081_EchoTime:=%g\n", d.TE); + if ((d.TE > 0.0) && (d.isXRay)) + fprintf(fp, "DICOM_0018_1152_XRayExposure:=%g\n", d.TE); + if (d.numberOfAverages > 0.0) + fprintf(fp, "DICOM_0018_0083_NumberOfAverages:=%g\n", d.numberOfAverages); + if (d.fieldStrength > 0.0) + fprintf(fp, "DICOM_0018_0087_MagneticFieldStrength:=%g\n", d.fieldStrength); + if (strlen(d.softwareVersions) > 0) + fprintf(fp, "DICOM_0018_1020_SoftwareVersions:=%s\n", d.softwareVersions); + if (d.flipAngle > 0.0) + fprintf(fp, "DICOM_0018_1314_FlipAngle:=%g\n", d.flipAngle); + //multivolume but NOT DTI, e.g. fMRI/DCE see https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer + // https://github.com/QIICR/PkModeling/blob/master/PkSolver/IO/MultiVolumeMetaDictReader.cxx#L34-L58 + // for "MultiVolume.FrameLabels:=" + // https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer + // for "axis 0 index values:=" + // https://github.com/mhe/pynrrd/issues/71 + // "I don't know if it is a good idea for dcm2niix to mimic Slicer converter tags" Andrey Fedorov + /* + if ((nDim > 3) && (hdr.dim[4] > 1) && (numDTI < 1)) { + if ((d.TE > 0.0) && (!d.isXRay)) fprintf(fp,"MultiVolume.DICOM.EchoTime:=%g\n",d.TE); + if (d.flipAngle > 0.0) fprintf(fp,"MultiVolume.DICOM.FlipAngle:=%g\n",d.flipAngle); + if (d.TR > 0.0) fprintf(fp,"MultiVolume.DICOM.RepetitionTime:=%g\n",d.TR); + fprintf(fp,"MultiVolume.FrameIdentifyingDICOMTagName:=TriggerTime\n"); + fprintf(fp,"MultiVolume.FrameIdentifyingDICOMTagUnits:=ms\n"); + fprintf(fp,"MultiVolume.FrameLabels:="); + double frameTime = d.TR; + if (d.triggerDelayTime > 0.0) + frameTime = d.triggerDelayTime / (hdr.dim[4] - 1); //GE dce data + for (int i = 0; i < (hdr.dim[4]-1); i++) + fprintf(fp,"%g,", i * frameTime); + fprintf(fp,"%g\n", (hdr.dim[4]-1) * frameTime); + fprintf(fp,"MultiVolume.NumberOfFrames:=%d\n",hdr.dim[4]); + } */ + //DWI values + if ((nDim > 3) && (numDTI > 0) && (numDTI < kMaxDTI4D)) { + mat33 inv; + LOAD_MAT33(inv, hdr.pixdim[1], 0.0, 0.0, 0.0, hdr.pixdim[2], 0.0, 0.0, 0.0, hdr.pixdim[3]); + inv = nifti_mat33_inverse(inv); + mat33 s; + LOAD_MAT33(s, hdr.srow_x[0], hdr.srow_x[1], hdr.srow_x[2], + hdr.srow_y[0], hdr.srow_y[1], hdr.srow_y[2], + hdr.srow_z[0], hdr.srow_z[1], hdr.srow_z[2]); + mat33 mf = nifti_mat33_mul(inv, s); + fprintf(fp, "measurement frame: (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)\n", + mf.m[0][0], mf.m[1][0], mf.m[2][0], + mf.m[0][1], mf.m[1][1], mf.m[2][1], + mf.m[0][2], mf.m[1][2], mf.m[2][2]); + //modality tag + fprintf(fp, "modality:=DWMRI\n"); + float b_max = 0.0; + for (int i = 0; i < numDTI; i++) + if (dti4D->S[i].V[0] > b_max) + b_max = dti4D->S[i].V[0]; + fprintf(fp, "DWMRI_b-value:=%g\n", b_max); + //gradient tag, e.g. DWMRI_gradient_0000:=0.0 0.0 0.0 + for (int i = 0; i < numDTI; i++) { + float factor = 0.0; + if (b_max > 0) + factor = sqrt(dti4D->S[i].V[0] / b_max); + if ((dti4D->S[i].V[0] > 50.0) && (isSameFloatGE(0.0, dti4D->S[i].V[1])) && (isSameFloatGE(0.0, dti4D->S[i].V[2])) && (isSameFloatGE(0.0, dti4D->S[i].V[3]))) { + //On May 2, 2019, at 10:47 AM, Gordon L. Kindlmann <> wrote: + //(assuming b_max 2000, we write "isotropic" for the b=2000 isotropic image, and specify the b-value if it is an isotropic image but not b-bax + // DWMRI_gradient_0003:=isotropic b=1000 + // DWMRI_gradient_0004:=isotropic + if (isSameFloatGE(b_max, dti4D->S[i].V[0])) + fprintf(fp, "DWMRI_gradient_%04d:=isotropic\n", i); + else + fprintf(fp, "DWMRI_gradient_%04d:=isotropic b=%g\n", i, dti4D->S[i].V[0]); + } else + fprintf(fp, "DWMRI_gradient_%04d:=%.17g %.17g %.17g\n", i, factor * dti4D->S[i].V[1], factor * dti4D->S[i].V[2], factor * dti4D->S[i].V[3]); + //printf("%g = %g %g %g>>>>\n",dti4D->S[i].V[0], dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]); + } + } + fprintf(fp, "\n"); //blank line: end of NRRD header + if (!isGz) + fwrite(&im[0], imgsz, 1, fp); + fclose(fp); + if (!isGz) + return EXIT_SUCCESS; +//below: gzip file +#ifdef myDisableZLib + if (strlen(opts.pigzname) < 1) { //internal compression + printError("Compiled without gz support, unable to compress %s\n", fname); + return EXIT_FAILURE; + } +#else + if (strlen(opts.pigzname) < 1) { //internal compression + writeNiiGz(fname, hdr, im, imgsz, opts.gzLevel, true); + return EXIT_SUCCESS; + } +#endif + //below pigz + strcpy(fname, niiFilename); //without gz + strcat(fname, ".raw"); + fp = fopen(fname, "wb"); + fwrite(&im[0], imgsz, 1, fp); + fclose(fp); + return pigz_File(fname, opts, imgsz); +} // nii_saveNRRD() + +int nii_saveForeign(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { + if (opts.saveFormat == kSaveFormatMGH) + return nii_saveMGH(niiFilename, hdr, im, opts, d, dti4D, numDTI); + return nii_saveNRRD(niiFilename, hdr, im, opts, d, dti4D, numDTI); +}// nii_saveForeign() + +#endif + +void removeSclSlopeInter(struct nifti_1_header *hdr, unsigned char *img) { + //NRRD does not have scl_slope scl_inter. Adjust data if possible + // https://discourse.slicer.org/t/preserve-image-rescale-and-slope-when-saving-in-nrrd-file/13357 + if (isSameFloat(hdr->scl_inter, 0.0) && isSameFloat(hdr->scl_slope, 1.0)) + return; + if ((!isSameFloat(fmod(hdr->scl_inter, 1.0f), 0.0)) || (!isSameFloat(fmod(hdr->scl_slope, 1.0f), 0.0))) + return; + int nVox = 1; + for (int i = 1; i < 8; i++) + if (hdr->dim[i] > 1) + nVox = nVox * hdr->dim[i]; + if (hdr->datatype == DT_INT16) { + int16_t *img16 = (int16_t *)img; + int16_t mn, mx; + mn = img16[0]; + mx = mn; + for (int i = 0; i < nVox; i++) { + mn = min(mn, img16[i]); + mx = max(mx, img16[i]); + } + float v = (mn * hdr->scl_slope) + hdr->scl_inter; + if ((v < -32768) || (v > 32767)) + return; + v = (mx * hdr->scl_slope) + hdr->scl_inter; + if ((v < -32768) || (v > 32767)) + return; + for (int i = 0; i < nVox; i++) + img16[i] = round((img16[i] * hdr->scl_slope) + hdr->scl_inter); + hdr->scl_slope = 1.0; + hdr->scl_inter = 0.0; + return; + } + if (hdr->datatype == DT_UINT16) { + uint16_t *img16 = (uint16_t *)img; + uint16_t mn, mx; + mn = img16[0]; + mx = mn; + for (int i = 0; i < nVox; i++) { + mn = min(mn, img16[i]); + mx = max(mx, img16[i]); + } + float v = (mn * hdr->scl_slope) + hdr->scl_inter; + if ((v < 0) || (v > 65535)) + return; + v = (mx * hdr->scl_slope) + hdr->scl_inter; + if ((v < 0) || (v > 65535)) + return; + for (int i = 0; i < nVox; i++) + img16[i] = round((img16[i] * hdr->scl_slope) + hdr->scl_inter); + hdr->scl_slope = 1.0; + hdr->scl_inter = 0.0; + return; + } + //printWarning("NRRD unable to record scl_slope/scl_inter %g/%g\n", hdr->scl_slope, hdr->scl_inter); +} + +#ifndef USING_R + +int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { + if (opts.isOnlyBIDS) + return EXIT_SUCCESS; + if (opts.saveFormat != kSaveFormatNIfTI) { + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); + int ret = nii_saveForeign(niiFilename, hdr, im, opts, d, dti4D, 0); + free(dti4D); + return ret; + } + hdr.vox_offset = 352; + size_t imgsz = nii_ImgBytes(hdr); + if (imgsz < 1) { + printMessage("Error: Image size is zero bytes %s\n", niiFilename); + return EXIT_FAILURE; + } +#ifndef myDisableGzSizeLimits + //see https://github.com/rordenlab/dcm2niix/issues/124 + uint64_t kMaxPigz = 4294967264; +//https://stackoverflow.com/questions/5272825/detecting-64bit-compile-in-c +#ifndef UINTPTR_MAX + uint64_t kMaxGz = 2147483647; +#elif UINTPTR_MAX == 0xffffffff + uint64_t kMaxGz = 2147483647; +#elif UINTPTR_MAX == 0xffffffffffffffff + uint64_t kMaxGz = kMaxPigz; +#else + compiler error : unable to determine is 32 or 64 bit +#endif +#ifndef myDisableZLib + if ((opts.isGz) && (strlen(opts.pigzname) < 1) && ((imgsz + hdr.vox_offset) >= kMaxGz)) { //use internal compressor + printWarning("Saving uncompressed data: internal compressor unable to process such large files.\n"); + if ((imgsz + hdr.vox_offset) < kMaxPigz) + printWarning(" Hint: using external compressor (pigz) should help.\n"); + } else if ((opts.isGz) && (strlen(opts.pigzname) < 1) && ((imgsz + hdr.vox_offset) < kMaxGz)) { //use internal compressor + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) + writeNiiGz(niiFilename, hdr, im, imgsz, opts.gzLevel, false); + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) + return EXIT_SUCCESS; + } +#endif +#endif + char fname[2048] = {""}; + strcpy(fname, niiFilename); + strcat(fname, ".nii"); +#if defined(_WIN64) || defined(_WIN32) + if ((opts.isGz) && (opts.isPipedGz)) + printWarning("The 'optimal' piped gz is only available for Unix\n"); +#else //if windows else Unix + if ((opts.isGz) && (opts.isPipedGz) && (strlen(opts.pigzname) > 0)) { + //piped gz + if (opts.isVerbose) + printMessage(" Optimal piped gz will fail if pigz version < 2.3.4.\n"); + char command[768]; + strcpy(command, "\""); + strcat(command, opts.pigzname); + if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { + char newstr[256]; + sprintf(newstr, "\" -n -f -%d > \"", opts.gzLevel); + strcat(command, newstr); + } else + strcat(command, "\" -n -f > \""); //current versions of pigz (2.3) built on Windows can hang if the filename is included, presumably because it is not finding the path characters ':\' + strcat(command, fname); + strcat(command, ".gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' + //strcat(command, "x.gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' + if (opts.isVerbose) + printMessage("Compress: %s\n", command); + FILE *pigzPipe; + if ((pigzPipe = popen(command, "w")) == NULL) { + printError("Unable to open pigz pipe\n"); + return EXIT_FAILURE; + } + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) + fwrite(&hdr, sizeof(hdr), 1, pigzPipe); + uint32_t pad = 0; + fwrite(&pad, sizeof(pad), 1, pigzPipe); + fwrite(&im[0], imgsz, 1, pigzPipe); + pclose(pigzPipe); + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) + return EXIT_SUCCESS; + } +#endif + +#ifndef MGH_FREESURFER + FILE *fp = fopen(fname, "wb"); + if (!fp) + return EXIT_FAILURE; + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) + fwrite(&hdr, sizeof(hdr), 1, fp); + uint32_t pad = 0; + fwrite(&pad, sizeof(pad), 1, fp); + fwrite(&im[0], imgsz, 1, fp); + fclose(fp); +#endif + + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) + if ((opts.isGz) && (strlen(opts.pigzname) > 0)) { +#ifndef myDisableGzSizeLimits + if ((imgsz + hdr.vox_offset) > kMaxPigz) { + printWarning("Saving uncompressed data: image too large for pigz.\n"); + return EXIT_SUCCESS; + } +#endif + return pigz_File(fname, opts, imgsz); + } + return EXIT_SUCCESS; +} // nii_saveNII() + +#endif + +int nii_saveNIIx(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts) { + struct TDICOMdata dcm = clear_dicom_data(); + return nii_saveNII(niiFilename, hdr, im, opts, dcm); +} + +int nii_saveNII3D(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { + //save 4D series as sequence of 3D volumes + struct nifti_1_header hdr1 = hdr; + int nVol = 1; + for (int i = 4; i < 8; i++) { + if (hdr.dim[i] > 1) + nVol = nVol * hdr.dim[i]; + hdr1.dim[i] = 0; + } + hdr1.dim[0] = 3; //save as 3D file + size_t imgsz = nii_ImgBytes(hdr1); + size_t pos = 0; + char fname[2048] = {""}; + char zeroPad[PATH_MAX] = {""}; + double fnVol = nVol; + int zeroPadLen = (1 + log10(fnVol)); + sprintf(zeroPad, "%%s_%%0%dd", zeroPadLen); + for (int i = 1; i <= nVol; i++) { + sprintf(fname, zeroPad, niiFilename, i); + if (nii_saveNII(fname, hdr1, (unsigned char *)&im[pos], opts, d) == EXIT_FAILURE) + return EXIT_FAILURE; + pos += imgsz; + } + return EXIT_SUCCESS; +} // nii_saveNII3D() + +void nii_storeIntegerScaleFactor(int scale, struct nifti_1_header *hdr) { + //appends NIfTI header description field with " isN" where N is integer scaling + char newstr[256]; + sprintf(newstr, " is%d", scale); + if ((strlen(newstr) + strlen(hdr->descrip)) < 80) + strcat(hdr->descrip, newstr); +} + +void nii_mask12bit(unsigned char *img, struct nifti_1_header *hdr) { + //https://github.com/rordenlab/dcm2niix/issues/251 + if (hdr->datatype != DT_INT16) + return; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return; + int16_t *img16 = (int16_t *)img; + for (int i = 0; i < nVox; i++) + img16[i] = img16[i] & 4095; //12 bit data ranges from 0..4095, any other values are overflow +} + +unsigned char * nii_uint16toFloat32(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { + if (hdr->datatype != DT_UINT16) + return img; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return img; + unsigned short *img16 = (unsigned short *)img; + unsigned char *imOut = (unsigned char *)malloc(nVox * 4); // *4 as 32-bits per voxel, sizeof(float) ) + float *imOut32 = (float *)imOut; + for (int i = 0; i < nVox; i++) + imOut32[i] = (hdr->scl_slope * img16[i]) + hdr->scl_inter; + free(img); + hdr->scl_slope = 1.0; + hdr->scl_inter = 1.0; + hdr->datatype = DT_FLOAT32; + hdr->bitpix = 32; + if (isVerbose) + printMessage("Converted uint16 to float32\n"); + return imOut; +} // nii_uint16toFloat32() + +void nii_scale16bitSigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { + //lossless scaling of INT16 data: e.g. input with range -100...3200 and scl_slope=1 + // will be stored as -1000...32000 with scl_slope 0.1 + if (hdr->datatype != DT_INT16) + return; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return; + int16_t *img16 = (int16_t *)img; + int16_t max16 = img16[0]; + int16_t min16 = max16; + for (int i = 0; i < nVox; i++) { + if (img16[i] < min16) + min16 = img16[i]; + if (img16[i] > max16) + max16 = img16[i]; + } + int kMx = 32000; //actually 32767 - maybe a bit of padding for interpolation ringing + int scale = kMx / (int)max16; + if (abs(min16) > max16) + scale = kMx / (int)abs(min16); + if (scale < 2) { + if (isVerbose) + printMessage("Sufficient 16-bit range: raw %d..%d\n", min16, max16); + return; //already uses dynamic range + } + hdr->scl_slope = hdr->scl_slope / scale; + for (int i = 0; i < nVox; i++) + img16[i] = img16[i] * scale; + printMessage("Maximizing 16-bit range: raw %d..%d is%d\n", min16, max16, scale); + nii_storeIntegerScaleFactor(scale, hdr); +} + +void nii_scale16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { + //lossless scaling of UINT16 data: e.g. input with range 0...3200 and scl_slope=1 + // will be stored as 0...64000 with scl_slope 0.05 + if (hdr->datatype != DT_UINT16) + return; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return; + uint16_t *img16 = (uint16_t *)img; + uint16_t max16 = img16[0]; + for (int i = 0; i < nVox; i++) + if (img16[i] > max16) + max16 = img16[i]; + int kMx = 64000; //actually 65535 - maybe a bit of padding for interpolation ringing + int scale = kMx / (int)max16; + if (scale < 2) { + if (isVerbose > 0) + printMessage("Sufficient unsigned 16-bit range: raw max %d\n", max16); + return; //already uses dynamic range + } + hdr->scl_slope = hdr->scl_slope / scale; + for (int i = 0; i < nVox; i++) + img16[i] = img16[i] * scale; + printMessage("Maximizing 16-bit range: raw max %d is%d\n", max16, scale); + nii_storeIntegerScaleFactor(scale, hdr); +} + +#define UINT16_TO_INT16_IF_LOSSLESS +#ifdef UINT16_TO_INT16_IF_LOSSLESS +void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { + //default NIfTI 16-bit is signed, set to unusual 16-bit unsigned if required... + if (hdr->datatype != DT_UINT16) + return; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return; + unsigned short *img16 = (unsigned short *)img; + unsigned short max16 = img16[0]; + //clock_t start = clock(); + for (int i = 0; i < nVox; i++) + if (img16[i] > max16) + max16 = img16[i]; + //printMessage("max16= %d vox=%d %fms\n",max16, nVox, ((double)(clock()-start))/1000); + if (max16 > 32767) { + if (isVerbose > 0) + printMessage("Note: 16-bit UNSIGNED integer image. Some tools will convert to 32-bit.\n"); + } else { + hdr->datatype = DT_INT16; + printMessage("UINT16->INT16 Future release will change default. github.com/rordenlab/dcm2niix/issues/338\n"); + } +} //nii_check16bitUnsigned() +#else +void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { + if (hdr->datatype != DT_UINT16) + return; + if (isVerbose < 1) + return; + printMessage("Note: 16-bit UNSIGNED integer image. Some tools will convert to 32-bit.\n"); +} +#endif + +//void reportPos(struct TDICOMdata d1) { +// printMessage("Instance\t%d\t0020,0032\t%g\t%g\t%g\n", d1.imageNum, d1.patientPosition[1],d1.patientPosition[2],d1.patientPosition[3]); +//} + +int siemensCtKludge(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[]) { + //Siemens CT bug: when a user draws an open object graphics object onto a 2D slice this is appended as an additional image, + //regardless of slice position. These images do not report number of positions in the volume, so we need tedious leg work to detect + uint64_t indx0 = dcmSort[0].indx; + if ((nConvert < 2) || (dcmList[indx0].manufacturer != kMANUFACTURER_SIEMENS) || (!isSameFloat(dcmList[indx0].TR, 0.0f))) + return nConvert; + float prevDx = 0.0; + for (int i = 1; i < nConvert; i++) { + float dx = intersliceDistance(dcmList[indx0], dcmList[dcmSort[i].indx]); + if ((!isSameFloat(dx, 0.0f)) && (dx < prevDx)) { + //for (int j = 1; j < nConvert; j++) + // reportPos(dcmList[dcmSort[j].indx]); + printMessage("Slices skipped: image position not sequential, admonish your vendor (Siemens OOG?)\n"); + return i; + } + prevDx = dx; + } + return nConvert; //all images in sequential order +} // siemensCtKludge() + +int isSameFloatT(float a, float b, float tolerance) { + return (fabs(a - b) <= tolerance); +} + +void adjustOriginForNegativeTilt(struct nifti_1_header *hdr, float shiftPxY) { + if (hdr->sform_code > 0) { + // Adjust the srow_* offsets using srow_y + hdr->srow_x[3] -= shiftPxY * hdr->srow_y[0]; + hdr->srow_y[3] -= shiftPxY * hdr->srow_y[1]; + hdr->srow_z[3] -= shiftPxY * hdr->srow_y[2]; + } + if (hdr->qform_code > 0) { + // Adjust the quaternion offsets using quatern_* and pixdim + mat44 mat = nifti_quatern_to_mat44(hdr->quatern_b, hdr->quatern_c, hdr->quatern_d, + hdr->qoffset_x, hdr->qoffset_y, hdr->qoffset_z, + hdr->pixdim[1], hdr->pixdim[2], hdr->pixdim[3], hdr->pixdim[0]); + hdr->qoffset_x -= shiftPxY * mat.m[1][0]; + hdr->qoffset_y -= shiftPxY * mat.m[1][1]; + hdr->qoffset_z -= shiftPxY * mat.m[1][2]; + } +} + +unsigned char *nii_saveNII3DtiltFloat32(char *niiFilename, struct nifti_1_header *hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, float *sliceMMarray, float gantryTiltDeg, int manufacturer) { + //correct for gantry tilt - http://www.mathworks.com/matlabcentral/fileexchange/24458-dicom-gantry-tilt-correction + if (opts.isOnlyBIDS) + return im; + if (gantryTiltDeg == 0.0) + return im; + struct nifti_1_header hdrIn = *hdr; + int nVox2DIn = hdrIn.dim[1] * hdrIn.dim[2]; + if ((nVox2DIn < 1) || (hdrIn.dim[0] != 3) || (hdrIn.dim[3] < 3)) + return im; + if (hdrIn.datatype != DT_FLOAT32) { + printMessage("Only able to correct gantry tilt for 16-bit integer or 32-bit float data with at least 3 slices."); + return im; + } + printMessage("Gantry Tilt Correction is new: please validate conversions\n"); + float GNTtanPx = tan(gantryTiltDeg / (180 / M_PI)) / hdrIn.pixdim[2]; //tangent(degrees->radian) +//unintuitive step: reverse sign for negative gantry tilt, therefore -27deg == +27deg (why @!?#) +// seen in http://www.mathworks.com/matlabcentral/fileexchange/28141-gantry-detector-tilt-correction/content/gantry2.m +// also validated with actual data... +#ifndef newTilt + if (manufacturer == kMANUFACTURER_PHILIPS) //see 'Manix' example from Osirix + GNTtanPx = -GNTtanPx; + else if ((manufacturer == kMANUFACTURER_SIEMENS) && (gantryTiltDeg > 0.0)) + GNTtanPx = -GNTtanPx; + else if (manufacturer == kMANUFACTURER_GE) + ; //do nothing + else if (gantryTiltDeg < 0.0) + GNTtanPx = -GNTtanPx; //see Toshiba examples from John Muschelli +#endif //newTilt + float *imIn32 = (float *)im; + //create new output image: larger due to skew + // compute how many pixels slice must be extended due to skew + int s = hdrIn.dim[3] - 1; //top slice + float maxSliceMM = fabs(s * hdrIn.pixdim[3]); + if (sliceMMarray != NULL) + maxSliceMM = fabs(sliceMMarray[s]); + int pxOffset = ceil(fabs(GNTtanPx * maxSliceMM)); + // printMessage("Tilt extends slice by %d pixels", pxOffset); + hdr->dim[2] = hdr->dim[2] + pxOffset; + // When there is negative tilt, the image origin must be adjusted for the padding that will be added. + if (GNTtanPx < 0) { + // printMessage("Adjusting origin for %d pixels padding (float)\n", pxOffset); + adjustOriginForNegativeTilt(hdr, pxOffset); + } + int nVox2D = hdr->dim[1] * hdr->dim[2]; + unsigned char *imOut = (unsigned char *)malloc(nVox2D * hdrIn.dim[3] * 4); // *4 as 32-bits per voxel, sizeof(float) ); + float *imOut32 = (float *)imOut; + //set surrounding voxels to padding (if present) or darkest observed value + bool hasPixelPaddingValue = !isnan(d.pixelPaddingValue); + float pixelPaddingValue; + if (hasPixelPaddingValue) { + pixelPaddingValue = d.pixelPaddingValue; + } else { + // Find darkest pixel value. Note that `hasPixelPaddingValue` remains false so that the darkest value + // will not trigger nearest neighbor interpolation below when this value is found in the image. + pixelPaddingValue = imIn32[0]; + for (int v = 0; v < (nVox2DIn * hdrIn.dim[3]); v++) + if (imIn32[v] < pixelPaddingValue) + pixelPaddingValue = imIn32[v]; + } + for (int v = 0; v < (nVox2D * hdrIn.dim[3]); v++) + imOut32[v] = pixelPaddingValue; + //copy skewed voxels + for (int s = 0; s < hdrIn.dim[3]; s++) { //for each slice + float sliceMM = s * hdrIn.pixdim[3]; + if (sliceMMarray != NULL) + sliceMM = sliceMMarray[s]; //variable slice thicknesses + //sliceMM -= mmMidZ; //adjust so tilt relative to middle slice + if (GNTtanPx < 0) + sliceMM -= maxSliceMM; + float Offset = GNTtanPx * sliceMM; + float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset + float fracLo = 1.0f - fracHi; + for (int r = 0; r < hdr->dim[2]; r++) { //for each row of output + float rI = (float)r - Offset; //input row + if ((rI >= 0.0) && (rI < hdrIn.dim[2])) { + int rLo = floor(rI); + int rHi = rLo + 1; + if (rHi >= hdrIn.dim[2]) + rHi = rLo; + rLo = (rLo * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row below + rHi = (rHi * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row above + int rOut = (r * hdrIn.dim[1]) + (s * nVox2D); //offset to output row + for (int c = 0; c < hdrIn.dim[1]; c++) { //for each column + float valLo = (float)imIn32[rLo + c]; + float valHi = (float)imIn32[rHi + c]; + if (hasPixelPaddingValue && (valLo == pixelPaddingValue || valHi == pixelPaddingValue)) { + // https://github.com/rordenlab/dcm2niix/issues/262 - Use nearest neighbor interpolation + // when at least one of the values is padding. + imOut32[rOut + c] = fracHi >= 0.5 ? valHi : valLo; + } else { + imOut32[rOut + c] = round(valLo * fracLo + valHi * fracHi); + } + } //for c (each column) + } //rI (input row) in range + } //for r (each row) + } //for s (each slice)*/ + free(im); + if (sliceMMarray != NULL) + return imOut; //we will save after correcting for variable slice thicknesses + char niiFilenameTilt[2048] = {""}; + strcat(niiFilenameTilt, niiFilename); + strcat(niiFilenameTilt, "_Tilt"); + nii_saveNII3D(niiFilenameTilt, *hdr, imOut, opts, d); + return imOut; +} // nii_saveNII3DtiltFloat32() + +unsigned char *nii_saveNII3Dtilt(char *niiFilename, struct nifti_1_header *hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, float *sliceMMarray, float gantryTiltDeg, int manufacturer) { + //correct for gantry tilt - http://www.mathworks.com/matlabcentral/fileexchange/24458-dicom-gantry-tilt-correction + if (opts.isOnlyBIDS) + return im; + if (gantryTiltDeg == 0.0) + return im; + struct nifti_1_header hdrIn = *hdr; + int nVox2DIn = hdrIn.dim[1] * hdrIn.dim[2]; + if ((nVox2DIn < 1) || (hdrIn.dim[0] != 3) || (hdrIn.dim[3] < 3)) + return im; + if (hdrIn.datatype == DT_FLOAT32) + return nii_saveNII3DtiltFloat32(niiFilename, hdr, im, opts, d, sliceMMarray, gantryTiltDeg, manufacturer); + if (hdrIn.datatype != DT_INT16) { + printMessage("Only able to correct gantry tilt for 16-bit integer data with at least 3 slices."); + return im; + } + printMessage("Gantry Tilt Correction is new: please validate conversions\n"); + float GNTtanPx = tan(gantryTiltDeg / (180 / M_PI)) / hdrIn.pixdim[2]; //tangent(degrees->radian) +//unintuitive step: reverse sign for negative gantry tilt, therefore -27deg == +27deg (why @!?#) +// seen in http://www.mathworks.com/matlabcentral/fileexchange/28141-gantry-detector-tilt-correction/content/gantry2.m +// also validated with actual data... +#ifndef newTilt + if (manufacturer == kMANUFACTURER_PHILIPS) //see 'Manix' example from Osirix + GNTtanPx = -GNTtanPx; + else if ((manufacturer == kMANUFACTURER_SIEMENS) && (gantryTiltDeg > 0.0)) + GNTtanPx = -GNTtanPx; + else if (manufacturer == kMANUFACTURER_GE) + ; //do nothing + else if (gantryTiltDeg < 0.0) + GNTtanPx = -GNTtanPx; //see Toshiba examples from John Muschelli +#endif //newTilt + short *imIn16 = (short *)im; + //create new output image: larger due to skew + // compute how many pixels slice must be extended due to skew + int s = hdrIn.dim[3] - 1; //top slice + float maxSliceMM = fabs(s * hdrIn.pixdim[3]); + if (sliceMMarray != NULL) + maxSliceMM = fabs(sliceMMarray[s]); + int pxOffset = ceil(fabs(GNTtanPx * maxSliceMM)); + // printMessage("Tilt extends slice by %d pixels", pxOffset); + hdr->dim[2] = hdr->dim[2] + pxOffset; + + // When there is negative tilt, the image origin must be adjusted for the padding that will be added. + if (GNTtanPx < 0) { + // printMessage("Adjusting origin for %d pixels padding (short)\n", pxOffset); + adjustOriginForNegativeTilt(hdr, pxOffset); + } + int nVox2D = hdr->dim[1] * hdr->dim[2]; + unsigned char *imOut = (unsigned char *)malloc(nVox2D * hdrIn.dim[3] * 2); // *2 as 16-bits per voxel, sizeof( short) ); + short *imOut16 = (short *)imOut; + //set surrounding voxels to padding (if present) or darkest observed value + bool hasPixelPaddingValue = !isnan(d.pixelPaddingValue); + short pixelPaddingValue; + if (hasPixelPaddingValue) { + pixelPaddingValue = (short)round(d.pixelPaddingValue); + } else { + // Find darkest pixel value. Note that `hasPixelPaddingValue` remains false so that the darkest value + // will not trigger nearest neighbor interpolation below when this value is found in the image. + pixelPaddingValue = imIn16[0]; + for (int v = 0; v < (nVox2DIn * hdrIn.dim[3]); v++) + if (imIn16[v] < pixelPaddingValue) + pixelPaddingValue = imIn16[v]; + } + for (int v = 0; v < (nVox2D * hdrIn.dim[3]); v++) + imOut16[v] = pixelPaddingValue; + //copy skewed voxels + for (int s = 0; s < hdrIn.dim[3]; s++) { //for each slice + float sliceMM = s * hdrIn.pixdim[3]; + if (sliceMMarray != NULL) + sliceMM = sliceMMarray[s]; //variable slice thicknesses + //sliceMM -= mmMidZ; //adjust so tilt relative to middle slice + if (GNTtanPx < 0) + sliceMM -= maxSliceMM; + float Offset = GNTtanPx * sliceMM; + float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset + float fracLo = 1.0f - fracHi; + for (int r = 0; r < hdr->dim[2]; r++) { //for each row of output + float rI = (float)r - Offset; //input row + if ((rI >= 0.0) && (rI < hdrIn.dim[2])) { + int rLo = floor(rI); + int rHi = rLo + 1; + if (rHi >= hdrIn.dim[2]) + rHi = rLo; + rLo = (rLo * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row below + rHi = (rHi * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row above + int rOut = (r * hdrIn.dim[1]) + (s * nVox2D); //offset to output row + for (int c = 0; c < hdrIn.dim[1]; c++) { //for each row + short valLo = imIn16[rLo + c]; + short valHi = imIn16[rHi + c]; + if (hasPixelPaddingValue && (valLo == pixelPaddingValue || valHi == pixelPaddingValue)) { + // https://github.com/rordenlab/dcm2niix/issues/262 - Use nearest neighbor interpolation + // when at least one of the values is padding. + imOut16[rOut + c] = fracHi >= 0.5 ? valHi : valLo; + } else { + imOut16[rOut + c] = round((((float)valLo) * fracLo) + ((float)valHi) * fracHi); + } + } //for c (each column) + } //rI (input row) in range + } //for r (each row) + } //for s (each slice)*/ + free(im); + if (sliceMMarray != NULL) + return imOut; //we will save after correcting for variable slice thicknesses + char niiFilenameTilt[2048] = {""}; + strcat(niiFilenameTilt, niiFilename); + strcat(niiFilenameTilt, "_Tilt"); + nii_saveNII3D(niiFilenameTilt, *hdr, imOut, opts, d); + return imOut; +} // nii_saveNII3Dtilt() + +int nii_saveNII3Deq(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, float *sliceMMarray) { + //convert image with unequal slice distances to equal slice distances + //sliceMMarray = 0.0 3.0 6.0 12.0 22.0 <- ascending distance from first slice + if (opts.isOnlyBIDS) + return EXIT_SUCCESS; + int nVox2D = hdr.dim[1] * hdr.dim[2]; + if ((nVox2D < 1) || (hdr.dim[0] != 3) || (hdr.dim[3] < 3)) + return EXIT_FAILURE; + if ((hdr.datatype != DT_FLOAT32) && (hdr.datatype != DT_UINT8) && (hdr.datatype != DT_RGB24) && (hdr.datatype != DT_INT16)) { + printMessage("Only able to make equidistant slices from 8,16,24-bit integer or 32-bit float image data."); + return EXIT_FAILURE; + } + float mn = sliceMMarray[1] - sliceMMarray[0]; + for (int i = 1; i < hdr.dim[3]; i++) { + float dx = sliceMMarray[i] - sliceMMarray[i - 1]; + if ((dx < mn) && (!isSameFloat(dx, 0.0))) // <- allow slice direction to reverse + mn = sliceMMarray[i] - sliceMMarray[i - 1]; + } + if (mn <= 0.0f) { + printMessage("Unable to equalize slice distances: slice order not consistently ascending:\n"); + printMessage("dx=[0"); + for (int i = 1; i < hdr.dim[3]; i++) + printMessage(" %g", sliceMMarray[i - 1]); + printMessage("]\n"); + printMessage(" Recompiling with '-DmyInstanceNumberOrderIsNotSpatial' might help.\n"); + return EXIT_FAILURE; + } + int slices = hdr.dim[3]; + slices = (int)ceil((sliceMMarray[slices - 1] - 0.5 * (sliceMMarray[slices - 1] - sliceMMarray[slices - 2])) / mn); //-0.5: fence post + if (slices > (hdr.dim[3] * 2)) { + slices = 2 * hdr.dim[3]; + mn = (sliceMMarray[hdr.dim[3] - 1]) / (slices - 1); + } + //printMessage("-->%g mn slices %d orig %d\n", mn, slices, hdr.dim[3]); + if (slices < 3) + return EXIT_FAILURE; + struct nifti_1_header hdrX = hdr; + hdrX.dim[3] = slices; + hdrX.pixdim[3] = mn; + if ((hdr.pixdim[3] != 0.0) && (hdr.pixdim[3] != hdrX.pixdim[3])) { + float Scale = hdrX.pixdim[3] / hdr.pixdim[3]; + //to do: do I change srow_z or srow_x[2], srow_y[2], srow_z[2], + hdrX.srow_z[0] = hdr.srow_z[0] * Scale; + hdrX.srow_z[1] = hdr.srow_z[1] * Scale; + hdrX.srow_z[2] = hdr.srow_z[2] * Scale; + } + unsigned char *imX; + if (hdr.datatype == DT_FLOAT32) { + float *im32 = (float *)im; + imX = (unsigned char *)malloc((nVox2D * slices) * 4); //sizeof(float) + float *imX32 = (float *)imX; + for (int s = 0; s < slices; s++) { + float sliceXmm = s * mn; //distance from first slice + int sliceXi = (s * nVox2D); //offset for this slice + int sHi = 0; + while ((sHi < (hdr.dim[3] - 1)) && (sliceMMarray[sHi] < sliceXmm)) + sHi += 1; + int sLo = sHi - 1; + if (sLo < 0) + sLo = 0; + float mmHi = sliceMMarray[sHi]; + float mmLo = sliceMMarray[sLo]; + sLo = sLo * nVox2D; + sHi = sHi * nVox2D; + if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX + //for (int v=0; v < nVox2D; v++) + // imX16[sliceXi+v] = im16[sHi+v]; + memcpy(&imX32[sliceXi], &im32[sHi], nVox2D * sizeof(float)); //memcpy( dest, src, bytes) + } else { + float fracHi = (sliceXmm - mmLo) / (mmHi - mmLo); + float fracLo = 1.0 - fracHi; + //weight between two slices + for (int v = 0; v < nVox2D; v++) + imX32[sliceXi + v] = round(((float)im32[sLo + v] * fracLo) + (float)im32[sHi + v] * fracHi); + } + } + } else if (hdr.datatype == DT_INT16) { + short *im16 = (short *)im; + imX = (unsigned char *)malloc((nVox2D * slices) * 2); //sizeof( short) ); + short *imX16 = (short *)imX; + for (int s = 0; s < slices; s++) { + float sliceXmm = s * mn; //distance from first slice + int sliceXi = (s * nVox2D); //offset for this slice + int sHi = 0; + while ((sHi < (hdr.dim[3] - 1)) && (sliceMMarray[sHi] < sliceXmm)) + sHi += 1; + int sLo = sHi - 1; + if (sLo < 0) + sLo = 0; + float mmHi = sliceMMarray[sHi]; + float mmLo = sliceMMarray[sLo]; + sLo = sLo * nVox2D; + sHi = sHi * nVox2D; + if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX + //for (int v=0; v < nVox2D; v++) + // imX16[sliceXi+v] = im16[sHi+v]; + memcpy(&imX16[sliceXi], &im16[sHi], nVox2D * sizeof(unsigned short)); //memcpy( dest, src, bytes) + } else { + float fracHi = (sliceXmm - mmLo) / (mmHi - mmLo); + float fracLo = 1.0 - fracHi; + //weight between two slices + for (int v = 0; v < nVox2D; v++) + imX16[sliceXi + v] = round(((float)im16[sLo + v] * fracLo) + (float)im16[sHi + v] * fracHi); + } + } + } else { + if (hdr.datatype == DT_RGB24) + nVox2D = nVox2D * 3; + imX = (unsigned char *)malloc((nVox2D * slices) * 2); //sizeof( short) ); + for (int s = 0; s < slices; s++) { + float sliceXmm = s * mn; //distance from first slice + int sliceXi = (s * nVox2D); //offset for this slice + int sHi = 0; + while ((sHi < (hdr.dim[3] - 1)) && (sliceMMarray[sHi] < sliceXmm)) + sHi += 1; + int sLo = sHi - 1; + if (sLo < 0) + sLo = 0; + float mmHi = sliceMMarray[sHi]; + float mmLo = sliceMMarray[sLo]; + sLo = sLo * nVox2D; + sHi = sHi * nVox2D; + if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX + memcpy(&imX[sliceXi], &im[sHi], nVox2D); //memcpy( dest, src, bytes) + } else { + float fracHi = (sliceXmm - mmLo) / (mmHi - mmLo); + float fracLo = 1.0 - fracHi; //weight between two slices + for (int v = 0; v < nVox2D; v++) + imX[sliceXi + v] = round(((float)im[sLo + v] * fracLo) + (float)im[sHi + v] * fracHi); + } + } + } + char niiFilenameEq[2048] = {""}; + strcat(niiFilenameEq, niiFilename); + strcat(niiFilenameEq, "_Eq"); + nii_saveNII3D(niiFilenameEq, hdrX, imX, opts, d); + free(imX); + return EXIT_SUCCESS; +} // nii_saveNII3Deq() + +float PhilipsPreciseVal(float lPV, float lRS, float lRI, float lSS) { + if ((lRS * lSS) == 0) //avoid divide by zero + return 0.0; + else + return (lPV * lRS + lRI) / (lRS * lSS); +} + +void PhilipsPrecise(struct TDICOMdata *d, bool isPhilipsFloatNotDisplayScaling, struct nifti_1_header *h, int verbose) { + if (d->manufacturer != kMANUFACTURER_PHILIPS) + return; //not Philips + if (d->isScaleVariesEnh) + return; //issue363 rescaled before slice reordering + /* + if (!isSameFloatGE(0.0, d->RWVScale)) { //https://github.com/rordenlab/dcm2niix/issues/493 + h->scl_slope = d->RWVScale; + h->scl_inter = d->RWVIntercept; + printMessage("Using RWVSlope:RWVIntercept = %g:%g\n",d->RWVScale,d->RWVIntercept); + printMessage(" Philips Scaling Values RS:RI:SS = %g:%g:%g (see PMC3998685)\n",d->intenScale,d->intenIntercept,d->intenScalePhilips); + if (verbose == 0) return; + printMessage("Potential Alternative Intensity Scalings\n"); + printMessage(" R = raw value, P = precise value, D = displayed value\n"); + printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); + printMessage(" D = R * RS + RI , P = D/(RS * SS)\n"); + return; + }*/ + if (d->intenScalePhilips == 0) + return; //no Philips Precise + //we will report calibrated "FP" values http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/ + float l0 = PhilipsPreciseVal(0, d->intenScale, d->intenIntercept, d->intenScalePhilips); + float l1 = PhilipsPreciseVal(1, d->intenScale, d->intenIntercept, d->intenScalePhilips); + float intenScaleP = d->intenScale; + float intenInterceptP = d->intenIntercept; + if (l0 != l1) { + intenInterceptP = l0; + intenScaleP = l1 - l0; + } + if (isSameFloat(d->intenIntercept, intenInterceptP) && isSameFloat(d->intenScale, intenScaleP)) + return; //same result for both methods: nothing to do or report! + printMessage("Philips Scaling Values RS:RI:SS = %g:%g:%g (see PMC3998685)\n", d->intenScale, d->intenIntercept, d->intenScalePhilips); + if (verbose > 0) { + printMessage(" R = raw value, P = precise value, D = displayed value\n"); + printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); + printMessage(" D = R * RS + RI; P = D/(RS * SS)\n"); + printMessage(" D scl_slope:scl_inter = %g:%g\n", d->intenScale, d->intenIntercept); + printMessage(" P scl_slope:scl_inter = %g:%g\n", intenScaleP, intenInterceptP); + } + //#define myUsePhilipsPrecise + if (isPhilipsFloatNotDisplayScaling) { + if (verbose > 0) + printMessage(" Using P values ('-p n ' for D values)\n"); + //to change DICOM: + //d->intenScale = intenScaleP; + //d->intenIntercept = intenInterceptP; + //to change NIfTI + h->scl_slope = intenScaleP; + h->scl_inter = intenInterceptP; + d->intenScalePhilips = 0; //so we never run this TWICE! + } else if (verbose > 0) + printMessage(" Using D values ('-p y ' for P values)\n"); +} //PhilipsPrecise() + +void smooth1D(int num, double *im) { + if (num < 3) + return; + double *src = (double *)malloc(sizeof(double) * num); + memcpy(&src[0], &im[0], num * sizeof(double)); //memcpy( dest, src, bytes) + double frac = 0.25; + for (int i = 1; i < (num - 1); i++) + im[i] = (src[i - 1] * frac) + (src[i] * frac * 2) + (src[i + 1] * frac); + free(src); +} // smooth1D() + +int nii_saveCrop(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { + //remove excess neck slices - assumes output of nii_setOrtho() + if (opts.isOnlyBIDS) + return EXIT_SUCCESS; + int nVox2D = hdr.dim[1] * hdr.dim[2]; + if ((nVox2D < 1) || (fabs(hdr.pixdim[3]) < 0.001) || (hdr.dim[0] != 3) || (hdr.dim[3] < 128)) + return EXIT_FAILURE; + if ((hdr.datatype != DT_INT16) && (hdr.datatype != DT_UINT16)) { + printMessage("Only able to crop 16-bit volumes."); + return EXIT_FAILURE; + } + short *im16 = (short *)im; + unsigned short *imu16 = (unsigned short *)im; + float kThresh = 0.09; //more than 9% of max brightness + int ventralCrop = 0; + //find max value for each slice + int slices = hdr.dim[3]; + double *sliceSums = (double *)malloc(sizeof(double) * slices); + double maxSliceVal = 0.0; + for (int i = (slices - 1); i >= 0; i--) { + sliceSums[i] = 0; + int sliceStart = i * nVox2D; + if (hdr.datatype == DT_UINT16) + for (int j = 0; j < nVox2D; j++) + sliceSums[i] += imu16[j + sliceStart]; + else + for (int j = 0; j < nVox2D; j++) + sliceSums[i] += im16[j + sliceStart]; + if (sliceSums[i] > maxSliceVal) + maxSliceVal = sliceSums[i]; + } + if (maxSliceVal <= 0) { + free(sliceSums); + return EXIT_FAILURE; + } + smooth1D(slices, sliceSums); + for (int i = 0; i < slices; i++) + sliceSums[i] = sliceSums[i] / maxSliceVal; //so brightest slice has value 1 + //dorsal crop: eliminate slices with more than 5% brightness + int dorsalCrop; + for (dorsalCrop = (slices - 1); dorsalCrop >= 1; dorsalCrop--) + if (sliceSums[dorsalCrop - 1] > kThresh) + break; + if (dorsalCrop <= 1) { + free(sliceSums); + return EXIT_FAILURE; + } + const double kMaxDVmm = 169.0; + ventralCrop = dorsalCrop - round(kMaxDVmm / hdr.pixdim[3]); + if (ventralCrop < 0) + ventralCrop = 0; + //apply crop + printMessage(" Cropping from slice %d to %d (of %d)\n", ventralCrop, dorsalCrop, slices); + struct nifti_1_header hdrX = hdr; + slices = dorsalCrop - ventralCrop + 1; + hdrX.dim[3] = slices; + //translate origin to account for missing slices + hdrX.srow_x[3] += hdr.srow_x[2] * ventralCrop; + hdrX.srow_y[3] += hdr.srow_y[2] * ventralCrop; + hdrX.srow_z[3] += hdr.srow_z[2] * ventralCrop; + //convert data + unsigned char *imX; + imX = (unsigned char *)malloc((nVox2D * slices) * 2); //sizeof( short) ); + short *imX16 = (short *)imX; + for (int s = 0; s < slices; s++) { + int sIn = s + ventralCrop; + int sOut = s; + sOut = sOut * nVox2D; + sIn = sIn * nVox2D; + memcpy(&imX16[sOut], &im16[sIn], nVox2D * sizeof(unsigned short)); //memcpy( dest, src, bytes) + } + char niiFilenameCrop[2048] = {""}; + strcat(niiFilenameCrop, niiFilename); + strcat(niiFilenameCrop, "_Crop"); + const int returnCode = nii_saveNII3D(niiFilenameCrop, hdrX, imX, opts, d); + free(imX); + return returnCode; +} // nii_saveCrop() + +double dicomTimeToSec(double dicomTime) { + //convert HHMMSS to seconds, 135300.024 -> 135259.731 are 0.293 sec apart + char acqTimeBuf[64]; + snprintf(acqTimeBuf, sizeof acqTimeBuf, "%+013.5f", (double)dicomTime); + int ahour, amin; + double asec; + int count = 0; + sscanf(acqTimeBuf, "%3d%2d%lf%n", &ahour, &amin, &asec, &count); + if (!count) + return -1; + return (ahour * 3600) + (amin * 60) + asec; +} + +double acquisitionTimeDifference(struct TDICOMdata *d1, struct TDICOMdata *d2) { + if (d1->acquisitionDate != d2->acquisitionDate) + return -1; //to do: scans running across midnight + double sec1 = dicomTimeToSec(d1->acquisitionTime); + double sec2 = dicomTimeToSec(d2->acquisitionTime); + //printMessage("%g\n",d2->acquisitionTime); + if ((sec1 < 0) || (sec2 < 0)) + return -1; + return (sec2 - sec1); +} + +void checkDateTimeOrder(struct TDICOMdata *d, struct TDICOMdata *d1) { + if (d->acquisitionDate < d1->acquisitionDate) + return; //d1 occurred on later date + if (d->acquisitionTime <= d1->acquisitionTime) + return; //d1 occurred on later (or same) time + if (d->imageNum > d1->imageNum) + printWarning("Images not sorted in ascending instance number (0020,0013)\n"); + else + printWarning("Images sorted by instance number [0020,0013](%d..%d), but AcquisitionTime [0008,0032] suggests a different order (%g..%g) \n", d->imageNum, d1->imageNum, d->acquisitionTime, d1->acquisitionTime); +} + +void checkSliceTiming(struct TDICOMdata *d, struct TDICOMdata *d1, int verbose, int isForceSliceTimeHHMMSS) { + //detect images with slice timing errors. https://github.com/rordenlab/dcm2niix/issues/126 + //modified 20190704: this function now ensures all slice times are in msec + if ((d->TR < 0.0) || (d->CSA.sliceTiming[0] < 0.0)) + return; //no slice timing + if (d->manufacturer == kMANUFACTURER_GE) + return; //compute directly from Protocol Block + if (d->modality == kMODALITY_PT) + return; //issue407 + int nSlices = 0; + while ((nSlices < kMaxEPI3D) && (d->CSA.sliceTiming[nSlices] >= 0.0)) + nSlices++; + if (nSlices < 1) + return; + if (d->CSA.sliceTiming[kMaxEPI3D - 1] < -1.0) //the value -2.0 is used as a flag for negative MosaicRefAcqTimes in checkSliceTimes(), see issue 271 + printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); + bool isSliceTimeHHMMSS = (d->manufacturer == kMANUFACTURER_UIH); + if (isForceSliceTimeHHMMSS) + isSliceTimeHHMMSS = true; + //if (d->isXA10A) isSliceTimeHHMMSS = true; //for XA10 use TimeAfterStart 0x0021,0x1104 -> Siemens de-identification can corrupt acquisition ties https://github.com/rordenlab/dcm2niix/issues/236 + if (isSliceTimeHHMMSS) { //handle midnight crossing + for (int i = 0; i < nSlices; i++) + d->CSA.sliceTiming[i] = dicomTimeToSec(d->CSA.sliceTiming[i]); + float minT = d->CSA.sliceTiming[0]; + float maxT = minT; + for (int i = 0; i < nSlices; i++) { + if (d->CSA.sliceTiming[i] < minT) + minT = d->CSA.sliceTiming[i]; + if (d->CSA.sliceTiming[i] < maxT) + maxT = d->CSA.sliceTiming[i]; + } + //printf("%d %g ---> %g..%g\n", nSlices, d->TR, minT, maxT); + float kMidnightSec = 86400; + float kNoonSec = 43200; + if ((maxT - minT) > kNoonSec) { //volume started before midnight but ended next day! + //identify and fix 'Cinderella error' where clock resets at midnight: untested + printWarning("Acquisition crossed midnight: check slice timing\n"); + for (int i = 0; i < nSlices; i++) + if (d->CSA.sliceTiming[i] > kNoonSec) + d->CSA.sliceTiming[i] = d->CSA.sliceTiming[i] - kMidnightSec; + minT = d->CSA.sliceTiming[0]; + for (int i = 0; i < nSlices; i++) + if (d->CSA.sliceTiming[i] < minT) + minT = d->CSA.sliceTiming[i]; + } + for (int i = 0; i < nSlices; i++) + d->CSA.sliceTiming[i] = d->CSA.sliceTiming[i] - minT; + } //XA10/UIH: HHMMSS -> Sec + float minT = d->CSA.sliceTiming[0]; + float maxT = minT; + for (int i = 0; i < kMaxEPI3D; i++) { + if (d->CSA.sliceTiming[i] < 0.0) + break; + if (d->CSA.sliceTiming[i] < minT) + minT = d->CSA.sliceTiming[i]; + if (d->CSA.sliceTiming[i] > maxT) + maxT = d->CSA.sliceTiming[i]; + } + if (isSliceTimeHHMMSS) //convert HHMMSS to msec + for (int i = 0; i < kMaxEPI3D; i++) + d->CSA.sliceTiming[i] = dicomTimeToSec(d->CSA.sliceTiming[i]) * 1000.0; + float TRms = d->TR; //d->TR in msec! + if ((minT != maxT) && (maxT <= TRms)) { + if (verbose != 0) + printMessage("Slice timing range appears reasonable (range %g..%g, TR=%g ms)\n", minT, maxT, TRms); + return; //looks fine + } + if ((minT == maxT) && (d->is3DAcq)) + return; //fine: 3D EPI + if ((minT == maxT) && (d->CSA.multiBandFactor == d->CSA.mosaicSlices)) + return; //fine: all slices single excitation + if ((strlen(d->seriesDescription) > 0) && (strstr(d->seriesDescription, "SBRef") != NULL)) + return; //fine: single-band calibration data, the slice timing WILL exceed the TR + if (verbose > 1) + printMessage("Slice timing range of first volume: range %g..%g, TR=%g ms)\n", minT, maxT, TRms); + //check if 2nd image has valid slice timing + float minT1 = d1->CSA.sliceTiming[0]; + float maxT1 = minT1; + for (int i = 0; i < nSlices; i++) { + //if (d1->CSA.sliceTiming[i] < 0.0) break; + if (d1->CSA.sliceTiming[i] < minT1) + minT1 = d1->CSA.sliceTiming[i]; + if (d1->CSA.sliceTiming[i] > maxT1) + maxT1 = d1->CSA.sliceTiming[i]; + } + if (verbose > 1) + printMessage("Slice timing range of 2nd volume: range %g..%g, TR=%g ms)\n", minT, maxT, TRms); + if ((minT1 < maxT1) && (minT1 > 0.0) && ((maxT1 - minT1) <= TRms)) { //issue 429: 2nd volume may not start from zero + for (int i = 0; i < nSlices; i++) + d1->CSA.sliceTiming[i] -= minT1; + maxT1 -= minT1; + minT1 -= minT1; + } + if ((minT1 < 0.0) && (d->rtia_timerGE >= 0.0)) + return; //use rtia timer + if (minT1 < 0.0) { //https://github.com/neurolabusc/MRIcroGL/issues/31 + if (d->modality == kMODALITY_MR) + printWarning("Siemens MoCo? Bogus slice timing (range %g..%g, TR=%g seconds)\n", minT1, maxT1, TRms); + return; + } + if ((minT1 == maxT1) || (maxT1 >= TRms)) { //both first and second image corrupted + printWarning("Slice timing appears corrupted (range %g..%g, TR=%g ms)\n", minT1, maxT1, TRms); + return; + } + //1st image corrupted, but 2nd looks ok - substitute values from 2nd image + for (int i = 0; i < kMaxEPI3D; i++) { + d->CSA.sliceTiming[i] = d1->CSA.sliceTiming[i]; + if (d1->CSA.sliceTiming[i] < 0.0) + break; + } + d->CSA.multiBandFactor = d1->CSA.multiBandFactor; + printMessage("CSA slice timing based on 2nd volume, 1st volume corrupted (CMRR bug, range %g..%g, TR=%g ms)\n", minT, maxT, TRms); +} //checkSliceTiming() + +void sliceTimingXA(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert) { + //Siemens XA10 slice timing + // Ignore first volume: For an example of erroneous first volume timing, see series 10 (Functional_w_SMS=3) https://github.com/rordenlab/dcm2niix/issues/240 + // an alternative would be to use 0018,9074 - this would need to be converted from DT to Secs, and is scrambled if de-identifies data see enhanced de-identified series 26 from issue 236 + uint64_t indx0 = dcmSort[0].indx; //first volume + if ((!dcmList[indx0].isXA10A) || (hdr->dim[3] < 1)) + return; + if ((nConvert == (hdr->dim[3] * hdr->dim[4])) && (hdr->dim[3] < (kMaxEPI3D - 1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { + //XA11 2D classic + for (int v = 0; v < hdr->dim[3]; v++) + dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].CSA.sliceTiming[0]; + } else if ((nConvert == (hdr->dim[4])) && (hdr->dim[3] < (kMaxEPI3D - 1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { + //XA10 mosaics - these are missing a lot of information + float mn = dcmList[dcmSort[1].indx].CSA.sliceTiming[0]; + //get slice timing from second volume + for (int v = 0; v < hdr->dim[3]; v++) { + dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[1].indx].CSA.sliceTiming[v]; + if (dcmList[indx0].CSA.sliceTiming[v] < mn) + mn = dcmList[indx0].CSA.sliceTiming[v]; + } + if (mn < 0.0) + mn = 0.0; + int mb = 0; + for (int v = 0; v < hdr->dim[3]; v++) { + dcmList[indx0].CSA.sliceTiming[v] -= mn; + if (isSameFloatGE(dcmList[indx0].CSA.sliceTiming[v], 0.0)) + mb++; + } + if ((dcmList[indx0].CSA.multiBandFactor < 2) && (mb > 1)) + dcmList[indx0].CSA.multiBandFactor = mb; + return; //we have subtracted min + } + //issue429: subtract min + float mn = dcmList[indx0].CSA.sliceTiming[0]; + for (int v = 0; v < hdr->dim[3]; v++) + mn = min(mn, dcmList[indx0].CSA.sliceTiming[v]); + if (isSameFloatGE(mn, 0.0)) + return; + for (int v = 0; v < hdr->dim[3]; v++) + dcmList[indx0].CSA.sliceTiming[v] -= mn; +} //sliceTimingXA() + +void sliceTimeGE(struct TDICOMdata *d, int mb, int dim3, float TR, bool isInterleaved, float geMajorVersion, bool is27r3, float groupDelaysec) { + //mb : multiband factor + //dim3 : number of slices in volume + //TRsec : repetition time in seconds + //isInterleaved : interleaved or sequential slice order + //geMajorVersion: version, e.g. 29.0 + //is27r3 : software release 27.0 R03 or later + float sliceTiming[kMaxEPI3D]; + //multiband can be fractional! 'extra' slices discarded + int nExcitations = ceil(float(dim3) / float(mb)); + if ((mb > 1) && (geMajorVersion < 26.0)) { + printWarning("Unable to determine slice times for early GE HyperBand.\n"); + d->CSA.sliceTiming[0] = -1; + return; + } + if ((mb > 1) && (!is27r3) && ((nExcitations % 2) == 0) ) { //number of slices divided by MB factor should is Even + nExcitations ++; //https://osf.io/q4d53/wiki/home/; Figure 3 of https://pubmed.ncbi.nlm.nih.gov/26308571/ + } + int nDiscardedSlices = (nExcitations * mb) - dim3; + float secPerSlice = (TR - groupDelaysec) / (nExcitations); + if (!isInterleaved) { + for (int i = 0; i < nExcitations; i++) + sliceTiming[i] = i * secPerSlice; + } else { + int nOdd = (nExcitations - 1) / 2; + for (int i = 0; i < nExcitations; i++) { + if (i % 2 == 0) //ODD slices since we index from 0! + sliceTiming[i] = (i / 2) * secPerSlice; + else + sliceTiming[i] = (nOdd + ((i + 1) / 2)) * secPerSlice; + } //for each slice + if ((mb > 1) && (is27r3) && (isInterleaved) && (nExcitations > 2) && ((nExcitations % 2) == 0)) { + float tmp = sliceTiming[nExcitations - 1]; + sliceTiming[nExcitations - 1] = sliceTiming[nExcitations - 3]; + sliceTiming[nExcitations - 3] = tmp; + //printf("SWAP!\n"); + } + } //if interleaved + for (int i = 0; i < dim3; i++) + sliceTiming[i] = sliceTiming[i % nExcitations]; +//#define testSliceTimesGE //note that early GE HyperBand sequences reported single-band values in x0021x105E +#ifdef testSliceTimesGE + float maxErr = 0.0; + for (int i = 0; i < dim3; i++) + maxErr = max(maxErr, fabs(sliceTiming[i] - d->CSA.sliceTiming[i])); + if ((d->CSA.sliceTiming[0] >= 0.0) && (maxErr > 1.0)) { //allow a 1.0 msec tolerance for rounding + printMessage("GE estimated slice times differ from reported (max error: %g)\n", maxErr); + printMessage("Slice\tEstimated\tReported\n"); + for (int i = 0; i < dim3; i++) { + printMessage("%d %g %g\n", i, sliceTiming[i], d->CSA.sliceTiming[i]); + maxErr = max(maxErr, fabs(sliceTiming[i] - d->CSA.sliceTiming[i])); + } + } +#endif + for (int i = 0; i < dim3; i++) + d->CSA.sliceTiming[i] = sliceTiming[i]; +} // sliceTimeGE() + +void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose, char geVersionPrefix[], float *geMajorVersion, int *geMajorVersionInt, int *geMinorVersionInt, int *geReleaseVersionInt, bool *is27r3) { + // softwareVersionsGE + // "27\LX\MR Software release:RX27.0_R02_1831.a" -> 27 + // "28\LX\MR29.1_EA_2039.g" -> 29 + // geVersionPrefix + // RX27.0_R02_1831.a -> RX + // MR29.1_EA_2039.g -> MR + // geMajorVersion + // RX27.0_R02_1831.a -> 27.0 + // MR29.1_EA_2039.g -> 29.1 + // geMajorVersionInt + // RX27.0_R02_1831.a -> 27 + // MR29.1_EA_2039.g -> 29 + // geMinorVersionInt + // RX27.0_R02_1831.a -> 0 + // MR29.1_EA_2039.g -> 1 + // geReleaseVersionInt + // EA->0, R01->1, R02->2, R03->4 + // RX27.0_R02_1831.a -> 2 + // MR29.1_EA_2039.g -> 0 + int len = 0; + // If softwareVersionsGE is 27\LX\MR Software release:RX27.0_R02_1831.a + char *sepStart = strchr(softwareVersionsGE, ':'); + if (sepStart == NULL) { + // If softwareVersionsGE is 28\LX\MR29.1_EA_2039.g + sepStart = strrchr(softwareVersionsGE, '\\'); + if (sepStart == NULL) + return; + } + sepStart += 1; + len = 11; + char *versionString = (char *)malloc(sizeof(char) * len); + versionString[len - 1] = 0; + memcpy(versionString, sepStart, len); + int ver1, ver2, ver3; + char c1, c2, c3, c4; + // RX27.0_R02_ or MR29.1_EA_2 + sscanf(versionString, "%c%c%d.%d_%c%c%d", &c1, &c2, geMajorVersionInt, geMinorVersionInt, &c3, &c4, geReleaseVersionInt); + memcpy(geVersionPrefix, &c1, 1); + memcpy(geVersionPrefix + 1, &c2, 1); + if ((c3 == 'E') && (c4 == 'A')) { + *geReleaseVersionInt = 0; + } + free(versionString); + *geMajorVersion = (float)*geMajorVersionInt + (float)0.1 * (float)*geMinorVersionInt; + *is27r3 = ((*geMajorVersion >= 27.1) || ((*geMajorVersionInt == 27) && (*geReleaseVersionInt >= 3))); + if (verbose > 1) { + printMessage("GE Software VersionPrefix: %s\n", geVersionPrefix); + printMessage("GE Software MajorVersion: %d\n", *geMajorVersionInt); + printMessage("GE Software MinorVersion: %d\n", *geMinorVersionInt); + printMessage("GE Software ReleaseVersion: %d\n", *geReleaseVersionInt); + printMessage("GE Software is27r3: %d\n", *is27r3); + } +} // readSoftwareVersionsGE() + +void sliceTimingGE_Testx0021x105E(struct TDICOMdata *d, struct TDCMopts opts, struct nifti_1_header *hdr, struct TDCMsort *dcmSort, struct TDICOMdata *dcmList) { + if ((!opts.isTestx0021x105E) || (hdr->dim[3] < 2) || (hdr->dim[4] < 1)) + return; + if (d->rtia_timerGE <= 0.0) { + printMessage("DICOM images do not report RTIA timer(0021,105E)\n"); + return; + } + int j = hdr->dim[3]; + float sliceTiming[kMaxEPI3D]; + float mn = INFINITY; + for (int v = 0; v < hdr->dim[3]; v++) { + sliceTiming[v] = dcmList[dcmSort[v + j].indx].rtia_timerGE; + mn = min(mn, sliceTiming[v]); + } + if (mn < 0.0) + return; + float mxErr = 0.0; + for (int v = 0; v < hdr->dim[3]; v++) { + sliceTiming[v] = (sliceTiming[v] - mn) * 1000.0; //subtract offset, convert sec -> ms + mxErr = max(mxErr, float(fabs(sliceTiming[v] - d->CSA.sliceTiming[v]))); + } + printMessage("Slice Timing Error between calculated and RTIA timer(0021,105E): %gms\n", mxErr); + if ((mxErr < 1.0) && (opts.isVerbose < 1)) + return; + for (int v = 0; v < hdr->dim[3]; v++) + printMessage("\t%g\t%g\n", d->CSA.sliceTiming[v], sliceTiming[v]); +} + +void reportProtocolBlockGE(struct TDICOMdata *d, const char *filename) { +#ifdef myReadGeProtocolBlock + if ((d->manufacturer != kMANUFACTURER_GE) || (d->protocolBlockStartGE < 1) || (d->protocolBlockLengthGE < 19)) + return; + int viewOrderGE = -1; + int sliceOrderGE = -1; + int mbAccel = -1; + int nSlices = -1; + float groupDelay = 0.0; + char ioptGE[3000] = ""; + geProtocolBlock(filename, d->protocolBlockStartGE, d->protocolBlockLengthGE, 2, &sliceOrderGE, &viewOrderGE, &mbAccel, &nSlices, &groupDelay, ioptGE); +#endif +} + +void sliceTimingGE(struct TDICOMdata *d, const char *filename, struct TDCMopts opts, struct nifti_1_header *hdr, struct TDCMsort *dcmSort, struct TDICOMdata *dcmList) { + //we can often read GE slice timing from TriggerTime (0018,1060) or RTIA Timer (0021,105E) + // if both of these methods fail, we can often guess based on slice order stored in the Private Protocol Data Block (0025,101B) + // this is referred to as "rescue" as we only know the TR, not the TA. So assumes continuous scans with no gap + if (d->manufacturer != kMANUFACTURER_GE) + return; + if ((d->is3DAcq) || (d->isLocalizer) || (hdr->dim[4] < 2)) + return; //no need for slice times + if (hdr->dim[3] < 2) + return; + if ((d->protocolBlockStartGE < 128) || (d->protocolBlockLengthGE < 10)) { + d->CSA.sliceTiming[0] = -1; + printWarning("Unable to determine GE Slice timing, no Protocol Data Block GE (0025,101B): %s\n", filename); + return; + } + //start version check: + float geMajorVersion = 0; + int geMajorVersionInt = 0, geMinorVersionInt = 0, geReleaseVersionInt = 0; + char geVersionPrefix[2] = " "; + bool is27r3 = false; + readSoftwareVersionsGE(d->softwareVersions, opts.isVerbose, geVersionPrefix, &geMajorVersion, &geMajorVersionInt, &geMinorVersionInt, &geReleaseVersionInt, &is27r3); + //readSoftwareVersionsGE(&geMajorVersion); + /* + //used for oldSliceTimingGE + if (!opts.isIgnorex0021x105E) { + if ((geMajorVersionInt >= 28) && (d->CSA.sliceTiming[0] >= 0.0)) { + //if (opts.isVerbose > 1) + printMessage("GEversion %.1f, slice timing from DICOM (0021,105E).\n", geMajorVersion); + return; //trust slice timings for versions > 27, see issue 336 + } + }*/ + //end: version check + if (d->maxEchoNumGE > 0) + printWarning("GE sequence with %d echoes. See issue 359\n", d->maxEchoNumGE); + if ((d->protocolBlockStartGE < 1) || (d->protocolBlockLengthGE < 19)) + return; +#ifdef myReadGeProtocolBlock + //GE final desperate attempt to determine slice order + // GE does not provide a good estimate for TA: here we use TR, which will be wrong for sparse designs + // Also, unclear what happens if slice order is flipped + // Therefore, we warning the user that we are guessing + int viewOrderGE = -1; + int sliceOrderGE = -1; + int mbAccel = -1; + int nSlices = -1; + float groupDelay = 0.0; + char ioptGE[3000] = ""; + //printWarning("Using GE Protocol Data Block for BIDS data (beware: new feature)\n"); + int ok = geProtocolBlock(filename, d->protocolBlockStartGE, d->protocolBlockLengthGE, 0, &sliceOrderGE, &viewOrderGE, &mbAccel, &nSlices, &groupDelay, ioptGE); + if (ok != EXIT_SUCCESS) { + d->CSA.sliceTiming[0] = -1; + printWarning("Unable to estimate slice times: issue decoding GE protocol block.\n"); + return; + } + mbAccel = max(mbAccel, 1); + if (nSlices != hdr->dim[3]) //redundant with locationsInAcquisition check? + printWarning("Missing DICOMs, number of slices estimated (%d) differs from Protocol Block (0025,101B) report (%d).\n", hdr->dim[3], nSlices); + d->CSA.multiBandFactor = max(d->CSA.multiBandFactor, mbAccel); + bool isInterleaved = (sliceOrderGE != 0); + groupDelay *= 1000.0; //sec -> ms + // + // Estimate GE Slice Time only for EPI Multi-Phase (epi) or EPI fMRI/BrainWave (epiRT) + // + // BrainWave (epiRT) + if (d->epiVersionGE >= kGE_EPI_PEPOLAR_FWD) + printWarning("GE ABCD pepolar research sequence handling is experimental\n");// + else if ((d->epiVersionGE == 1) || (strstr(ioptGE, "FMRI") != NULL)) { //-1 = not epi, 0 = epi, 1 = epiRT + d->epiVersionGE = 1; + d->internalepiVersionGE = 1; // 'EPI'(gradient echo)/'EPI2'(spin echo) + if (!isSameFloatGE(groupDelay, d->groupDelay)) + printWarning("With epiRT (i.e. FMRI option), Group delay reported in private tag (0043,107C = %g) and Protocol Block (0025,101B = %g) differ.\n", d->groupDelay, groupDelay); + } + // EPI Multi-Phase (epi) + else if ((d->epiVersionGE == 0) || (strstr(ioptGE, "MPh") != NULL)) { //-1 = not epi, 0 = epi, 1 = epiRT + d->epiVersionGE = 0; + d->internalepiVersionGE = 1; // 'EPI'(gradient echo)/'EPI2'(spin echo) + if (groupDelay > 0) { + d->TR += groupDelay; + d->groupDelay = groupDelay; + } + // EPI Multi-Phase (epi) with Variable Delays (Unsupported) + if (groupDelay < -0.5) { + printWarning("SliceTiming Unsupported: GE Multi-Phase EPI with Variable Delays\n"); + d->CSA.sliceTiming[0] = -1; + return; + } + } + // Diffusion (Unsupported) + else if ((d->epiVersionGE == 2) || (d->internalepiVersionGE == 2) || (strstr(ioptGE, "DIFF") != NULL)) { + printWarning("Unable to compute slice times for GE Diffusion\n"); + d->CSA.sliceTiming[0] = -1.0; + return; + } + // Others (Unsupported) + else { + printWarning("Unable to compute slice times for this GE dataset\n"); + d->CSA.sliceTiming[0] = -1.0; + return; + } + if (opts.isVerbose > 1) { + printMessage("GEiopt: %s, groupDelay (%g), internalepiVersionGE (%d), epiVersionGE(%d)\n", ioptGE, groupDelay, d->internalepiVersionGE, d->epiVersionGE); + printMessage("GEversion %s%.1f_R0%d, TRms %g, interleaved %d, multiband %d, groupdelayms %g\n", geVersionPrefix, geMajorVersion, geReleaseVersionInt, d->TR, isInterleaved, d->CSA.multiBandFactor, d->groupDelay); + } + sliceTimeGE(d, d->CSA.multiBandFactor, hdr->dim[3], d->TR, isInterleaved, geMajorVersion, is27r3, d->groupDelay); + sliceTimingGE_Testx0021x105E(d, opts, hdr, dcmSort, dcmList); +#endif +} //sliceTimingGE() + +void reverseSliceTiming(struct TDICOMdata *d, int verbose, int nSL) { + if ((d->CSA.protocolSliceNumber1 == 0) || ((d->CSA.protocolSliceNumber1 == 1))) + return; //slices not flipped + if (d->is3DAcq) + return; //no need for slice times + if (d->CSA.sliceTiming[0] < 0.0) + return; //no slice times + if (nSL > kMaxEPI3D) + return; + if (nSL < 2) + return; + if (verbose) + printMessage("Slices were spatially flipped, so slice times are flipped\n"); + d->CSA.protocolSliceNumber1 = 0; + float sliceTiming[kMaxEPI3D]; + for (int i = 0; i < nSL; i++) + sliceTiming[i] = d->CSA.sliceTiming[i]; + for (int i = 0; i < nSL; i++) + d->CSA.sliceTiming[i] = sliceTiming[(nSL - 1) - i]; +} + +int sliceTimingSiemens2D(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert) { + //only for Siemens 2D images, use acquisitionTime + uint64_t indx0 = dcmSort[0].indx; //first volume + if (!(dcmList[indx0].manufacturer == kMANUFACTURER_SIEMENS)) + return 0; + if (dcmList[indx0].is3DAcq) + return 0; //no need for slice times + if (dcmList[indx0].CSA.sliceTiming[0] >= 0.0) + return 0; //slice times calculated + if (dcmList[indx0].CSA.mosaicSlices > 1) + return 0; + if (nConvert != (hdr->dim[3] * hdr->dim[4])) + return 0; + if (hdr->dim[3] > (kMaxEPI3D - 1)) + return 0; + int nZero = 0; //infer multiband: E11C may not populate kPATModeText + for (int v = 0; v < hdr->dim[3]; v++) { + dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].acquisitionTime; //nb format is HHMMSS we need to handle midnight-crossing and convert to ms, see checkSliceTiming() + if (dcmList[indx0].CSA.sliceTiming[v] == dcmList[indx0].CSA.sliceTiming[0]) + nZero++; + } + if ((dcmList[indx0].CSA.multiBandFactor < 2) && (nZero > 1)) + dcmList[indx0].CSA.multiBandFactor = nZero; + return 1; +} + +void rescueSliceTimingSiemens(struct TDICOMdata *d, int verbose, int nSL, const char *filename) { + if (d->is3DAcq) + return; //no need for slice times + if (d->CSA.multiBandFactor > 1) + return; //pattern of multiband slice order unknown + if (d->CSA.sliceTiming[0] >= 0.0) + return; //slice times calculated + if (d->CSA.mosaicSlices < 2) + return; //20190807 E11C 2D (not mosaic) files do not report mosaicAcqTimes or multi-band factor. + if (nSL < 2) + return; + if ((d->manufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) + return; +#ifdef myReadAsciiCsa + float shimSetting[8]; + char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge]; + TCsaAscii csaAscii; + siemensCsaAscii(filename, &csaAscii, d->CSA.SeriesHeader_offset, d->CSA.SeriesHeader_length, shimSetting, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock); + int ucMode = csaAscii.ucMode; + if ((ucMode < 1) || (ucMode == 3) || (ucMode > 4)) + return; + float trSec = d->TR / 1000.0; + float delaySec = csaAscii.delayTimeInTR / 1000000.0; + float taSec = trSec - delaySec; + float sliceTiming[kMaxEPI3D]; + for (int i = 0; i < nSL; i++) + sliceTiming[i] = i * taSec / nSL * 1000.0; //expected in ms + if (ucMode == 1) //asc + for (int i = 0; i < nSL; i++) + d->CSA.sliceTiming[i] = sliceTiming[i]; + if (ucMode == 2) //desc + for (int i = 0; i < nSL; i++) + d->CSA.sliceTiming[i] = sliceTiming[(nSL - 1) - i]; + if (ucMode == 4) { //int + int oddInc = 0; //for slices 1,3,5 + int evenInc = (nSL + 1) / 2; //for 4 slices 0,1,2,3 we will order [2,0,3,1] for 5 slices [0,3,1,4,2] + if (nSL % 2 == 0) { //Siemens interleaved for acquisitions with odd number of slices https://www.mccauslandcenter.sc.edu/crnl/tools/stc + oddInc = evenInc; + evenInc = 0; + } + for (int i = 0; i < nSL; i++) { + if (i % 2 == 0) { //odd slice 1,3,etc [indexed from 0]! + d->CSA.sliceTiming[i] = sliceTiming[oddInc]; + //printf("%d %d\n", i, oddInc); + oddInc += 1; + } else { //even slice + d->CSA.sliceTiming[i] = sliceTiming[evenInc]; + //printf("%d %d %d\n", i, evenInc, nSL); + evenInc += 1; + } + } + } //if ucMode == 3 int + //dicm2nii provides sSliceArray.ucImageNumb - similar to protocolSliceNumber1 + //if asc_header(s, 'sSliceArray.ucImageNumb'), t = t(nSL:-1:1); end % rev-num +#endif +} + +void sliceTimingUIH(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert) { + uint64_t indx0 = dcmSort[0].indx; //first volume + if (!(dcmList[indx0].manufacturer == kMANUFACTURER_UIH)) + return; + if (nConvert != (hdr->dim[3] * hdr->dim[4])) + return; + if (hdr->dim[3] > (kMaxEPI3D - 1)) + return; + if (hdr->dim[4] < 2) + return; + for (int v = 0; v < hdr->dim[3]; v++) + dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].acquisitionTime; //nb format is HHMMSS we need to handle midnight-crossing and convert to ms, see checkSliceTiming() +} + +/* +void oldSliceTimingGE(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct nifti_1_header * hdr, int verbose, const char * filename, int nConvert) { + //GE check slice timing >>> + uint64_t indx0 = dcmSort[0].indx; //first volume + if (!(dcmList[indx0].manufacturer == kMANUFACTURER_GE)) return; + bool GEsliceTiming_x0018x1060 = false; + if ((hdr->dim[3] < (kMaxEPI3D-1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { + //GE: 1st method for "epi" PSD + //0018x1060 is defined in msec: http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,1060) + //as of 20190704 dcm2niix expects sliceTiming to be encoded in msec for all vendors + GEsliceTiming_x0018x1060 = true; + for (int v = 0; v < hdr->dim[3]; v++) { + if (dcmList[dcmSort[v].indx].CSA.sliceTiming[0] < 0) + GEsliceTiming_x0018x1060 = false; + dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].CSA.sliceTiming[0]; //ms 20190704 + //dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].CSA.sliceTiming[0] / 1000.0; //ms -> sec prior to 20190704 + } + //printMessage(">>>>Reading GE slice timing from 0018,1060\n"); + //0018,1060 provides time at end of acquisition, not start... + if (GEsliceTiming_x0018x1060) { + float minT = dcmList[indx0].CSA.sliceTiming[0]; + float maxT = minT; + for (int v = 0; v < hdr->dim[3]; v++) + if (dcmList[indx0].CSA.sliceTiming[v] < minT) + minT = dcmList[indx0].CSA.sliceTiming[v]; + for (int v = 0; v < hdr->dim[3]; v++) + if (dcmList[indx0].CSA.sliceTiming[v] > maxT) + maxT = dcmList[indx0].CSA.sliceTiming[v]; + for (int v = 0; v < hdr->dim[3]; v++) + dcmList[indx0].CSA.sliceTiming[v] = dcmList[indx0].CSA.sliceTiming[v] - minT; + if (isSameFloatGE(minT, maxT)) { + //ABCD simulated GE DICOMs do not populate 0018,1060 correctly + GEsliceTiming_x0018x1060 = false; + dcmList[indx0].CSA.sliceTiming[0] = -1.0; //no valid slice times + } + } //adjust: first slice is time = 0.0 + } //GE slice timing from 0018,1060 + if ((!GEsliceTiming_x0018x1060) && (hdr->dim[3] < (kMaxEPI3D-1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { + //printMessage(">>>>Reading GE slice timing from epiRT (0018,1060 did not work)\n"); + //GE: 2nd method for "epiRT" PSD + //ignore bogus values of first volume https://neurostars.org/t/getting-missing-ge-information-required-by-bids-for-common-preprocessing/1357/6 + // this necessarily requires at last two volumes, hence dim[4] > 1 + int j = hdr->dim[3]; + //since first volume is bogus, we define the volume start time as the first slice in the second volume + float minTime = dcmList[dcmSort[j].indx].rtia_timerGE; + float maxTime = minTime; + for (int v = 0; v < hdr->dim[3]; v++) { + if (dcmList[dcmSort[v+j].indx].rtia_timerGE < minTime) + minTime = dcmList[dcmSort[v+j].indx].rtia_timerGE; + if (dcmList[dcmSort[v+j].indx].rtia_timerGE > maxTime) + maxTime = dcmList[dcmSort[v+j].indx].rtia_timerGE; + } + //compare all slice times in 2nd volume to start time for this volume + if (maxTime != minTime) { + double scale2Sec = 1.0; + if (dcmList[indx0].TR > 0.0) { //issue 286: determine units for rtia_timerGE + //See https://github.com/rordenlab/dcm2niix/tree/master/GE + // Nikadon's DV24 data stores RTIA Timer as seconds, issue 286 14_LX uses 1/10,000 sec + // The slice timing should always be less than the TR (which is in ms) + // Below we assume 1/10,000 of a sec if slice time is >90% and less than <100% of a TR + // Will not work for sparse designs, but slice timing inappropriate for those datasets + float maxSliceTimeFrac = (maxTime-minTime) / dcmList[indx0].TR; //should be slightly less than 1.0 + if ((maxSliceTimeFrac > 9.0) && (maxSliceTimeFrac < 10)) + scale2Sec = 1.0 / 10000.0; + //printMessage(">> %g %g %g\n", maxSliceTimeFrac, scale2Sec, dcmList[indx0].TR); + } + double scale2ms = scale2Sec * 1000.0; //20190704: convert slice timing values to ms for all vendors + for (int v = 0; v < hdr->dim[3]; v++) + dcmList[indx0].CSA.sliceTiming[v] = (dcmList[dcmSort[v+j].indx].rtia_timerGE - minTime) * scale2ms; + dcmList[indx0].CSA.sliceTiming[hdr->dim[3]] = -1; + //detect multi-band + int nZero = 0; + for (int v = 0; v < hdr->dim[3]; v++) + if (isSameFloatGE(dcmList[indx0].CSA.sliceTiming[hdr->dim[3]], 0.0)) + nZero ++; + if ((nZero > 1) && (nZero < hdr->dim[3]) && ((hdr->dim[3] % nZero) == 0)) + dcmList[indx0].CSA.multiBandFactor = nZero; + //report times + if (verbose > 0) { + printMessage("GE slice timing (sec)\n"); + printMessage("\tTime\tX\tY\tZ\tInstance\n"); + for (int v = 0; v < hdr->dim[3]; v++) { + if (v == (hdr->dim[3]-1)) + printMessage("...\n"); + if ((v < 4) || (v == (hdr->dim[3]-1))) + printMessage("\t%g\t%g\t%g\t%g\t%d\n", dcmList[indx0].CSA.sliceTiming[v] / 1000.0, dcmList[dcmSort[v+j].indx].patientPosition[1], dcmList[dcmSort[v+j].indx].patientPosition[2], dcmList[dcmSort[v+j].indx].patientPosition[3], dcmList[dcmSort[v+j].indx].imageNum); + } //for v + } //verbose > 1 + } //if maxTime != minTIme + } //GE slice timing from 0021,105E +} //oldSliceTimingGE() +*/ + +int sliceTimingCore(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert, struct TDCMopts opts) { + int sliceDir = 0; + if (hdr->dim[3] < 2) + return sliceDir; + //uint64_t indx0 = dcmSort[0].indx; + //uint64_t indx1 = dcmSort[1].indx; + struct TDICOMdata *d0 = &dcmList[dcmSort[0].indx]; + uint64_t indx1 = dcmSort[0].indx; + if (nConvert > 1) //use 2nd volume as CMRR bug can create bogus slice timing in first volume + indx1 = dcmSort[1].indx; + struct TDICOMdata *d1 = &dcmList[indx1]; + //oldSliceTimingGE(dcmSort, dcmList, hdr, verbose, filename, nConvert); + sliceTimingUIH(dcmSort, dcmList, hdr, verbose, filename, nConvert); + int isSliceTimeHHMMSS = sliceTimingSiemens2D(dcmSort, dcmList, hdr, verbose, filename, nConvert); + sliceTimingXA(dcmSort, dcmList, hdr, verbose, filename, nConvert); + checkSliceTiming(d0, d1, verbose, isSliceTimeHHMMSS); + rescueSliceTimingSiemens(d0, verbose, hdr->dim[3], filename); //desperate attempts if conventional methods fail + if (hdr->dim[3] > 1) + sliceDir = headerDcm2Nii2(dcmList[dcmSort[0].indx], dcmList[indx1], hdr, true); + //UNCOMMENT NEXT TWO LINES TO RE-ORDER MOSAIC WHERE CSA's protocolSliceNumber does not start with 1 + if (dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1 > 1) { + printWarning("Weird CSA 'ProtocolSliceNumber' (System/Miscellaneous/ImageNumbering reversed): VALIDATE SLICETIMING AND BVECS\n"); + //https://www.healthcare.siemens.com/siemens_hwem-hwem_ssxa_websites-context-root/wcm/idc/groups/public/@global/@imaging/@mri/documents/download/mdaz/nzmy/~edisp/mri_60_graessner-01646277.pdf + //see https://github.com/neurolabusc/dcm2niix/issues/40 + sliceDir = -1; //not sure how to handle negative determinants? + } + if (sliceDir < 0) { + if ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UIH) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE)) + dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1 = -1; + } + sliceTimingGE(d0, filename, opts, hdr, dcmSort, dcmList); + //ensure slice times have variability + reverseSliceTiming(d0, verbose, hdr->dim[3]); + bool allSame = true; + for (int i = 0; i < hdr->dim[3]; i++) + if (!isSameFloatGE(d0->CSA.sliceTiming[i], d0->CSA.sliceTiming[0])) + allSame = false; + if (allSame) + d0->CSA.sliceTiming[0] = -1.0; + return sliceDir; +} //sliceTiming() + +void loadOverlay(char *imgname, unsigned char *img, int offset, int x, int y, int z) { + int nvox = x * y * z; + size_t imgszRead = (nvox + 7) >> 3; //overlay stored as 1 bit per voxel + FILE *file = fopen(imgname, "rb"); + if (!file) { + printError("Unable to open '%s'\n", imgname); + return; + } + fseek(file, 0, SEEK_END); + long fileLen = ftell(file); + if (fileLen < (imgszRead + offset)) { + printWarning("File not large enough to store overlay: %s\n", imgname); + return; + } + fseek(file, (long)offset, SEEK_SET); + unsigned char *bImg = (unsigned char *)malloc(imgszRead); + size_t sz = fread(bImg, 1, imgszRead, file); + //static unsigned char mask[] = {128, 64, 32, 16, 8, 4, 2, 1}; + static unsigned char mask[] = {1, 2, 4, 8, 16, 32, 64, 128}; + for (int i = 0; i < nvox; i++) { + int byt = (i >> 3); + int bit = (i % 8); + img[i] = ((bImg[byt] & mask[bit]) != 0); + } + free(bImg); + fclose(file); + return; +} //loadOverlay() + +int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D, int segVol) { +#ifdef MGH_FREESURFER + double seriesNum = (double) dcmList[dcmSort[0].indx].seriesUidCrc; + int segVolEcho = segVol; + if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) + segVolEcho = dcmList[dcmSort[0].indx].echoNum + 1; + if (segVolEcho > 0) + seriesNum = seriesNum + ((double)segVolEcho - 1.0) / 10.0; + + if (!isSameDouble(opts.seriesNumber[0], seriesNum)) + return EXIT_SUCCESS; +#endif + + bool iVaries = intensityScaleVaries(nConvert, dcmSort, dcmList); + float *sliceMMarray = NULL; //only used if slices are not equidistant + uint64_t indx = dcmSort[0].indx; + uint64_t indx0 = dcmSort[0].indx; + uint64_t indx1 = indx0; + if (nConvert > 1) + indx1 = dcmSort[1].indx; + uint64_t indxEnd = dcmSort[nConvert - 1].indx; + dti4D->repetitionTimeInversion = 0.0; //only set for Siemens and GE 3D T1 "TR" + dti4D->repetitionTimeExcitation = 0.0; //only set for Philips 3D T1 "TR" +#ifdef newTilt //see issue 254 + if (((nConvert > 1) || (dcmList[indx0].xyzDim[3] > 1)) && ((dcmList[indx0].modality == kMODALITY_CT) || (dcmList[indx0].isXRay) || (dcmList[indx0].gantryTilt > 0.0))) { //issue372: enhanced DICOMs can also have gantry tilt + dcmList[indx0].gantryTilt = computeGantryTiltPrecise(dcmList[indx0], dcmList[indxEnd], opts.isVerbose); + if (isnan(dcmList[indx0].gantryTilt)) + return EXIT_FAILURE; + } +#endif //newTilt see issue 254 + if (dcmList[indx0].isPrivateCreatorRemap) + printWarning("PrivateCreator remapping detected. DICOMs are not archival quality (issue 435).\n"); + if (dcmList[indx0].isScaleVariesEnh) //issue363 + iVaries = true; + if ((dcmList[indx].isXA10A) && (dcmList[indx].CSA.mosaicSlices < 0)) { + printMessage("Siemens XA10 Mosaics are not primary images and lack vital data.\n"); + printMessage(" See https://github.com/rordenlab/dcm2niix/issues/236\n"); +#ifdef mySaveXA10Mosaics + int n; + printMessage("INPUT REQUIRED FOR %s\n", dcmList[indx].imageBaseName); + printMessage("PLEASE ENTER NUMBER OF SLICES IN MOSAIC:\n"); + scanf("%d", &n); + for (int i = 0; i < nConvert; i++) + dcmList[dcmSort[i].indx].CSA.mosaicSlices = n; +#endif + } + if (opts.isIgnoreDerivedAnd2D && dcmList[indx].isDerived) { + printMessage("Ignoring derived image(s) of series %ld %s\n", dcmList[indx].seriesNum, nameList->str[indx]); + return EXIT_SUCCESS; + } + if ((opts.isIgnoreDerivedAnd2D) && ((dcmList[indx].isLocalizer) || (strcmp(dcmList[indx].sequenceName, "_tfl2d1") == 0) || (strcmp(dcmList[indx].sequenceName, "_fl3d1_ns") == 0) || (strcmp(dcmList[indx].sequenceName, "_fl2d1") == 0))) { + printMessage("Ignoring localizer (sequence '%s') of series %ld %s\n", dcmList[indx].sequenceName, dcmList[indx].seriesNum, nameList->str[indx]); + return EXIT_SUCCESS; + } + if ((opts.isIgnoreDerivedAnd2D) && (nConvert < 2) && (dcmList[indx].CSA.mosaicSlices < 2) && (dcmList[indx].xyzDim[3] < 2)) { + printMessage("Ignoring 2D image of series %ld %s\n", dcmList[indx].seriesNum, nameList->str[indx]); + return EXIT_SUCCESS; + } + if (dcmList[indx].manufacturer == kMANUFACTURER_UNKNOWN) + printWarning("Unable to determine manufacturer (0008,0070), so conversion is not tuned for vendor.\n"); +#ifdef myForce3DPhaseRealImaginary //compiler option: segment each phase/real/imaginary map + bool saveAs3D = dcmList[indx].isHasPhase || dcmList[indx].isHasReal || dcmList[indx].isHasImaginary; +#else + bool saveAs3D = false; +#endif + +#ifdef MGH_FREESURFER + mrifsStruct.tdicomData = dcmList[indx]; // first in sorted list dcmSort +#endif + + struct nifti_1_header hdr0; + unsigned char *img = nii_loadImgXL(nameList->str[indx], &hdr0, dcmList[indx], iVaries, opts.compressFlag, opts.isVerbose, dti4D); + if (strlen(opts.imageComments) > 0) { + for (int i = 0; i < 24; i++) + hdr0.aux_file[i] = 0; //remove dcm.imageComments + snprintf(hdr0.aux_file, 24, "%s", opts.imageComments); + } + if (opts.isVerbose) + printMessage("Converting %s\n", nameList->str[indx]); + if (img == NULL) + return EXIT_FAILURE; + size_t imgsz = nii_ImgBytes(hdr0); + unsigned char *imgM = (unsigned char *)malloc(imgsz * (uint64_t)nConvert); + memcpy(&imgM[0], &img[0], imgsz); + free(img); + +#ifdef MGH_FREESURFER + printMessage("load Image %s, size %ld\n", nameList->str[indx], imgsz); +#endif + + //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); + bool isHasOverlay = dcmList[indx0].isHasOverlay; + if (nConvert > 1) { + //next: detect trigger time see example https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer + double triggerDx = dcmList[dcmSort[nConvert - 1].indx].triggerDelayTime - dcmList[indx0].triggerDelayTime; + if ((triggerDx > 0.0) && (dcmList[indx0].aslFlags == kASL_FLAG_NONE)) //issue 384, issue533 + dcmList[indx0].triggerDelayTime = triggerDx; + //next: determine gantry tilt + if (dcmList[indx0].gantryTilt != 0.0f) + printWarning("Note these images have gantry tilt of %g degrees (manufacturer ID = %d)\n", dcmList[indx0].gantryTilt, dcmList[indx0].manufacturer); + if (hdr0.dim[3] < 2) { + //stack volumes with multiple acquisitions + int nAcq = 1; + //Next line works in theory, but fails with Siemens CT that saves pairs of slices as acquisitions, see example "testSiemensStackAcq" + // nAcq = 1+abs( dcmList[dcmSort[nConvert-1].indx].acquNum-dcmList[indx0].acquNum); + //therefore, the 'same position' is the most robust solution in the real world. + if ((dcmList[indx0].manufacturer == kMANUFACTURER_SIEMENS) && (isSameFloat(dcmList[indx0].TR, 0.0f))) { + nConvert = siemensCtKludge(nConvert, dcmSort, dcmList); + } + //resolve GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081 + // e.g. T1 scans can be interpolated in the slice direction, so choose large number + // EPI can report total number of slices (dim3*dim4), so choose smaller number + if ((nAcq == 1) && (dcmList[indx0].locationsInAcquisitionConflict > 0) && ((nConvert % dcmList[indx0].locationsInAcquisitionConflict) == 0)) { + //printMessage(" >>>%d %d %d %d\n", nAcq, nConvert, dcmList[indx0].locationsInAcquisitionConflict, dcmList[indx0].locationsInAcquisition); + nAcq = 0; + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx])) + nAcq++; + int nAcqConflict = nConvert / dcmList[indx0].locationsInAcquisitionConflict; + if (nAcq == nAcqConflict) { + printMessage("Resolved discrepancy between tags (0020,1002; 0021,104F; 0054,0081)\n"); + dcmList[indx0].locationsInAcquisition = dcmList[indx0].locationsInAcquisitionConflict; + } + } + if ((nConvert > 1) && (nAcq == 1) && (dcmList[indx0].locationsInAcquisition > 0)) { + if ((nConvert % dcmList[indx0].locationsInAcquisition) == 0) + nAcq = nConvert / dcmList[indx0].locationsInAcquisition; + else + printMessage("DICOM images may be missing, expected %d spatial locations per volume, but found %d slices.\n", dcmList[indx0].locationsInAcquisition, nConvert); + } + if (nAcq < 2) { + nAcq = 0; + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx])) + nAcq++; + } + if ((nAcq > 1) && ((nConvert / nAcq) > 1) && ((nConvert % nAcq) == 0)) { + hdr0.dim[3] = nConvert / nAcq; + hdr0.dim[4] = nAcq; + hdr0.dim[0] = 4; + if ((dcmList[indx0].locationsInAcquisition > 0) && (dcmList[indx0].locationsInAcquisition != hdr0.dim[3])) + printMessage("DICOM images may be missing, expected %d spatial locations per volume, but found %d.\n", dcmList[indx0].locationsInAcquisition, hdr0.dim[3]); + } else if ((dcmList[indx0].isXA10A) && (nConvert > nAcq) && (nAcq > 1)) { + nAcq -= 1; + hdr0.dim[3] = nConvert / nAcq; + hdr0.dim[4] = nAcq; + hdr0.dim[0] = 4; + if ((nAcq > 1) && (nConvert != nAcq)) { + printMessage("Slice positions repeated, but number of slices (%d) not divisible by number of repeats (%d): converting only complete volumes.\n", nConvert, nAcq); + } + } else { + hdr0.dim[3] = nConvert; + if ((nAcq > 1) && (nConvert != nAcq)) { + printMessage("Slice positions repeated, but number of slices (%d) not divisible by number of repeats (%d): missing images?\n", nConvert, nAcq); + if (dcmList[indx0].locationsInAcquisition > 0) + printMessage("Hint: expected %d locations\n", dcmList[indx0].locationsInAcquisition); + } + } + //next options removed: features now thoroughly detected in nii_loadDir() + for (int i = 0; i < nConvert; i++) { //make sure 1st volume describes shared features + if (dcmList[dcmSort[i].indx].isHasOverlay) + isHasOverlay = true; + if (dcmList[dcmSort[i].indx].isCoilVaries) + dcmList[indx0].isCoilVaries = true; + if (dcmList[dcmSort[i].indx].isMultiEcho) + dcmList[indx0].isMultiEcho = true; + if (dcmList[dcmSort[i].indx].isNonParallelSlices) + dcmList[indx0].isNonParallelSlices = true; + if (dcmList[dcmSort[i].indx].isHasPhase) + dcmList[indx0].isHasPhase = true; + if (dcmList[dcmSort[i].indx].isHasReal) + dcmList[indx0].isHasReal = true; + if (dcmList[dcmSort[i].indx].isHasImaginary) + dcmList[indx0].isHasImaginary = true; + } + //next: detect variable inter-volume time https://github.com/rordenlab/dcm2niix/issues/184 + //if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT)|| (opts.isForceOnsetTimes))) { + if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT) || ((opts.isForceOnsetTimes) && (dcmList[indx0].manufacturer != kMANUFACTURER_GE)))) { + if (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS) { + ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList, opts.isVerbose); //issue529 + indx0 = dcmSort[0].indx; + } + //printf("Bogo529\n"); return EXIT_SUCCESS; + //note: GE 0008,0032 unreliable, see mb=6 data from sw27.0 20201026 + //issue 407 + int nTR = 0; + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { + dti4D->frameDuration[nTR] = dcmList[dcmSort[i].indx].frameDuration; + nTR += 1; + if (nTR >= kMaxDTI4D) + break; + } + bool trVaries = false; + bool dayVaries = false; + float tr = -1.0; + float mintr = 1000000; + float maxtr = -1.0; + float volumeTimeStartFirstStartLast = -1.0; + int nVol = 0; + uint64_t prevVolIndx = indx0; + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { + float trDiff = acquisitionTimeDifference(&dcmList[prevVolIndx], &dcmList[dcmSort[i].indx]); + prevVolIndx = dcmSort[i].indx; + nVol++; + if (trDiff <= 0) + continue; + mintr = min(mintr, trDiff); + maxtr = max(maxtr, trDiff); + if (tr < 0) + tr = trDiff; + if (trDiff < 0) + dayVaries = true; + float trDiff0 = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); + volumeTimeStartFirstStartLast = max(volumeTimeStartFirstStartLast, trDiff0); + } + float toleranceSec = 50.0 / 1000.0; //e.g. 50/1000 = 50ms + if ((nVol > 1) && (volumeTimeStartFirstStartLast > 0.0)) { + tr = volumeTimeStartFirstStartLast / (nVol - 1.0); + if (fabs(tr - hdr0.pixdim[4]) > toleranceSec) { + if ((dcmList[indx0].isIR) && (dcmList[indx0].manufacturer != kMANUFACTURER_PHILIPS)) + dti4D->repetitionTimeInversion = hdr0.pixdim[4]; + else + dti4D->repetitionTimeExcitation = hdr0.pixdim[4]; + hdr0.pixdim[4] = tr; + dcmList[indx0].TR = tr * 1000.0; //as msec + } + } + if (dcmList[indx0].aslFlags != kASL_FLAG_NONE) { //issue533 + int nTR = 0; + for (int i = 0; i < nConvert; i++) { + if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { + dti4D->triggerDelayTime[nTR] = dcmList[dcmSort[i].indx].triggerDelayTime; + nTR += 1; + if (nTR >= kMaxDTI4D) + break; + } + } //for each volume + } //if ASL + if ((maxtr - mintr) > toleranceSec) + trVaries = true; + if (trVaries) { + if (dayVaries) + printWarning("Seconds between volumes varies (perhaps run through midnight)\n"); + else + printWarning("Seconds between volumes varies\n"); + //issue 407 + int nTR = 0; + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { + float trDiff = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); + dti4D->volumeOnsetTime[nTR] = trDiff; + dti4D->decayFactor[nTR] = dcmList[dcmSort[i].indx].decayFactor; + nTR += 1; + if (nTR >= kMaxDTI4D) + break; + } + if (dcmList[indx0].modality != kMODALITY_PT) + dti4D->decayFactor[0] = -1.0; //only for PET + else + hdr0.pixdim[4] = 0.0; + // saveAs3D = true; + // printWarning("Creating independent volumes as time between volumes varies\n"); + if (opts.isVerbose) { + printMessage(" OnsetTime = ["); + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { + float trDiff = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); + printMessage(" %g", trDiff); + } + printMessage(" ]\n"); + } + } //if trVaries + } //if PET + //next: detect variable inter-slice distance + float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); +#ifdef myInstanceNumberOrderIsNotSpatial + if (!isSameFloat(dx, 0.0)) //only for XYZT, not TXYZ: perhaps run for swapDim3Dim4? Extremely rare anomaly + if (!ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList, opts.isVerbose)) + dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + indx0 = dcmSort[0].indx; + if (nConvert > 1) + indx1 = dcmSort[1].indx; +#endif + bool dxVaries = false; + for (int i = 1; i < nConvert; i++) + if (!isSameFloatT(dx, intersliceDistance(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]), 0.2)) + dxVaries = true; + if (hdr0.dim[4] < 2) { + if (dxVaries) { + sliceMMarray = (float *)malloc(sizeof(float) * nConvert); + sliceMMarray[0] = 0.0f; + for (int i = 1; i < nConvert; i++) + sliceMMarray[i] = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); + printWarning("Interslice distance varies in this volume (incompatible with NIfTI format).\n"); + if (opts.isVerbose) { + printMessage("Dimensions %d %d %d %d nAcq %d nConvert %d\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], nAcq, nConvert); + printMessage(" Distance from first slice:\n"); + printMessage("dx=[0"); + for (int i = 1; i < nConvert; i++) + printMessage(" %g", sliceMMarray[i]); + printMessage("]\n"); + } +#ifndef myInstanceNumberOrderIsNotSpatial + //kludge to handle single volume without instance numbers (0020,0013), e.g. https://www.morphosource.org/Detail/MediaDetail/Show/media_id/8430 + bool isInconsistenSliceDir = false; + int slicePositionRepeats = 1; //how many times is first position repeated + if (nConvert > 2) { + float dxPrev = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; + for (int i = 2; i < nConvert; i++) { + float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); + if (dx < dxPrev) + isInconsistenSliceDir = true; + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; + dxPrev = dx; + } + } + if ((isInconsistenSliceDir) && (slicePositionRepeats == 1)) { + //printWarning("Slice order as defined by instance number not spatially sequential.\n"); + //printWarning("Attempting to reorder slices based on spatial position.\n"); + ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList, opts.isVerbose); + dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + hdr0.pixdim[3] = dx; + isInconsistenSliceDir = false; + //code below duplicates prior code, could be written as modular function(s) + indx0 = dcmSort[0].indx; + if (nConvert > 1) + indx1 = dcmSort[1].indx; + dxVaries = false; + dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + for (int i = 1; i < nConvert; i++) + if (!isSameFloatT(dx, intersliceDistance(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]), 0.2)) + dxVaries = true; + for (int i = 1; i < nConvert; i++) + sliceMMarray[i] = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); + //printf("dx=["); + //for (int i = 1; i < nConvert; i++) + // printf("%g ", intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]) ); + //printf("\n"); + bool isInconsistenSliceDir = false; + int slicePositionRepeats = 1; //how many times is first position repeated + if (nConvert > 2) { + float dxPrev = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; + for (int i = 2; i < nConvert; i++) { + float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); + if (dx < dxPrev) + isInconsistenSliceDir = true; + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; + dxPrev = dx; + } + } + if (!dxVaries) { + printMessage("Slice re-ordering resolved inter-slice distance variability.\n"); + free(sliceMMarray); + sliceMMarray = NULL; + } + } + if (isInconsistenSliceDir) { + printMessage("Unable to equalize slice distances: slice order not consistently ascending.\n"); + printMessage("First spatial position repeated %d times\n", slicePositionRepeats); + printError(" Recompiling with '-DmyInstanceNumberOrderIsNotSpatial' might help.\n"); + return EXIT_FAILURE; + } +#endif + int imageNumRange = 1 + abs(dcmList[dcmSort[nConvert - 1].indx].imageNum - dcmList[dcmSort[0].indx].imageNum); + if ((imageNumRange > 1) && (imageNumRange != nConvert)) { + if ((dcmList[dcmSort[0].indx].locationsInAcquisition > 0) && ((nConvert % dcmList[dcmSort[0].indx].locationsInAcquisition) != 0)) + printError("Missing images. Found %d images, expected %d slices per volume and instance number (0020,0013) ranges from %d to %d\n", nConvert, dcmList[dcmSort[0].indx].locationsInAcquisition, dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[nConvert - 1].indx].imageNum); + else + printWarning("Missing images? Expected %d images, but instance number (0020,0013) ranges from %d to %d\n", nConvert, dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[nConvert - 1].indx].imageNum); + if (opts.isVerbose) { + printMessage("instance=["); + for (int i = 0; i < nConvert; i++) { + printMessage(" %d", dcmList[dcmSort[i].indx].imageNum); + } + printMessage("]\n"); + } + } //imageNum not sequential + } //dx varies + } //not 4D + if ((hdr0.dim[4] > 0) && (dxVaries) && (dx == 0.0) && ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UNKNOWN) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS))) { //Niels Janssen has provided GE sequential multi-phase acquisitions that also require swizzling + swapDim3Dim4(hdr0.dim[3], hdr0.dim[4], dcmSort); + dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + if (opts.isVerbose) + printMessage("Swizzling 3rd and 4th dimensions (XYTZ -> XYZT), assuming interslice distance is %f\n", dx); + } + if ((dx == 0.0) && (!dxVaries)) { //all images are the same slice - 16 Dec 2014 + printWarning("All images appear to be a single slice - please check slice/vector orientation\n"); + hdr0.dim[3] = 1; + hdr0.dim[4] = nConvert; + hdr0.dim[0] = 4; + } + if ((dx > 0) && (!isSameFloatGE(dx, hdr0.pixdim[3]))) + hdr0.pixdim[3] = dx; + dcmList[dcmSort[0].indx].xyzMM[3] = dx; //16Sept2014 : correct DICOM for true distance between slice centers: + // e.g. MCBI Siemens ToF 0018:0088 reports 16mm SpacingBetweenSlices, but actually 0.5mm + } else if (hdr0.dim[4] < 2) { + hdr0.dim[4] = nConvert; + hdr0.dim[0] = 4; + } else { + hdr0.dim[5] = nConvert; + hdr0.dim[0] = 5; + } + if (((dcmList[indx0].manufacturer == kMANUFACTURER_TOSHIBA) || (dcmList[indx0].manufacturer == kMANUFACTURER_CANON)) && (dcmList[indx0].CSA.numDti < 1) && (nConvert > 1) && (hdr0.dim[4] > 1)) { + //TOSHIBA omits 0018,9087 from B=0 volumes in diffusion series. Since most diffusion series start with B=0 volume, this would cause us to detect a diffusion series + for (int i = 1; i < nConvert; i++) + if (dcmList[dcmSort[i].indx].CSA.numDti > 0) + dcmList[indx0].CSA.numDti = 1; + } + //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); + struct nifti_1_header hdrI; + //double time = -1.0; + if ((!opts.isOnlyBIDS) && (nConvert > 1)) { + //for (int i = 0; i < nConvert; i++) + // printMessage("%d\t%s\n", i, nameList->str[indx]); + //int iStart = 1; + //if (isReorder) iStart = 0; + //for (int i = 1; i < nConvert; i++) { //<- works except where ensureSequentialSlicePositions() changes 1st slice + for (int i = 0; i < nConvert; i++) { //stack additional images + indx = dcmSort[i].indx; + //double time2 = dcmList[dcmSort[i].indx].acquisitionTime; + //if (time != time2) + // printWarning("%g\n", time2); + //time = time2; + //if (headerDcm2Nii(dcmList[indx], &hdrI) == EXIT_FAILURE) return EXIT_FAILURE; + img = nii_loadImgXL(nameList->str[indx], &hdrI, dcmList[indx], iVaries, opts.compressFlag, opts.isVerbose, dti4D); + if (img == NULL) + return EXIT_FAILURE; + if ((hdr0.dim[1] != hdrI.dim[1]) || (hdr0.dim[2] != hdrI.dim[2]) || (hdr0.bitpix != hdrI.bitpix)) { + printError("Image dimensions differ %s %s", nameList->str[dcmSort[0].indx], nameList->str[indx]); + free(imgM); + free(img); + return EXIT_FAILURE; + } + memcpy(&imgM[(uint64_t)i * imgsz], &img[0], imgsz); + free(img); + +#ifdef MGH_FREESURFER + if (opts.isVerbose) + printMessage("load Image #%d %s, size %ld\n", i, nameList->str[indx], imgsz); +#endif + } + } //skip if we are only creating BIDS + if (hdr0.dim[4] > 1) //for 4d datasets, last volume should be acquired before first + checkDateTimeOrder(&dcmList[dcmSort[0].indx], &dcmList[dcmSort[nConvert - 1].indx]); + } // end of if (nConvert > 1) + if (opts.isVerbose > 1) + reportProtocolBlockGE(&dcmList[indx0], nameList->str[dcmSort[0].indx]); + int sliceDir = sliceTimingCore(dcmSort, dcmList, &hdr0, opts.isVerbose, nameList->str[dcmSort[0].indx], nConvert, opts); +#ifdef myReportSliceFilenames + if (sliceDir < 0) { + for (int i = nConvert; i > 0; --i) + printMessage("|%d|%s\n", i, nameList->str[dcmSort[i - 1].indx]); + } else { + for (int i = 0; i < nConvert; i++) + printMessage("|%d|%s\n", i, nameList->str[dcmSort[i].indx]); + } +#endif + if (strlen(dcmList[dcmSort[0].indx].protocolName) < 1) //beware: tProtocolName can vary within a series "t1+AF8-mpr+AF8-ns+AF8-sag+AF8-p2+AF8-iso" vs "T1_mprage_ns_sag_p2_iso 1.0mm_192" + rescueProtocolName(&dcmList[dcmSort[0].indx], nameList->str[dcmSort[0].indx]); + //move before headerDcm2Nii2 checkSliceTiming(&dcmList[indx0], &dcmList[indx1]); + char pathoutname[2048] = {""}; + if (nii_createFilename(dcmList[dcmSort[0].indx], pathoutname, opts) == EXIT_FAILURE) { //make sure call is subsequent to rescueProtocolName() + free(imgM); + return EXIT_FAILURE; + } + if (strlen(pathoutname) < 1) { + free(imgM); + return EXIT_FAILURE; + } + // skip converting if user has specified one or more series, but has not specified this one + if (opts.numSeries > 0) { //issue453: moved to before saveBIDS + int i = 0; + double seriesNum = (double)dcmList[dcmSort[0].indx].seriesUidCrc; + int segVolEcho = segVol; + if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) + segVolEcho = dcmList[dcmSort[0].indx].echoNum + 1; + if (segVolEcho > 0) + seriesNum = seriesNum + ((double)segVolEcho - 1.0) / 10.0; + for (; i < opts.numSeries; i++) { + if (isSameDouble(opts.seriesNumber[i], seriesNum)) + break; + } + if (i == opts.numSeries) + return EXIT_SUCCESS; + } + if (opts.numSeries >= 0) //issue453 + nii_SaveBIDSX(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[dcmSort[0].indx], dti4D); + if (opts.isOnlyBIDS) { + //note we waste time loading every image, however this ensures hdr0 matches actual output +#ifndef MGH_FREESURFER + free(imgM); +#endif + return EXIT_SUCCESS; + } + if ((segVol >= 0) && (hdr0.dim[4] > 1)) { + int inVol = hdr0.dim[4]; + int nVol = 0; + for (int v = 0; v < inVol; v++) + if (dti4D->gradDynVol[v] == segVol) + nVol++; + if (nVol < 1) { + printError("Series %d does not exist\n", segVol); + return EXIT_FAILURE; + } + size_t imgsz4D = imgsz; + if (nVol < 2) + hdr0.dim[0] = 3; //3D + hdr0.dim[4] = 1; + size_t imgsz3D = nii_ImgBytes(hdr0); + unsigned char *img4D = (unsigned char *)malloc(imgsz4D); + memcpy(&img4D[0], &imgM[0], imgsz4D); + free(imgM); + imgM = (unsigned char *)malloc(imgsz3D * nVol); + int outVol = 0; + for (int v = 0; v < inVol; v++) { + if ((dti4D->gradDynVol[v] == segVol) && (outVol < nVol)) { + memcpy(&imgM[outVol * imgsz3D], &img4D[v * imgsz3D], imgsz3D); + outVol++; + } + } + hdr0.dim[4] = nVol; + imgsz = nii_ImgBytes(hdr0); + free(img4D); + saveAs3D = false; + } + // Prevent these DICOM files from being reused. + for (int i = 0; i < nConvert; ++i) + dcmList[dcmSort[i].indx].converted2NII = 1; + if (opts.numSeries < 0) { //report series number but do not convert + int segVolEcho = segVol; + if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) + segVolEcho = dcmList[dcmSort[0].indx].echoNum + 1; + if (segVolEcho >= 0) { + printMessage("\t%u.%d\t%s\n", dcmList[dcmSort[0].indx].seriesUidCrc, segVolEcho - 1, pathoutname); + //printMessage("\t%ld.%d\t%s\n", dcmList[dcmSort[0].indx].seriesNum, segVol-1, pathoutname); + } else { + printMessage("\t%u\t%s\n", dcmList[dcmSort[0].indx].seriesUidCrc, pathoutname); + //printMessage("\t%ld\t%s\n", dcmList[dcmSort[0].indx].seriesNum, pathoutname); + } + printMessage(" %s\n", nameList->str[dcmSort[0].indx]); + return EXIT_SUCCESS; + } + struct nifti_1_header hdrrx = hdr0; + bool isFlipZ = false; + if (sliceDir < 0) { +#ifdef MGH_FREESURFER // freesurfer fix dcm/261000-10-6?.dcm + printMessage("***MGH_FREESURFER***: skip nii_flipZ() when sliceDir < 0 (%s:%s:%d)\n", __FILE__, __func__, __LINE__); +#else + isFlipZ = true; + imgM = nii_flipZ(imgM, &hdr0); + sliceDir = abs(sliceDir); //change this, we have flipped the image so GE DTI bvecs no longer need to be flipped! +#endif + } + + nii_saveText(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[indx]); + int numADC = 0; + int *volOrderIndex = nii_saveDTI(pathoutname, nConvert, dcmSort, dcmList, opts, sliceDir, dti4D, &numADC, hdr0.dim[4]); + PhilipsPrecise(&dcmList[dcmSort[0].indx], opts.isPhilipsFloatNotDisplayScaling, &hdr0, opts.isVerbose); + if ((dcmList[dcmSort[0].indx].bitsStored == 12) && (dcmList[dcmSort[0].indx].bitsAllocated == 16)) + nii_mask12bit(imgM, &hdr0); + if ((opts.saveFormat == kSaveFormatMGH) && (hdr0.datatype == DT_UINT16)) + imgM = nii_uint16toFloat32(imgM, &hdr0, opts.isVerbose); + if ((opts.isMaximize16BitRange == kMaximize16BitRange_True) && (hdr0.datatype == DT_INT16)) { + nii_scale16bitSigned(imgM, &hdr0, opts.isVerbose); //allow INT16 to use full dynamic range + } else if ((opts.isMaximize16BitRange == kMaximize16BitRange_True) && (hdr0.datatype == DT_UINT16) && (!dcmList[dcmSort[0].indx].isSigned)) { + nii_scale16bitUnsigned(imgM, &hdr0, opts.isVerbose); //allow UINT16 to use full dynamic range + } else if ((opts.isMaximize16BitRange == kMaximize16BitRange_False) && (hdr0.datatype == DT_UINT16) && (!dcmList[dcmSort[0].indx].isSigned)) + nii_check16bitUnsigned(imgM, &hdr0, opts.isVerbose); //save UINT16 as INT16 if we can do this losslessly + if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert < 2)) + printWarning("Siemens XA DICOM inadequate for robust conversion (issue 236)\n"); + if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1)) + printWarning("Siemens XA exported as classic not enhanced DICOM (issue 236)\n"); +#ifndef MGH_FREESURFER + printMessage("Convert %d DICOM as %s (%dx%dx%dx%d)\n", nConvert, pathoutname, hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4]); +#else + printMessage( "Convert %d DICOM (%dx%dx%dx%d)\n", nConvert, hdr0.dim[1],hdr0.dim[2],hdr0.dim[3],hdr0.dim[4]); +#endif +#ifndef USING_R + fflush(stdout); //show immediately if run from MRIcroGL GUI +#endif + //~ if (!dcmList[dcmSort[0].indx].isSlicesSpatiallySequentialPhilips) + //~ nii_reorderSlices(imgM, &hdr0, dti4D); + //hdr0.pixdim[3] = dxNoTilt; + if (hdr0.dim[3] < 2) + printWarning("Check that 2D images are not mirrored.\n"); +#ifndef USING_R + else + fflush(stdout); //GUI buffers printf, display all results +#endif + //3D-EPI vs 3D SPACE/MPRAGE/ETC + bool isFlipY = false; + bool isSetOrtho = false; + if ((opts.isRotate3DAcq) && (dcmList[dcmSort[0].indx].is3DAcq) && (!dcmList[dcmSort[0].indx].isEPI) && (hdr0.dim[3] > 1) && (hdr0.dim[0] < 4)) { + // freesurfer fix isRotate3DAcq is set to false in dcm2niixWrapper dcm/261000-10-6?.dcm + bool isSliceEquidistant = true; //issue539 + if ((nConvert > 0) && (sliceMMarray != NULL)){ + float dx = sliceMMarray[1] - sliceMMarray[0]; + float thr = fabs(dx) * 0.1; + for (int i = 2; i < nConvert; i++) + if (fabs(dx- (sliceMMarray[i]-sliceMMarray[i-1])) > (thr) ) { + printWarning("Unable to rotate 3D volume: slices not equidistant: %g != %g\n", dx, sliceMMarray[i]-sliceMMarray[i-1]); + isSliceEquidistant = false; + break; + } + } + if (isSliceEquidistant) { + imgM = nii_setOrtho(imgM, &hdr0); + isSetOrtho = true; + } + } else if (opts.isFlipY) { //(FLIP_Y) //(dcmList[indx0].CSA.mosaicSlices < 2) && + // freesurfer fix isFlipY is set to false in dcm2niixWrapper + imgM = nii_flipY(imgM, &hdr0); + isFlipY = true; + } else + printMessage("DICOM row order preserved: may appear upside down in tools that ignore spatial transforms\n"); + if ((dcmList[dcmSort[0].indx].epiVersionGE == kGE_EPI_PEPOLAR_REV) || (dcmList[dcmSort[0].indx].epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV_FLIP) || (dcmList[dcmSort[0].indx].epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD_FLIP)) { + imgM = nii_flipImgY(imgM, &hdr0); + } + //begin: gantry tilt we need to save the shear in the transform + mat44 sForm; + LOAD_MAT44(sForm, + hdr0.srow_x[0], hdr0.srow_x[1], hdr0.srow_x[2], hdr0.srow_x[3], + hdr0.srow_y[0], hdr0.srow_y[1], hdr0.srow_y[2], hdr0.srow_y[3], + hdr0.srow_z[0], hdr0.srow_z[1], hdr0.srow_z[2], hdr0.srow_z[3]); + if (!isSameFloatGE(dcmList[indx0].gantryTilt, 0.0)) { + float thetaRad = dcmList[indx0].gantryTilt * M_PI / 180.0; + float c = cos(thetaRad); + if (!isSameFloatGE(c, 0.0)) { + mat33 shearMat; + LOAD_MAT33(shearMat, 1.0, 0.0, 0.0, + 0.0, 1.0, sin(thetaRad) / c, + 0.0, 0.0, 1.0); + mat33 s; + LOAD_MAT33(s, hdr0.srow_x[0], hdr0.srow_x[1], hdr0.srow_x[2], + hdr0.srow_y[0], hdr0.srow_y[1], hdr0.srow_y[2], + hdr0.srow_z[0], hdr0.srow_z[1], hdr0.srow_z[2]); + s = nifti_mat33_mul(shearMat, s); + mat44 shearForm; + LOAD_MAT44(shearForm, s.m[0][0], s.m[0][1], s.m[0][2], hdr0.srow_x[3], + s.m[1][0], s.m[1][1], s.m[1][2], hdr0.srow_y[3], + s.m[2][0], s.m[2][1], s.m[2][2], hdr0.srow_z[3]); + setQSForm(&hdr0, shearForm, true); + } //avoid div/0: cosine not zero + } //if gantry tilt + //end: gantry tilt we need to save the shear in the transform + int returnCode = EXIT_FAILURE; +#ifndef myNoSave + // Indicates success or failure of the (last) save + if (opts.saveFormat != kSaveFormatNIfTI) + removeSclSlopeInter(&hdr0, imgM); + //printMessage(" x--> %d ----\n", nConvert); + if (!opts.isRGBplanar) //save RGB as packed RGBRGBRGB... instead of planar RRR..RGGG..GBBB..B + imgM = nii_planar2rgb(imgM, &hdr0, true); //NIfTI is packed while Analyze was planar + if ((hdr0.dim[4] > 1) && (saveAs3D)) + returnCode = nii_saveNII3D(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); + else { + if (volOrderIndex) //reorder volumes + imgM = reorderVolumes(&hdr0, imgM, volOrderIndex); +#ifndef USING_R + if ((opts.isIgnoreDerivedAnd2D) && (numADC > 0)) + printMessage("Ignoring derived diffusion image(s). Better isotropic and ADC maps can be generated later processing.\n"); + if ((!opts.isIgnoreDerivedAnd2D) && (numADC > 0)) { //ADC maps can disrupt analysis: save a copy with the ADC map, and another without + char pathoutnameADC[2048] = {""}; + strcat(pathoutnameADC, pathoutname); + strcat(pathoutnameADC, "_ADC"); + if (opts.isSave3D) + nii_saveNII3D(pathoutnameADC, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); + else + nii_saveNII(pathoutnameADC, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); + } + if (isHasOverlay) { //each series can have up to 16 overlays, overlays may not be on all slices + for (int j = 0; j < kMaxOverlay; j++) { + bool isOverlay = false; + for (int i = 0; i < nConvert; i++) + if (dcmList[dcmSort[i].indx].overlayStart[j] > 0) + isOverlay = true; + if (!isOverlay) + continue; + char pathoutnameROI[2048] = {""}; + strcat(pathoutnameROI, pathoutname); + char append[128] = {""}; + sprintf(append, "_ROI%d", j + 1); + strcat(pathoutnameROI, append); + struct nifti_1_header hdrr = hdrrx; + hdrr.dim[0] = 3; + if (hdrr.dim[1] < 1) + hdrr.dim[1] = 1; + if (hdrr.dim[2] < 1) + hdrr.dim[2] = 1; + if (hdrr.dim[3] < 1) + hdrr.dim[3] = 1; + hdrr.dim[4] = 1; + hdrr.bitpix = 8; + hdrr.datatype = 2; + hdrr.scl_inter = 0.0; + hdrr.scl_slope = 1.0; + int nvox = hdrr.dim[1] * hdrr.dim[2] * hdrr.dim[3]; + unsigned char *imgR = (unsigned char *)malloc(nvox); + for (int v = 0; v < nvox; v++) + imgR[v] = 0; + if (nConvert == 1) { + int indx = dcmSort[0].indx; + loadOverlay(nameList->str[indx], imgR, dcmList[indx].overlayStart[j], hdrr.dim[1], hdrr.dim[2], hdrr.dim[3]); + } else if (nConvert == hdrr.dim[3]) { + for (int i = 0; i < nConvert; i++) { + int indx = dcmSort[i].indx; + if (dcmList[indx].overlayStart[j] > 0) { + unsigned char *imgRS = imgR + (i * hdrr.dim[1] * hdrr.dim[2]); + loadOverlay(nameList->str[indx], imgRS, dcmList[indx].overlayStart[j], hdrr.dim[1], hdrr.dim[2], 1); + } //if overlay on slice + } //for each volume + } // + if (isFlipZ) + imgR = nii_flipZ(imgR, &hdrr); + if (isSetOrtho) + imgR = nii_setOrtho(imgR, &hdrr); + if (isFlipY) + imgR = nii_flipY(imgR, &hdrr); + nii_saveNII(pathoutnameROI, hdrr, imgR, opts, dcmList[dcmSort[0].indx]); + } + } +#endif + imgM = removeADC(&hdr0, imgM, numADC); +#ifndef USING_R + if (iVaries) + printMessage("Saving as 32-bit float (slope, intercept or bits allocated varies).\n"); + if (opts.saveFormat != kSaveFormatNIfTI) + returnCode = nii_saveForeign(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx], dti4D, dcmList[indx0].CSA.numDti); + else if (opts.isSave3D) + returnCode = nii_saveNII3D(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); + else + returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); +#endif + } +#endif + if (dcmList[indx0].gantryTilt != 0.0) { + setQSForm(&hdr0, sForm, true); + //if (dcmList[indx0].isResampled) { //we no detect based on image orientation https://github.com/rordenlab/dcm2niix/issues/253 + // printMessage("Tilt correction skipped: 0008,2111 reports RESAMPLED\n"); + //} else + if (opts.isTiltCorrect) { + imgM = nii_saveNII3Dtilt(pathoutname, &hdr0, imgM, opts, dcmList[dcmSort[0].indx], sliceMMarray, dcmList[indx0].gantryTilt, dcmList[indx0].manufacturer); + strcat(pathoutname, "_Tilt"); + } else + printMessage("Tilt correction skipped\n"); + } + if ((sliceMMarray != NULL) && (!isSetOrtho)) { + if (dcmList[indx0].isResampled) { + printMessage("Slice thickness correction skipped: 0008,2111 reports RESAMPLED\n"); + } else + returnCode = nii_saveNII3Deq(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx], sliceMMarray); + free(sliceMMarray); + } + //3D-EPI vs 3D SPACE/MPRAGE/ETC + if ((opts.isRotate3DAcq) && (opts.isCrop) && (dcmList[indx0].is3DAcq) && (!dcmList[indx0].isEPI) && (hdr0.dim[3] > 1) && (hdr0.dim[0] < 4)) //for T1 scan: && (dcmList[indx0].TE < 25) + returnCode = nii_saveCrop(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); //n.b. must be run AFTER nii_setOrtho()! +#ifdef USING_R + // Note that for R, only one image should be created per series + // Hence this extra test + if (returnCode != EXIT_SUCCESS) + returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); + if (returnCode == EXIT_SUCCESS) + nii_saveAttributes(dcmList[dcmSort[0].indx], hdr0, opts, nameList->str[dcmSort[0].indx]); +#endif + +#ifdef MGH_FREESURFER + hdr0.vox_offset = 352; + + mrifsStruct.hdr0 = hdr0; + mrifsStruct.imgsz = nii_ImgBytes(hdr0); + mrifsStruct.imgM = imgM; +#else + free(imgM); +#endif + if (dcmList[dcmSort[0].indx].xyzDim[0] > 1) + returnCode = kEXIT_INCOMPLETE_VOLUMES_FOUND; //issue515 + return returnCode; //EXIT_SUCCESS; +} // saveDcm2NiiCore() + +int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D) { + //this wrapper does nothing if all the images share the same echo time and scale + // however, it segments images when these properties vary + uint64_t indx = dcmSort[0].indx; + if ((!dcmList[indx].isScaleOrTEVaries) || (dcmList[indx].xyzDim[4] < 2)) + return saveDcm2NiiCore(nConvert, dcmSort, dcmList, nameList, opts, dti4D, -1); + if ((dcmList[indx].xyzDim[4]) && (dti4D->sliceOrder[0] < 0)) { + printError("Unexpected error for image with varying echo time or intensity scaling\n"); + return EXIT_FAILURE; + } + int ret = EXIT_SUCCESS; + //check for repeated echoes - count unique number of echoes + //code below checks for multi-echoes - not required if maxNumberOfEchoes reported in PARREC + int echoNum[kMaxDTI4D]; + int echo = 1; + for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) + echoNum[i] = 0; + echoNum[0] = 1; + for (int i = 1; i < dcmList[indx].xyzDim[4]; i++) { + for (int j = 0; j < i; j++) + if (dti4D->TE[i] == dti4D->TE[j]) + echoNum[i] = echoNum[j]; + if (echoNum[i] == 0) { + echo++; + echoNum[i] = echo; + } + } + if (echo > 1) + dcmList[indx].isMultiEcho = true; + //check for repeated volumes + int series = 1; + for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) + dti4D->gradDynVol[i] = 0; + dti4D->gradDynVol[0] = 1; + for (int i = 1; i < dcmList[indx].xyzDim[4]; i++) { + for (int j = 0; j < i; j++) + if (( (dcmList[indx].aslFlags != kASL_FLAG_NONE) || isSameFloatGE(dti4D->triggerDelayTime[i], dti4D->triggerDelayTime[j])) && (dti4D->intenIntercept[i] == dti4D->intenIntercept[j]) && (dti4D->intenScale[i] == dti4D->intenScale[j]) && (dti4D->isReal[i] == dti4D->isReal[j]) && (dti4D->isImaginary[i] == dti4D->isImaginary[j]) && (dti4D->isPhase[i] == dti4D->isPhase[j]) && (dti4D->TE[i] == dti4D->TE[j])) + dti4D->gradDynVol[i] = dti4D->gradDynVol[j]; + if (dti4D->gradDynVol[i] == 0) { + series++; + dti4D->gradDynVol[i] = series; + } + } + //bvec/bval saved for each series (real, phase, magnitude, imaginary) https://github.com/rordenlab/dcm2niix/issues/219 + TDTI4D dti4Ds = *dti4D; + bool isHasDti = (dcmList[indx].CSA.numDti > 0); + if ((isHasDti) && (dcmList[indx].CSA.numDti == dcmList[indx].xyzDim[4])) { + int nDti = 0; + for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { + if (dti4D->gradDynVol[i] == 1) { + dti4Ds.S[nDti].V[0] = dti4Ds.S[i].V[0]; + dti4Ds.S[nDti].V[1] = dti4Ds.S[i].V[1]; + dti4Ds.S[nDti].V[2] = dti4Ds.S[i].V[2]; + dti4Ds.S[nDti].V[3] = dti4Ds.S[i].V[3]; + nDti++; + } + } + dcmList[indx].CSA.numDti = nDti; + } + //save each series + bool isScaleVariesEnh = dcmList[indx].isScaleVariesEnh; //issue363: any variation in any image + float intenScale = dcmList[indx].intenScale; + float intenIntercept = dcmList[indx].intenIntercept; + float intenScalePhilips = dcmList[indx].intenScalePhilips; + float RWVIntercept = dcmList[indx].RWVIntercept; + float RWVScale = dcmList[indx].RWVScale; + for (int s = 1; s <= series; s++) { + //issue461: assert these values as saveDcm2NiiCore modifies them when it applies Philips scaling + dcmList[indx].intenScale = intenScale; + dcmList[indx].intenIntercept = intenIntercept; + dcmList[indx].intenScalePhilips = intenScalePhilips; + dcmList[indx].RWVIntercept = RWVIntercept; + dcmList[indx].RWVScale = RWVScale; + //for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) //for each volume + // printf("%g<<triggerDelayTime[i]); + for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { //for each volume + if (dti4D->gradDynVol[i] == s) { + //dti4D->gradDynVol[i] = s; + //nVol ++; + dcmList[indx].TE = dti4D->TE[i]; + //dcmList[indx].intenScale = dti4D->intenScale[i]; + //dcmList[indx].intenIntercept = dti4D->intenIntercept[i]; + //dcmList[indx].intenScalePhilips = dti4D->intenScalePhilips[i]; + //dcmList[indx].RWVScale = dti4D->RWVScale[i]; + //dcmList[indx].RWVIntercept = dti4D->RWVIntercept[i]; + dcmList[indx].isHasPhase = dti4D->isPhase[i]; + dcmList[indx].isHasReal = dti4D->isReal[i]; + dcmList[indx].isHasImaginary = dti4D->isImaginary[i]; + dcmList[indx].triggerDelayTime = dti4D->triggerDelayTime[i]; + dcmList[indx].isHasMagnitude = false; + dcmList[indx].echoNum = echoNum[i]; + break; + } + } + dcmList[indx].isScaleVariesEnh = false; + if (isScaleVariesEnh) { //check if intensity scale varies for this particular output image, this will force 32-bit output + int nz = 0; + for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { //for each volume + if (dti4D->gradDynVol[i] == s) { + for (int z = 0; z < dcmList[indx].xyzDim[3]; z++) { //for each slice + int ix = (i * dcmList[indx].xyzDim[3]) + z; + dti4Ds.intenScale[nz] = dti4D->intenScale[ix]; + dti4Ds.intenIntercept[nz] = dti4D->intenIntercept[ix]; + dti4Ds.intenScalePhilips[nz] = dti4D->intenScalePhilips[ix]; + dti4Ds.RWVIntercept[nz] = dti4D->RWVIntercept[ix]; + dti4Ds.RWVScale[nz] = dti4D->RWVScale[ix]; + nz++; + } //for z: each slice + } //if series matches + } //for each volume + for (int i = 0; i < nz; i++) { + if (dti4Ds.intenIntercept[i] != dti4Ds.intenIntercept[0]) + dcmList[indx].isScaleVariesEnh = true; + if (dti4Ds.intenScale[i] != dti4Ds.intenScale[0]) + dcmList[indx].isScaleVariesEnh = true; + if (dti4Ds.intenScalePhilips[i] != dti4Ds.intenScalePhilips[0]) + dcmList[indx].isScaleVariesEnh = true; + } + dcmList[indx].intenScale = dti4Ds.intenScale[0]; + dcmList[indx].intenIntercept = dti4Ds.intenIntercept[0]; + dcmList[indx].intenScalePhilips = dti4Ds.intenScalePhilips[0]; + dcmList[indx].RWVIntercept = dti4Ds.RWVIntercept[0]; + dcmList[indx].RWVScale = dti4Ds.RWVScale[0]; + } + if (s > 1) + dcmList[indx].CSA.numDti = 0; //only save bvec for first type (magnitude) + int ret2 = saveDcm2NiiCore(nConvert, dcmSort, dcmList, nameList, opts, &dti4Ds, s); + if (ret2 != EXIT_SUCCESS) + ret = ret2; //return EXIT_SUCCESS only if ALL are successful + } + return ret; +} // saveDcm2Nii() + +void fillTDCMsort(struct TDCMsort &tdcmref, const uint64_t indx, const struct TDICOMdata &dcmdata) { + // Copy the relevant parts of dcmdata to tdcmref. + tdcmref.indx = indx; + //printf("series/image %d %d\n", dcmdata.seriesNum, dcmdata.imageNum); + tdcmref.img = ((uint64_t)dcmdata.seriesNum << 32) + dcmdata.imageNum; + for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) + tdcmref.dimensionIndexValues[i] = dcmdata.dimensionIndexValues[i]; + //lines below added to cope with extreme anonymization + // https://github.com/rordenlab/dcm2niix/issues/211 + if (tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] != 0) + return; + //Since dimensionIndexValues are indexed from 1, 0 indicates unused + // we leverage this as a hail mary attempt to distinguish images with identical series and instance numbers + //See Correction Number CP-1242: + // "Clarify in the description of dimension indices ... start from 1" + // 0008,0032 stored as HHMMSS.FFFFFF, there are 86400000 ms per day + // dimensionIndexValues stored as uint32, so encode acquisition time in ms + uint32_t h = trunc(dcmdata.acquisitionTime / 10000.0); + double tm = dcmdata.acquisitionTime - (h * 10000.0); + uint32_t m = trunc(tm / 100.0); + tm = tm - (m * 100.0); + uint32_t ms = round(tm * 1000); + ms += (h * 3600000) + (m * 60000); + //printf("HHMMSS.FFFF %.5f -> %d ms\n", dcmdata.acquisitionTime, ms); + tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] = ms; +} // fillTDCMsort() + +int compareTDCMsort(void const *item1, void const *item2) { + //for quicksort http://blog.ablepear.com/2011/11/objective-c-tuesdays-sorting-arrays.html + struct TDCMsort const *dcm1 = (const struct TDCMsort *)item1; + struct TDCMsort const *dcm2 = (const struct TDCMsort *)item2; + //to do: detect duplicates with SOPInstanceUID (0008,0018) - accurate but slow text comparison + int retval = 0; // tie + if (dcm1->img < dcm2->img) + retval = -1; + else if (dcm1->img > dcm2->img) + retval = 1; + //printf("%d %d\n", dcm1->img, dcm2->img); + //for(int i=0; i < MAX_NUMBER_OF_DIMENSIONS; i++) + // printf("%d %d\n", dcm1->dimensionIndexValues[i], dcm2->dimensionIndexValues[i]); + if (retval != 0) + return retval; //sorted images + // Check the dimensionIndexValues (useful for enhanced DICOM 4D series). + // ->img is basically behaving as a (seriesNum, imageNum) sort key + // concatenated into a (large) integer for qsort. That is unwieldy when + // dimensionIndexValues need to be compared, because the existence of + // uint128_t, uint256_t, etc. is not guaranteed. This sorts by + // (seriesNum, ImageNum, div[0], div[1], ...), or if you think of it as a + // number, the dimensionIndexValues come after the decimal point. + for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) { + if (dcm1->dimensionIndexValues[i] < dcm2->dimensionIndexValues[i]) + return -1; + else if (dcm1->dimensionIndexValues[i] > dcm2->dimensionIndexValues[i]) + return 1; + } + return retval; +} //compareTDCMsort() + +int isSameFloatDouble(double a, double b) { + //Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! + // return (a == b); //niave approach does not have any tolerance for rounding errors + return (fabs(a - b) <= 0.0001); +} + +struct TWarnings { //generate a warning only once per set + bool manufacturerVaries, modalityVaries, derivedVaries, acqNumVaries, dimensionVaries, dateTimeVaries, studyUidVaries, echoVaries, triggerVaries, phaseVaries, coilVaries, forceStackSeries, seriesUidVaries, nameVaries, nameEmpty, orientVaries; +}; + +TWarnings setWarnings() { + TWarnings r; + r.manufacturerVaries = false; + r.modalityVaries = false; + r.derivedVaries = false; + r.acqNumVaries = false; + r.dimensionVaries = false; + r.dateTimeVaries = false; + r.studyUidVaries = false; + r.phaseVaries = false; + r.echoVaries = false; + r.triggerVaries = false; + r.coilVaries = false; + r.seriesUidVaries = false; + r.forceStackSeries = false; + r.nameVaries = false; + r.nameEmpty = false; + r.orientVaries = false; + return r; +} + +bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts, struct TWarnings *warnings, bool *isMultiEcho, bool *isNonParallelSlices, bool *isCoilVaries) { + //returns true if d1 and d2 should be stacked together as a single output + if (!d1.isValid) + return false; + if (!d2.isValid) + return false; + if ((opts->isVerbose) && (d1.seriesNum == d2.seriesNum)) { + //one would never want to combine in these situations: only raise warning for verbose modes to help troubleshooting + if ((d1.manufacturer != d2.manufacturer) && (!warnings->manufacturerVaries)) { + printMessage("Volumes not stacked: manufacturer varies.\n"); + warnings->manufacturerVaries = true; + } + if ((d1.modality != d2.modality) && (!warnings->modalityVaries)) { + printMessage("Volumes not stacked: modality varies.\n"); + warnings->modalityVaries = true; + } + if ((d1.isDerived != d2.isDerived) && (!warnings->derivedVaries)) { + printMessage("Volumes not stacked: derived varies.\n"); + warnings->derivedVaries = true; + } + } + if (d1.manufacturer != d2.manufacturer) + return false; //do not stack data from different vendors + if (d1.modality != d2.modality) + return false; //do not stack MR and CT data! + if (d1.isDerived != d2.isDerived) + return false; //do not stack raw and derived image types + bool isForceStackSeries = false; + if ((opts->isForceStackDCE) && (d1.isStackableSeries) && (d2.isStackableSeries) && (d1.seriesNum != d2.seriesNum)) { + if (!warnings->forceStackSeries) + printMessage("Volumes stacked despite varying series number (use '-m o' to turn off merging).\n"); + warnings->forceStackSeries = true; + isForceStackSeries = true; + } + if ((d1.manufacturer == kMANUFACTURER_SIEMENS) && (strcmp(d1.protocolName, d2.protocolName) == 0) && (strlen(d1.softwareVersions) > 4) && (strlen(d1.sequenceName) > 4) && (strlen(d2.sequenceName) > 4)) { + if (strstr(d1.sequenceName, "_ep_b") && strstr(d2.sequenceName, "_ep_b") && (strstr(d1.softwareVersions, "VB13") || strstr(d1.softwareVersions, "VB12"))) { + //Siemens B12/B13 users with a "DWI" but not "DTI" license would ofter create multi-series acquisitions + if (!warnings->forceStackSeries) + printMessage("Diffusion images stacked despite varying series number (early Siemens DTI).\n"); + warnings->forceStackSeries = true; + isForceStackSeries = true; + } + } + if (isForceStackSeries) + ; + else if ((d1.isXA10A) && (d2.isXA10A) && (d1.seriesNum > 1000) && (d2.seriesNum > 1000)) { + //kludge XA10A (0020,0011) increments [16001, 16002, ...] https://github.com/rordenlab/dcm2niix/issues/236 + //images from series 16001,16002 report different study times (0008,0030)! + if ((d1.seriesNum / 1000) != (d2.seriesNum / 1000)) + return false; + } else if (d1.seriesNum != d2.seriesNum) + return false; +#ifdef mySegmentByAcq + if (d1.acquNum != d2.acquNum) + return false; +#endif + bool isSameStudyInstanceUID = false; + if ((strlen(d1.studyInstanceUID) > 1) && (strlen(d2.studyInstanceUID) > 1)) { + if (strcmp(d1.studyInstanceUID, d2.studyInstanceUID) == 0) + isSameStudyInstanceUID = true; + } + bool isSameTime = isSameFloatDouble(d1.dateTime, d2.dateTime); + if ((isSameStudyInstanceUID) && (d1.isXA10A) && (d2.isXA10A)) + isSameTime = true; //kludge XA10A 0008,0030 incorrect https://github.com/rordenlab/dcm2niix/issues/236 + bool isDimensionVaries = ((d1.xyzDim[1] != d2.xyzDim[1]) || (d1.xyzDim[2] != d2.xyzDim[2]) || (d1.xyzDim[3] != d2.xyzDim[3])); + if ((!isSameStudyInstanceUID) && (!isSameTime)) { + if (opts->isForceStackDCE) { + if (!warnings->studyUidVaries) + printMessage("Slices stacked despite Study Date/Time (0008,0020;0008,0030) and Study UID (0020,000E) variation %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); + warnings->studyUidVaries = true; + } else { + if (!warnings->studyUidVaries) + printMessage("Slices not stacked: Study Date/Time (0008,0020;0008,0030) and Study UID (0020,000E) varies %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); + warnings->studyUidVaries = true; + return false; + } + } + if (isDimensionVaries) { + if (!warnings->dimensionVaries) + printMessage("Slices not stacked: dimensions vary across slices\n"); + warnings->dimensionVaries = true; + return false; + } +#ifndef myIgnoreStudyTime + if (!isSameTime) { //beware, some vendors incorrectly store Image Time (0008,0033) as Study Time (0008,0030). + if (!warnings->dateTimeVaries) + printMessage("Slices not stacked: Study Date/Time (0008,0020;0008,0030) varies %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); + warnings->dateTimeVaries = true; + return false; + } +#endif + if ((opts->isForceStackSameSeries == 1) || ((opts->isForceStackSameSeries == 2) && (d1.isXRay))) { + // "isForceStackSameSeries == 2" will automatically stack CT scans but not MR + //if (((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) && (!d1.isXRay)) { + // *isMultiEcho = true; + //} +#ifdef MGH_FREESURFER + printf("isForceStackSameSeries = true, seriesNum %ld, %ld, seriesInstanceUidCrc %d, %d\n", d1.seriesNum, d2.seriesNum, d1.seriesUidCrc, d2.seriesUidCrc); +#endif + return true; //we will stack these images, even if they differ in the following attributes + } + if ((d1.isHasImaginary != d2.isHasImaginary) || (d1.isHasPhase != d2.isHasPhase) || (d1.isHasReal != d2.isHasReal)) { + if (!warnings->phaseVaries) + printMessage("Slices not stacked: some are phase/real/imaginary/phase maps, others are not. Instances %d %d\n", d1.imageNum, d2.imageNum); + warnings->phaseVaries = true; + return false; + } + //if ((d1.TE != d2.TE) || (d1.echoNum != d2.echoNum)) { + if ((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) { + if ((!warnings->echoVaries) && (d1.isXRay)) //for CT/XRay we check DICOM tag 0018,1152 (XRayExposure) + printMessage("Slices not stacked: X-Ray Exposure varies (exposure %g, %g; number %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); + if ((!warnings->echoVaries) && (!d1.isXRay)) //for MRI + printMessage("Slices not stacked: echo varies (TE %g, %g; echo %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); + warnings->echoVaries = true; + *isMultiEcho = true; + return false; + } + if ((d1.triggerDelayTime != d2.triggerDelayTime) && (d1.manufacturer == kMANUFACTURER_PHILIPS) && (d1.aslFlags == kASL_FLAG_NONE)) { //issue 384 + if (!warnings->triggerVaries) + printMessage("Slices not stacked: trigger time varies\n"); + warnings->triggerVaries = true; + return false; + } + if (d1.coilCrc != d2.coilCrc) { + if (opts->isForceStackDCE) { + if (!warnings->coilVaries) + printMessage("Slices stacked despite coil variation '%s' vs '%s' (use '-m o' to turn off merging)\n", d1.coilName, d2.coilName); + warnings->coilVaries = true; + *isCoilVaries = true; + } else { + if (!warnings->coilVaries) + printMessage("Slices not stacked: coil varies '%s' vs '%s'\n", d1.coilName, d2.coilName); + warnings->coilVaries = true; + *isCoilVaries = true; + return false; + } + } + if ((strlen(d1.protocolName) < 1) && (strlen(d2.protocolName) < 1)) { + if (!warnings->nameEmpty) + printWarning("Empty protocol name(s) (0018,1030)\n"); + warnings->nameEmpty = true; + } else if ((strcmp(d1.protocolName, d2.protocolName) != 0)) { + if (!warnings->nameVaries) + printMessage("Slices not stacked: protocol name varies '%s' != '%s'\n", d1.protocolName, d2.protocolName); + warnings->nameVaries = true; + return false; + } + if ((*isNonParallelSlices) && (d1.CSA.mosaicSlices > 1)) + return false; //issue481 + if ((!isSameFloatGE(d1.orient[1], d2.orient[1]) || !isSameFloatGE(d1.orient[2], d2.orient[2]) || !isSameFloatGE(d1.orient[3], d2.orient[3]) || + !isSameFloatGE(d1.orient[4], d2.orient[4]) || !isSameFloatGE(d1.orient[5], d2.orient[5]) || !isSameFloatGE(d1.orient[6], d2.orient[6]))) { + if ((!warnings->orientVaries) && (!d1.isNonParallelSlices) && (!d1.isLocalizer)) + printMessage("Slices not stacked: orientation varies (vNav or localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n", + d1.orient[1], d1.orient[2], d1.orient[3], d1.orient[4], d1.orient[5], d1.orient[6], + d2.orient[1], d2.orient[2], d2.orient[3], d2.orient[4], d2.orient[5], d2.orient[6]); + warnings->orientVaries = true; + *isNonParallelSlices = true; + return false; + } + if (d1.acquNum != d2.acquNum) { + if ((!warnings->acqNumVaries) && (opts->isVerbose)) //virtually always people want to stack these + printMessage("Slices stacked despite varying acquisition numbers (if this is not desired recompile with 'mySegmentByAcq')\n"); + warnings->acqNumVaries = true; + } + if ((!isForceStackSeries) && (d1.seriesUidCrc != d2.seriesUidCrc)) { + if (!warnings->seriesUidVaries) + printMessage("Slices not stacked: series instance UID varies (duplicates all other properties)\n"); + warnings->seriesUidVaries = true; + return false; + } + return true; +} // isSameSet() + +void freeNameList(struct TSearchList nameList) { + if (nameList.numItems > 0) { + unsigned long n = nameList.numItems; + if (n > nameList.maxItems) + n = nameList.maxItems; //assigned if (nameList->numItems < nameList->maxItems) + for (unsigned long i = 0; i < n; i++) + free(nameList.str[i]); + } + free(nameList.str); +} + +int singleDICOM(struct TDCMopts *opts, char *fname) { + if (isDICOMfile(fname) == 0) { + printError("Not a DICOM image : %s\n", fname); + return 0; + } + struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(sizeof(struct TDICOMdata)); + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); + struct TSearchList nameList; + struct TDCMprefs prefs; + opts2Prefs(opts, &prefs); + nameList.maxItems = 1; // larger requires more memory, smaller more passes + nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file + nameList.numItems = 0; + nameList.str[nameList.numItems] = (char *)malloc(strlen(fname) + 1); + strcpy(nameList.str[nameList.numItems], fname); + nameList.numItems++; + TDCMsort *dcmSort = (TDCMsort *)malloc(sizeof(TDCMsort)); + dcmList[0].converted2NII = 1; + dcmList[0] = readDICOMx(nameList.str[0], &prefs, dti4D); //ignore compile warning - memory only freed on first of 2 passes + //dcmList[0] = readDICOMv(nameList.str[0], opts->isVerbose, opts->compressFlag, dti4D); //ignore compile warning - memory only freed on first of 2 passes + fillTDCMsort(dcmSort[0], 0, dcmList[0]); + int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, dti4D); + freeNameList(nameList); + free(dti4D); + free(dcmSort); + free(dcmList); + return ret; +} // singleDICOM() + +size_t fileBytes(const char *fname) { + FILE *fp = fopen(fname, "rb"); + if (!fp) + return 0; + fseek(fp, 0, SEEK_END); + size_t fileLen = ftell(fp); + fclose(fp); + return fileLen; +} //fileBytes() + +int searchDirForDICOM(char *path, struct TSearchList *nameList, int maxDepth, int depth, struct TDCMopts *opts) { + int ret = kEXIT_NOMINAL; + tinydir_dir dir; + tinydir_open(&dir, path); + while (dir.has_next) { + tinydir_file file; + file.is_dir = 0; //avoids compiler warning: this is set by tinydir_readfile + tinydir_readfile(&dir, &file); + //printMessage("%s\n", file.name); + char filename[768] = ""; + strcat(filename, path); + strcat(filename, kFileSep); + strcat(filename, file.name); + if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { + int tmp = searchDirForDICOM(filename, nameList, maxDepth, depth + 1, opts); + if (tmp != kEXIT_NOMINAL) ret = tmp; //e.g. found ecat + } else if (!file.is_reg) //ignore files "." and ".." + ; + else if ((strlen(file.name) < 1) || (file.name[0] == '.')) + ; //printMessage("skipping hidden file %s\n", file.name); + else if ((strlen(file.name) == 8) && (strcicmp(file.name, "DICOMDIR") == 0)) + ; //printMessage("skipping DICOMDIR\n"); + else if ((isDICOMfile(filename) > 0) || (isExt(filename, ".par"))) { + if (nameList->numItems < nameList->maxItems) { + nameList->str[nameList->numItems] = (char *)malloc(strlen(filename) + 1); + strcpy(nameList->str[nameList->numItems], filename); + } + nameList->numItems++; + //printMessage("dcm %lu %s \n",nameList->numItems, filename); +#ifndef USING_R + } else { + if (fileBytes(filename) > 2048) { + int tmp = convert_foreign(filename, *opts); + if (tmp == EXIT_SUCCESS) ret = tmp; //e.g. found ecat + } +#ifdef MY_DEBUG + printMessage("Not a dicom:\t%s\n", filename); +#endif +#endif + } + tinydir_next(&dir); + } + tinydir_close(&dir); + return ret; +} // searchDirForDICOM() + +int removeDuplicates(int nConvert, struct TDCMsort dcmSort[]) { + //done AFTER sorting, so duplicates will be sequential + if (nConvert < 2) + return nConvert; + int nDuplicates = 0; + for (int i = 1; i < nConvert; i++) { + if (compareTDCMsort(&dcmSort[i], &dcmSort[i - 1]) == 0) { + nDuplicates++; + } else { + dcmSort[i - nDuplicates].img = dcmSort[i].img; + dcmSort[i - nDuplicates].indx = dcmSort[i].indx; + for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; ++j) + dcmSort[i - nDuplicates].dimensionIndexValues[j] = dcmSort[i].dimensionIndexValues[j]; + } + } + if (nDuplicates > 0) + printMessage("%d images have identical time, series, acquisition and instance values. DUPLICATES REMOVED.\n", nDuplicates); + return nConvert - nDuplicates; +} // removeDuplicates() + +int removeDuplicatesVerbose(int nConvert, struct TDCMsort dcmSort[], struct TSearchList *nameList) { + //done AFTER sorting, so duplicates will be sequential + if (nConvert < 2) + return nConvert; + int nDuplicates = 0; + for (int i = 1; i < nConvert; i++) { + if (compareTDCMsort(&dcmSort[i], &dcmSort[i - 1]) == 0) { + printMessage("\t%s\t=\t%s\n", nameList->str[dcmSort[i - 1].indx], nameList->str[dcmSort[i].indx]); + nDuplicates++; + } else { + dcmSort[i - nDuplicates].img = dcmSort[i].img; + dcmSort[i - nDuplicates].indx = dcmSort[i].indx; + for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; ++j) + dcmSort[i - nDuplicates].dimensionIndexValues[j] = dcmSort[i].dimensionIndexValues[j]; + } + } + if (nDuplicates > 0) + printMessage("%d images have identical time, series, acquisition and instance values. Duplicates removed.\n", nDuplicates); + return nConvert - nDuplicates; +} // removeDuplicatesVerbose() + +int convert_parRec(char *fnm, struct TDCMopts opts) { + //sample dataset from Ed Gronenschild + struct TSearchList nameList; + int ret = EXIT_FAILURE; + nameList.numItems = 1; + nameList.maxItems = 1; + nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //we reserve one pointer (32 or 64 bits) per potential file + struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(nameList.numItems * sizeof(struct TDICOMdata)); + nameList.str[0] = (char *)malloc(strlen(fnm) + 1); + strcpy(nameList.str[0], fnm); + //nameList.str[0] = (char *)malloc(strlen(opts.indir)+1); + //strcpy(nameList.str[0],opts.indir); + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); + dcmList[0] = nii_readParRec(nameList.str[0], opts.isVerbose, dti4D, false); + struct TDCMsort dcmSort[1]; + dcmSort[0].indx = 0; + if (dcmList[0].isValid) + ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, opts, dti4D); + free(dti4D); + free(dcmList); //if (nConvertTotal == 0) + if (nameList.numItems < 1) + printMessage("No valid PAR/REC files were found\n"); + freeNameList(nameList); + return ret; +} // convert_parRec() + +int copyFile(char *src_path, char *dst_path) { +#define BUFFSIZE 32768 + unsigned char buffer[BUFFSIZE]; + FILE *fin = fopen(src_path, "rb"); + if (fin == NULL) { + printError("Check file permissions: Unable to open input %s\n", src_path); + return EXIT_SUCCESS; + } + if (is_fileexists(dst_path)) { + if (true) { + printWarning("Naming conflict (duplicates?): '%s' '%s'\n", src_path, dst_path); + return EXIT_SUCCESS; + } else { + printError("File naming conflict. Existing file %s\n", dst_path); + return EXIT_FAILURE; + } + } + FILE *fou = fopen(dst_path, "wb"); + if (fou == NULL) { + printError("Check file permission. Unable to open output %s\n", dst_path); + return EXIT_FAILURE; + } + size_t bytes; + while ((bytes = fread(buffer, 1, BUFFSIZE, fin)) != 0) { + if (fwrite(buffer, 1, bytes, fou) != bytes) { + printError("Unable to write %zu bytes to output %s\n", bytes, dst_path); + return EXIT_FAILURE; + } + } + fclose(fin); + fclose(fou); + return EXIT_SUCCESS; +} + +#ifdef USING_R + +// This implementation differs enough from the mainline one to be separated +int searchDirRenameDICOM(char *path, int maxDepth, int depth, struct TDCMopts *opts) { + // The tinydir_open_sorted function reads the whole directory at once, + // which is necessary in this context since we may be creating new + // files in the same directory, which we don't want to further examine + tinydir_dir dir; + int count = 0; + if (tinydir_open_sorted(&dir, path) != 0) + return -1; + for (size_t i = 0; i < dir.n_files; i++) { + // If this directory entry is a subdirectory, search it recursively + tinydir_file &file = dir._files[i]; + const std::string sourcePath = std::string(path) + kFileSep + file.name; + char *sourcePathPtr = const_cast(sourcePath.c_str()); + if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { + const int subdirectoryCount = searchDirRenameDICOM(sourcePathPtr, maxDepth, depth + 1, opts); + if (subdirectoryCount < 0) { + tinydir_close(&dir); + return -1; + } + count += subdirectoryCount; + } else if (file.is_reg && strlen(file.name) > 0 && file.name[0] != '.' && strcicmp(file.name, "DICOMDIR") != 0 && isDICOMfile(sourcePathPtr)) { + TDICOMdata dcm = readDICOM(sourcePathPtr); + if (dcm.imageNum > 0) { + if ((opts->isIgnoreDerivedAnd2D) && ((dcm.isLocalizer) || (strcmp(dcm.sequenceName, "_tfl2d1") == 0) || (strcmp(dcm.sequenceName, "_fl3d1_ns") == 0) || (strcmp(dcm.sequenceName, "_fl2d1") == 0))) { + printMessage("Ignoring localizer %s\n", sourcePathPtr); + opts->ignoredPaths.push_back(sourcePath); + } else if ((opts->isIgnoreDerivedAnd2D && dcm.isDerived)) { + printMessage("Ignoring derived %s\n", sourcePathPtr); + opts->ignoredPaths.push_back(sourcePath); + } else { + // Create an initial file name + char outname[PATH_MAX] = {""}; + if (dcm.echoNum > 1) + dcm.isMultiEcho = true; + nii_createFilename(dcm, outname, *opts); + // If the file name part of the target path has no extension, add ".dcm" + std::string targetPath(outname); + std::string targetStem, targetExtension; + const size_t periodLoc = targetPath.find_last_of('.'); + if (periodLoc == targetPath.length() - 1) { + targetStem = targetPath.substr(0, targetPath.length() - 1); + targetExtension = ".dcm"; + } else if (periodLoc == std::string::npos || periodLoc < targetPath.find_last_of("\\/")) { + targetStem = targetPath; + targetExtension = ".dcm"; + } else { + targetStem = targetPath.substr(0, periodLoc); + targetExtension = targetPath.substr(periodLoc); + } + // Deduplicate the target path to avoid overwriting existing files + targetPath = targetStem + targetExtension; + GetRNGstate(); + while (is_fileexists(targetPath.c_str())) { + std::ostringstream suffix; + unsigned suffixValue = static_cast(round(R::unif_rand() * (R_pow_di(2.0, 24) - 1.0))); + suffix << std::hex << std::setfill('0') << std::setw(6) << suffixValue; + targetPath = targetStem + "_" + suffix.str() + targetExtension; + } + PutRNGstate(); + // Copy the file, unless the source and target paths are the same + if (targetPath.compare(sourcePath) == 0) { + if (opts->isVerbose > 1) + printMessage("Skipping %s, which would be copied onto itself\n", sourcePathPtr); + } else if (copyFile(sourcePathPtr, const_cast(targetPath.c_str())) == EXIT_SUCCESS) { + opts->sourcePaths.push_back(sourcePath); + opts->targetPaths.push_back(targetPath); + count++; + if (opts->isVerbose > 0) + printMessage("Copying %s -> %s\n", sourcePathPtr, targetPath.c_str()); + } else { + printWarning("Unable to copy to path %s\n", targetPath.c_str()); + } + } + } + } + } + return count; +} + +#else + +int searchDirRenameDICOM(char *path, int maxDepth, int depth, struct TDCMopts *opts) { + int retAll = 0; + tinydir_dir dir; + if (tinydir_open_sorted(&dir, path) != 0) { + if (opts->isVerbose > 0) + printMessage("Unable to open %s\n", path); + return -1; + } + if (dir.n_files < 1) { + if (opts->isVerbose > 0) + printMessage("No files in %s\n", path); + return 0; + } + if (opts->isVerbose > 0) + printMessage("Found %zu items in %s\n", dir.n_files, path); //%lu -> %zu + for (size_t i = 0; i < dir.n_files; i++) { + // If this directory entry is a subdirectory, search it recursively + tinydir_file &file = dir._files[i]; + char filename[768] = ""; + strcat(filename, path); + strcat(filename, kFileSep); + strcat(filename, file.name); + if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { + int retSub = searchDirRenameDICOM(filename, maxDepth, depth + 1, opts); + if (retSub < 0) + return retSub; + retAll += retSub; + } else if (!file.is_reg) //ignore files "." and ".." + ; + else if ((strlen(file.name) < 1) || (file.name[0] == '.')) + ; //printMessage("skipping hidden file %s\n", file.name); + else if ((strlen(file.name) == 8) && (strcicmp(file.name, "DICOMDIR") == 0)) + ; //printMessage("skipping DICOMDIR\n"); + else if (isDICOMfile(filename) > 0) { + //printMessage("dcm %s \n", filename); + struct TDICOMdata dcm = readDICOM(filename); //ignore compile warning - memory only freed on first of 2 passes + //~ if ((dcm.isValid) &&((dcm.totalSlicesIn4DOrder != NULL) ||(dcm.patientPositionNumPhilips > 1) || (dcm.CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately + if (dcm.imageNum > 0) { //use imageNum instead of isValid to convert non-images (kWaveformSq will have instance number but is not a valid image) + if ((opts->isIgnoreDerivedAnd2D) && ((dcm.isLocalizer) || (strcmp(dcm.sequenceName, "_tfl2d1") == 0) || (strcmp(dcm.sequenceName, "_fl3d1_ns") == 0) || (strcmp(dcm.sequenceName, "_fl2d1") == 0))) { + printMessage("Ignoring localizer %s\n", filename); + } else if ((opts->isIgnoreDerivedAnd2D && dcm.isDerived)) { + printMessage("Ignoring derived %s\n", filename); + } else { + char outname[PATH_MAX] = {""}; + if (dcm.echoNum > 1) + dcm.isMultiEcho = true; //last resort: Siemens gives different echoes the same image number: avoid overwriting, e.g "-f %r.dcm" should generate "1.dcm", "1_e2.dcm" for multi-echo volumes + nii_createFilename(dcm, outname, *opts); + //if (isDcmExt) strcat (outname,".dcm"); + int ret = copyFile(filename, outname); + if (ret != EXIT_SUCCESS) { + printError("Unable to rename all DICOM images.\n"); + return -1; + } + retAll += 1; + if (opts->isVerbose > 0) + printMessage("Renaming %s -> %s\n", filename, outname); + } + } + } + tinydir_next(&dir); + } + tinydir_close(&dir); + return retAll; +} // searchDirForDICOM() + +#endif // USING_R + +//Timing +#define myTimer + +//"BubbleSort" method uses nested "for i = 0..nDCM; for j = i+1..nDCM" +// the alternative is to quick-sort based on seriesUID and only test for matches in buckets where seriesUID matches +// the advantage of the bubble sort method is that it has been used extensively +// the quick sort method should be faster when handling thousands of files. +// difference very small for typical datasets (~0.1s for 3200 DICOMs) +//#define myBubbleSort +#ifndef myBubbleSort +struct TCRCsort { + uint64_t indx; + uint32_t crc; +}; + +void fillTCRCsort(struct TCRCsort &tcrcref, const uint64_t indx, const uint32_t crc) { + tcrcref.indx = indx; + tcrcref.crc = crc; +} + +int compareTCRCsort(void const *item1, void const *item2) { + //for quicksort http://blog.ablepear.com/2011/11/objective-c-tuesdays-sorting-arrays.html + struct TCRCsort const *dcm1 = (const struct TCRCsort *)item1; + struct TCRCsort const *dcm2 = (const struct TCRCsort *)item2; + if (dcm1->crc < dcm2->crc) + return -1; + else if (dcm1->crc > dcm2->crc) + return 1; + return 0; //tie +} +#endif + +#ifdef myTimer +int reportProgress(int progressPct, float frac) { + int newProgressPct = round(100.0 * frac); + const int kMinPct = 5; //e.g. if 10 then report 0.1, 0.2, 0.3... + newProgressPct = (newProgressPct / kMinPct) * kMinPct; //if MinPct is 5 and we are 87 percent done report 85% + if (newProgressPct == progressPct) + return progressPct; + if (newProgressPct != progressPct) //only report for change + printProgress((float)newProgressPct / 100.0); + return newProgressPct; +} +#endif + +int nii_loadDirCore(char *indir, struct TDCMopts *opts) { +#ifdef MGH_FREESURFER + memset(&mrifsStruct, 0, sizeof(mrifsStruct)); +#endif + + struct TSearchList nameList; + int nConvertTotal = 0; +#if defined(_WIN64) || defined(_WIN32) || defined(USING_R) + nameList.maxItems = 24000; // larger requires more memory, smaller more passes +#else //UNIX, not R + nameList.maxItems = 96000; // larger requires more memory, smaller more passes +#endif + //progress variables + const float kStage1Frac = 0.05; //e.g. finding files requires ~05pct + const float kStage2Frac = 0.45; //e.g. reading headers and converting 4D files requires ~45pct + const float kStage3Frac = 0.50; //e.g. converting 2D/3D files to 3D/4D files requires ~50pct + int progressPct = 0; //proportion correct, 0..100 + if (opts->isProgress) + progressPct = reportProgress(-1, 0.0); //report 0% +#ifdef myTimer + clock_t start = clock(); +#endif + if ((is_fileNotDir(opts->indir)) && isExt(opts->indir, ".txt")) { + nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file + nameList.numItems = 0; + FILE *fp = fopen(opts->indir, "r"); //textDICOM + if (fp == NULL) + return EXIT_FAILURE; + char dcmname[2048]; + while (fgets(dcmname, sizeof(dcmname), fp)) { + int sz = (int)strlen(dcmname); + if (sz > 0 && dcmname[sz - 1] == '\n') + dcmname[sz - 1] = 0; //Unix LF + if (sz > 1 && dcmname[sz - 2] == '\r') + dcmname[sz - 2] = 0; //Windows CR/LF + if ((!is_fileexists(dcmname)) || (!is_fileNotDir(dcmname))) { //<-this will accept meta data + fclose(fp); + printError("Problem with file '%s'\n", dcmname); + return EXIT_FAILURE; + } + if (nameList.numItems < nameList.maxItems) { + nameList.str[nameList.numItems] = (char *)malloc(strlen(dcmname) + 1); + strcpy(nameList.str[nameList.numItems], dcmname); + } + nameList.numItems++; + } + fclose(fp); + if (nameList.numItems >= nameList.maxItems) { + printError("Too many file names in '%s'\n", opts->indir); + return EXIT_FAILURE; + } + if (nameList.numItems < 1) + return kEXIT_NO_VALID_FILES_FOUND; + printMessage("Found %lu files in '%s'\n", nameList.numItems, opts->indir); + } else { + //1: find filenames of dicom files: up to two passes if we found more files than we allocated memory + for (int i = 0; i < 2; i++) { + nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file + nameList.numItems = 0; + int ret = searchDirForDICOM(indir, &nameList, opts->dirSearchDepth, 0, opts); + if (ret == EXIT_SUCCESS) //e.g. converted ECAT + nConvertTotal++; + if (nameList.numItems <= nameList.maxItems) + break; + freeNameList(nameList); + nameList.maxItems = nameList.numItems + 1; + //printMessage("Second pass required, found %ld images\n", nameList.numItems); + } + if (nameList.numItems < 1) { + if ((opts->dirSearchDepth > 0) && (nConvertTotal < 1)) + printError("Unable to find any DICOM images in %s (or subfolders %d deep)\n", indir, opts->dirSearchDepth); + else //keep silent for dirSearchDepth = 0 - presumably searching multiple folders + { + }; + free(nameList.str); //ignore compile warning - memory only freed on first of 2 passes + if (nConvertTotal > 0) return EXIT_SUCCESS; //e.g. converted ECAT + return kEXIT_NO_VALID_FILES_FOUND; + } + } + size_t nDcm = nameList.numItems; + printMessage("Found %lu DICOM file(s)\n", nameList.numItems); //includes images and other non-image DICOMs +#ifdef myTimer + if (opts->isProgress > 1) + printMessage("Stage 1 (Count number of DICOMs) required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC); + start = clock(); +#endif + if (opts->isProgress) + progressPct = reportProgress(progressPct, kStage1Frac); //proportion correct, 0..100 + // struct TDICOMdata dcmList [nameList.numItems]; //<- this exhausts the stack for large arrays + struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(nameList.numItems * sizeof(struct TDICOMdata)); + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); + struct TDCMprefs prefs; + opts2Prefs(opts, &prefs); + bool compressionWarning = false; + bool convertError = false; + bool isDcmExt = isExt(opts->filename, ".dcm"); // "%r.dcm" with multi-echo should generate "1.dcm", "1e2.dcm" + if (isDcmExt) + opts->filename[strlen(opts->filename) - 4] = 0; // "%s_%r.dcm" -> "%s_%r" + //consider OpenMP + // g++-9 -I. main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG -fopenmp + for (int i = 0; i < (int)nDcm; i++) { + if ((isExt(nameList.str[i], ".par")) && (isDICOMfile(nameList.str[i]) < 1)) { + //strcpy(opts->indir, nameList.str[i]); //set to original file name, not path + dcmList[i].converted2NII = 1; + int ret = convert_parRec(nameList.str[i], *opts); + if (ret == EXIT_SUCCESS) + nConvertTotal++; + else + convertError = true; + continue; + } + dcmList[i] = readDICOMx(nameList.str[i], &prefs, dti4D); //ignore compile warning - memory only freed on first of 2 passes + //dcmList[i] = readDICOMv(nameList.str[i], opts->isVerbose, opts->compressFlag, dti4D); //ignore compile warning - memory only freed on first of 2 passes + if (opts->isIgnoreSeriesInstanceUID) + dcmList[i].seriesUidCrc = dcmList[i].seriesNum; + //if (!dcmList[i].isValid) printf(">>>>Not a valid DICOM %s\n", nameList.str[i]); + if ((dcmList[i].isValid) && ((dti4D->sliceOrder[0] >= 0) || (dcmList[i].CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately + struct TDCMsort dcmSort[1]; + fillTDCMsort(dcmSort[0], i, dcmList[i]); + //printMessage("***MGH_FREESURFER***: calling saveDcm2Nii() (%s:%s:%d)\n", __FILE__, __func__, __LINE__); + dcmList[i].converted2NII = 1; + int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, dti4D); + if (ret == EXIT_SUCCESS) + nConvertTotal++; + else + convertError = true; + } + if ((dcmList[i].compressionScheme != kCompressNone) && (!compressionWarning) && (opts->compressFlag != kCompressNone)) { + compressionWarning = true; //generate once per conversion rather than once per image + printMessage("Image Decompression is new: please validate conversions\n"); + } + if (opts->isProgress) + progressPct = reportProgress(progressPct, kStage1Frac + (kStage2Frac * (float)i / (float)nDcm)); //proportion correct, 0..100 + } +#ifdef myTimer + if (opts->isProgress > 1) + printMessage("Stage 2 (Read DICOM headers, Convert 4D) required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC); + start = clock(); +#endif + if (opts->isRenameNotConvert) { + free(dcmList); + free(dti4D); + return EXIT_SUCCESS; + } +#ifdef USING_R + if (opts->isScanOnly) { + TWarnings warnings = setWarnings(); + // Create the first series from the first DICOM file + TDicomSeries firstSeries; + char firstSeriesName[2048] = ""; + nii_createFilename(dcmList[0], firstSeriesName, *opts); + firstSeries.name = firstSeriesName; + firstSeries.representativeData = dcmList[0]; + firstSeries.files.push_back(nameList.str[0]); + opts->series.push_back(firstSeries); + // Iterate over the remaining files + for (size_t i = 1; i < nDcm; i++) { + bool matched = false; + // If the file matches an existing series, add it to the corresponding file list + for (int j = 0; j < opts->series.size(); j++) { + bool isMultiEcho = false, isNonParallelSlices = false, isCoilVaries = false; + if (isSameSet(opts->series[j].representativeData, dcmList[i], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) { + opts->series[j].files.push_back(nameList.str[i]); + matched = true; + break; + } + } + // If not, create a new series object + if (!matched) { + TDicomSeries nextSeries; + char nextSeriesName[2048] = ""; + nii_createFilename(dcmList[i], nextSeriesName, *opts); + nextSeries.name = nextSeriesName; + nextSeries.representativeData = dcmList[i]; + nextSeries.files.push_back(nameList.str[i]); + opts->series.push_back(nextSeries); + } + } + // To avoid a spurious warning below + nConvertTotal = nDcm; + } else { +#endif +#ifdef myBubbleSort + //3: stack DICOMs with the same Series + struct TWarnings warnings = setWarnings(); + for (int i = 0; i < (int)nDcm; i++) { + if ((dcmList[i].converted2NII == 0) && (dcmList[i].isValid)) { + int nConvert = 0; + bool isMultiEcho = false; + bool isNonParallelSlices = false; + bool isCoilVaries = false; + for (int j = i; j < (int)nDcm; j++) + if (isSameSet(dcmList[i], dcmList[j], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) + nConvert++; + if (nConvert < 1) + nConvert = 1; //prevents compiler warning for next line: never executed since j=i always causes nConvert ++ + TDCMsort *dcmSort = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); + nConvert = 0; + for (int j = i; j < (int)nDcm; j++) { + isMultiEcho = false; + isCoilVaries = false; + isNonParallelSlices = false; + if (isSameSet(dcmList[i], dcmList[j], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) { + dcmList[j].converted2NII = 1; //do not reprocess repeats + fillTDCMsort(dcmSort[nConvert], j, dcmList[j]); + nConvert++; + } else { + if (isNonParallelSlices) { + dcmList[i].isNonParallelSlices = true; + dcmList[j].isNonParallelSlices = true; + } + if (isMultiEcho) { + dcmList[i].isMultiEcho = true; + dcmList[j].isMultiEcho = true; + } + if (isCoilVaries) { + dcmList[i].isCoilVaries = true; + dcmList[j].isCoilVaries = true; + } + } //unable to stack images: mark files that may need file name dis-ambiguation + } + qsort(dcmSort, nConvert, sizeof(struct TDCMsort), compareTDCMsort); //sort based on series and image numbers.... + //dcmList[dcmSort[0].indx].isMultiEcho = isMultiEcho; + if (opts->isVerbose) + nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); + else + //nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); + nConvert = removeDuplicates(nConvert, dcmSort); + int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, dti4D); + if (ret == EXIT_SUCCESS) + nConvertTotal += nConvert; + else + convertError = true; + free(dcmSort); + } //convert all images of this series + } +#else //avoid bubble sort - dont check all images for match, only those with identical series instance UID + //3: stack DICOMs with the same Series + struct TWarnings warnings = setWarnings(); + //sort by series instance UID ... avoids bubble-sort penalty + TCRCsort *crcSort = (TCRCsort *)malloc(nDcm * sizeof(TCRCsort)); + for (int i = 0; i < (int)nDcm; i++) + fillTCRCsort(crcSort[i], i, dcmList[i].seriesUidCrc); + qsort(crcSort, nDcm, sizeof(struct TCRCsort), compareTCRCsort); //sort based on series and image numbers.... + int *convertIdxs = (int *)malloc(sizeof(int) * (nDcm)); + for (int i = 0; i < (int)nDcm; i++) { + int ii = crcSort[i].indx; + if (dcmList[ii].converted2NII) + continue; + if (!dcmList[ii].isValid) + continue; + +#ifdef MGH_FREESURFER + double seriesNum = (double) dcmList[ii].seriesUidCrc; + if (!isSameDouble(opts->seriesNumber[0], seriesNum)) + continue; // we convert one series at a time, skip the ones that we are not interested in +#endif + + int nConvert = 0; + bool isMultiEcho = false; + bool isNonParallelSlices = false; + bool isCoilVaries = false; + int jMax = i; + for (int j = i; j < (int)nDcm; j++) { + int ji = crcSort[j].indx; + if (dcmList[ii].seriesUidCrc != dcmList[ji].seriesUidCrc) + break; //seriesUID no longer matches no need to examine any subsequent images + jMax = j; + if (isSameSet(dcmList[ii], dcmList[ji], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) { + dcmList[ji].converted2NII = 1; //do not reprocess repeats + convertIdxs[nConvert] = ji; + nConvert++; + } + } //for all images with same seriesUID as first one + if ((isNonParallelSlices) && (dcmList[ii].CSA.mosaicSlices > 1) && (nConvert > 0)) { //issue481: if ANY volumes are non-parallel, save ALL as 3D + printWarning("Saving mosaics with non-parallel slices as 3D (issue 481)\n"); + for (int j = i; j < (int)nDcm; j++) { + int ji = crcSort[j].indx; + if (dcmList[ii].seriesUidCrc != dcmList[ji].seriesUidCrc) + break; + dcmList[ji].converted2NII = 1; + dcmList[ji].isNonParallelSlices = true; + if (isMultiEcho) + dcmList[ji].isMultiEcho = true; + if (isCoilVaries) + dcmList[ji].isCoilVaries = true; + struct TDCMsort dcmSort[1]; + fillTDCMsort(dcmSort[0], ji, dcmList[ji]); + int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, dti4D); + if (ret == EXIT_SUCCESS) + nConvertTotal++; + else + convertError = true; + } + continue; + } //issue481 + //issue 381: ensure all images are informed if there are variations in echo, parallel slices, coil name: + if (isMultiEcho) + for (int j = i; j <= jMax; j++) { + int ji = crcSort[j].indx; + dcmList[ji].isMultiEcho = true; + } + if (isNonParallelSlices) + for (int j = i; j <= jMax; j++) { + int ji = crcSort[j].indx; + dcmList[ji].isNonParallelSlices = true; + } + if (isCoilVaries) + for (int j = i; j <= jMax; j++) { + int ji = crcSort[j].indx; + dcmList[ji].isCoilVaries = true; + } + TDCMsort *dcmSort = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); + for (int j = 0; j < nConvert; j++) + fillTDCMsort(dcmSort[j], convertIdxs[j], dcmList[convertIdxs[j]]); + qsort(dcmSort, nConvert, sizeof(struct TDCMsort), compareTDCMsort); //sort based on series and image numbers.... + if (opts->isVerbose) + nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); + else + nConvert = removeDuplicates(nConvert, dcmSort); + int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, dti4D); + if (ret == EXIT_SUCCESS) + nConvertTotal += nConvert; + else + convertError = true; + free(dcmSort); + if (opts->isProgress) + progressPct = reportProgress(progressPct, kStage1Frac + kStage2Frac + (kStage3Frac * (float)nConvertTotal / (float)nDcm)); //proportion correct, 0..100 + } + free(convertIdxs); + free(crcSort); +#endif +#ifdef USING_R + } +#endif +#ifdef myTimer + if (opts->isProgress > 1) + printMessage("Stage 3 (Convert 2D and 3D images) required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC); +#endif + if (opts->isProgress) + progressPct = reportProgress(progressPct, 1); //proportion correct, 0..100 + free(dcmList); + free(dti4D); + freeNameList(nameList); + if (convertError) { + if (nConvertTotal == 0) + return EXIT_FAILURE; //nothing converted + printError("Converted %d of %zu files\n", nConvertTotal, nDcm); + //printError("Converted %d of %lu files\n", nConvertTotal, nDcm); + return kEXIT_SOME_OK_SOME_BAD; //partial failure + } + if (nConvertTotal == 0) { + printMessage("No valid DICOM images were found\n"); //we may have found valid DICOM files but they are not DICOM images + return kEXIT_NO_VALID_FILES_FOUND; + } + return EXIT_SUCCESS; +} //nii_loadDirCore() + +int nii_loadDirOneDirAtATime(char *path, struct TDCMopts *opts, int maxDepth, int depth) { + //return kEXIT_NO_VALID_FILES_FOUND if no files in ANY sub folders + //return EXIT_FAILURE if ANY failure + //return EXIT_SUCCESS if no failures and at least one image converted + int ret = nii_loadDirCore(path, opts); + if (ret == EXIT_FAILURE) + return ret; + tinydir_dir dir; + tinydir_open(&dir, path); + while (dir.has_next) { + tinydir_file file; + file.is_dir = 0; //avoids compiler warning: this is set by tinydir_readfile + tinydir_readfile(&dir, &file); + char filename[768] = ""; + strcat(filename, path); + strcat(filename, kFileSep); + strcat(filename, file.name); + if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { + int retSub = nii_loadDirOneDirAtATime(filename, opts, maxDepth, depth + 1); + if (retSub == EXIT_FAILURE) + return retSub; + if (retSub == EXIT_SUCCESS) + ret = retSub; + } + tinydir_next(&dir); + } + tinydir_close(&dir); + return ret; +} + +int nii_loadDir(struct TDCMopts *opts) { + //Identifies all the DICOM files in a folder and its subfolders + if (strlen(opts->indir) < 1) { + printMessage("No input\n"); + return EXIT_FAILURE; + } + char indir[512]; + strcpy(indir, opts->indir); + bool isFile = is_fileNotDir(indir); + if (isFile) //if user passes ~/dicom/mr1.dcm we will look at all files in ~/dicom + dropFilenameFromPath(opts->indir); + dropTrailingFileSep(opts->indir); + if (!is_dir(opts->indir, true)) { + printError("Input folder invalid: %s\n", opts->indir); + return kEXIT_INPUT_FOLDER_INVALID; + } +#ifdef USING_R + // Full file paths are only used by R/divest when reorganising DICOM files + if (opts->isRenameNotConvert) { +#endif + if (strlen(opts->outdir) < 1) { + strcpy(opts->outdir, opts->indir); + } else + dropTrailingFileSep(opts->outdir); + if (!is_dir(opts->outdir, true)) { +#ifdef myUseInDirIfOutDirUnavailable + printWarning("Output folder invalid %s will try %s\n", opts->outdir, opts->indir); + strcpy(opts->outdir, opts->indir); +#else + printError("Output folder invalid: %s\n", opts->outdir); + return kEXIT_OUTPUT_FOLDER_INVALID; +#endif + } + //check file permissions + if ((opts->isCreateBIDS != false) || (opts->isOnlyBIDS != true)) { //output files expected: either BIDS or images + int w = access(opts->outdir, W_OK); + if (w != 0) { +#ifdef USE_CWD_IF_OUTDIR_NO_WRITE + char outdir[512]; + strcpy(outdir, opts->outdir); + strcpy(opts->outdir, opts->indir); + w = access(opts->outdir, W_OK); + if (w != 0) { + printError("Unable to write to output folder: %s\n", outdir); + return kEXIT_OUTPUT_FOLDER_READ_ONLY; + } else + printWarning("Writing to working directory, unable to write to output folder: %s\n", outdir); +#else + printError("Unable to write to output folder: %s\n", opts->outdir); + return kEXIT_OUTPUT_FOLDER_READ_ONLY; +#endif + } + } +#ifdef USING_R + } +#endif + getFileNameX(opts->indirParent, opts->indir, 512); +#ifndef USING_R + if (isFile && ((isExt(indir, ".v")))) + return convert_foreign(indir, *opts); +#endif + if (isFile && ((isExt(indir, ".par")) || (isExt(indir, ".rec")))) { + char pname[512], rname[512]; + strcpy(pname, indir); + strcpy(rname, indir); + changeExt(pname, "PAR"); + changeExt(rname, "REC"); +#ifndef _MSC_VER //Linux is case sensitive, #include + if (access(rname, F_OK) != 0) + changeExt(rname, "rec"); + if (access(pname, F_OK) != 0) + changeExt(pname, "par"); +#endif + if (is_fileNotDir(rname) && is_fileNotDir(pname)) { + //strcpy(opts->indir, pname); //set to original file name, not path + return convert_parRec(pname, *opts); + }; + } + if (isFile && (opts->isOnlySingleFile) && isExt(indir, ".txt")) { + strcpy(opts->indir, indir); + return nii_loadDirCore(opts->indir, opts); + } + if (opts->isRenameNotConvert) { + int nConvert = searchDirRenameDICOM(opts->indir, opts->dirSearchDepth, 0, opts); + if (nConvert < 0) + return kEXIT_RENAME_ERROR; +#ifdef USING_R + printMessage("Renamed %d DICOMs\n", nConvert); +#else + printMessage("Converted %d DICOMs\n", nConvert); +#endif + return EXIT_SUCCESS; + } + if ((isFile) && (opts->isOnlySingleFile)) + return singleDICOM(opts, indir); + if (opts->isOneDirAtATime) { + int maxDepth = opts->dirSearchDepth; + opts->dirSearchDepth = 0; + strcpy(indir, opts->indir); + return nii_loadDirOneDirAtATime(indir, opts, maxDepth, 0); + } else + return nii_loadDirCore(opts->indir, opts); +} // nii_loadDir() + +#if defined(_WIN64) || defined(_WIN32) || defined(USING_R) +#else //UNIX, not R + +int findpathof(char *pth, const char *exe) { + //Find executable by searching the PATH environment variable. + // http://www.linuxquestions.org/questions/programming-9/get-full-path-of-a-command-in-c-117965/ + char *searchpath; + char *beg, *end; + int stop, found; + size_t len; + if (strchr(exe, '/') != NULL) { + if (realpath(exe, pth) == NULL) + return 0; + return is_exe(pth); + } + searchpath = getenv("PATH"); + if (searchpath == NULL) + return 0; + if (strlen(searchpath) <= 0) + return 0; + beg = searchpath; + stop = 0; + found = 0; + do { + end = strchr(beg, ':'); + if (end == NULL) { + len = strlen(beg); + if (len == 0) + return 0; + //gcc 8.1 warning: specified bound depends on the length of the source argument + //https://developers.redhat.com/blog/2018/05/24/detecting-string-truncation-with-gcc-8/ + //strncpy(pth, beg, len); + strcpy(pth, beg); + stop = 1; + } else { + strncpy(pth, beg, end - beg); + pth[end - beg] = '\0'; + len = end - beg; + } + //gcc8.1 warning: specified bound depends on the length of the source argument + //if (pth[len - 1] != '/') strncat(pth, "/", 1); + if (pth[len - 1] != '/') + strcat(pth, "/"); + strncat(pth, exe, PATH_MAX - len); + found = is_exe(pth); + if (!stop) + beg = end + 1; + } while (!stop && !found); + if (!found) + strcpy(pth, ""); + return found; +} +#endif + +#ifndef USING_R +void readFindPigz(struct TDCMopts *opts, const char *argv[]) { +#if defined(_WIN64) || defined(_WIN32) + strcpy(opts->pigzname, "pigz.exe"); + if (!is_exe(opts->pigzname)) { +#if defined(__APPLE__) +#ifdef myDisableZLib + printMessage("Compression requires %s in the same folder as the executable http://macappstore.org/pigz/\n", opts->pigzname); +#else //myUseZLib + if (opts->isVerbose > 0) + printMessage("Compression will be faster with %s in the same folder as the executable http://macappstore.org/pigz/\n", opts->pigzname); +#endif + strcpy(opts->pigzname, ""); +#else +#ifdef myDisableZLib + printMessage("Compression requires %s in the same folder as the executable\n", opts->pigzname); +#else //myUseZLib + if (opts->isVerbose > 0) + printMessage("Compression will be faster with %s in the same folder as the executable\n", opts->pigzname); +#endif + strcpy(opts->pigzname, ""); +#endif + } else + strcpy(opts->pigzname, ".\\pigz"); //drop +#else + char str[PATH_MAX]; + //possible pigz names + const char *names[] = { + "pigz", + "pigz_mricron", + "pigz_afni", + }; +#define n_nam (sizeof(names) / sizeof(const char *)) + for (int n = 0; n < (int)n_nam; n++) { + if (findpathof(str, names[n])) { + strcpy(opts->pigzname, str); + //printMessage("Found pigz: %s\n", str); + return; + } + } + //possible pigz paths + const char *pths[] = { + "/usr/local/bin/", + "/usr/bin/", + }; +#define n_pth (sizeof(pths) / sizeof(const char *)) + char exepth[PATH_MAX]; + strcpy(exepth, argv[0]); + dropFilenameFromPath(exepth); //, opts.pigzname); + char appendChar[2] = {"a"}; + appendChar[0] = kPathSeparator; + if (exepth[strlen(exepth) - 1] != kPathSeparator) + strcat(exepth, appendChar); + //see if pigz in any path + for (int n = 0; n < (int)n_nam; n++) { + //printf ("%d: %s\n", i, names[n]); + for (int p = 0; p < (int)n_pth; p++) { + strcpy(str, pths[p]); + strcat(str, names[n]); + if (is_exe(str)) + goto pigzFound; + } //p + //check exepth + strcpy(str, exepth); + strcat(str, names[n]); + if (is_exe(str)) + goto pigzFound; + } //n + //Failure: +#if defined(__APPLE__) +#ifdef myDisableZLib + printMessage("Compression requires 'pigz' to be installed http://macappstore.org/pigz/\n"); +#else //myUseZLib + if (opts->isVerbose > 0) + printMessage("Compression will be faster with 'pigz' installed http://macappstore.org/pigz/\n"); +#endif +#else //if APPLE else ... +#ifdef myDisableZLib + printMessage("Compression requires 'pigz' to be installed\n"); +#else //myUseZLib + if (opts->isVerbose > 0) + printMessage("Compression will be faster with 'pigz' installed\n"); +#endif +#endif + return; +pigzFound: //Success + strcpy(opts->pigzname, str); +//printMessage("Found pigz: %s\n", str); +#endif +} //readFindPigz() +#endif + +void setDefaultOpts(struct TDCMopts *opts, const char *argv[]) { //either "setDefaultOpts(opts,NULL)" or "setDefaultOpts(opts,argv)" where argv[0] is path to search + strcpy(opts->pigzname, ""); +#ifndef USING_R + if (argv != NULL) + readFindPigz(opts, argv); +#endif +#ifdef myEnableJasper + opts->compressFlag = kCompressYes; //JASPER for JPEG2000 +#else +#ifdef myDisableOpenJPEG + opts->compressFlag = kCompressNone; //no decompressor +#else + opts->compressFlag = kCompressYes; //OPENJPEG for JPEG2000 +#endif +#endif + //printMessage("%d %s\n",opts->compressFlag, opts->compressname); + strcpy(opts->indir, ""); + strcpy(opts->outdir, ""); + strcpy(opts->imageComments, ""); + opts->isOnlySingleFile = false; //convert all files in a directory, not just a single file + opts->isOneDirAtATime = false; + opts->isRenameNotConvert = false; + opts->isForceStackSameSeries = 2; //automatic: stack CTs, do not stack MRI + opts->isForceStackDCE = true; + opts->isIgnoreSeriesInstanceUID = false; + opts->isIgnoreDerivedAnd2D = false; + opts->isForceOnsetTimes = true; + opts->isPhilipsFloatNotDisplayScaling = true; + opts->isCrop = false; + opts->isRotate3DAcq = true; + opts->isGz = false; + opts->isSaveNativeEndian = true; + opts->isAddNamePostFixes = true; //e.g. "_e2" added for second echo + opts->isTestx0021x105E = false; //GE test slice times stored in 0021,105E + opts->isIgnoreTriggerTimes = false; + opts->saveFormat = kSaveFormatNIfTI; + opts->isPipedGz = false; //e.g. pipe data directly to pigz instead of saving uncompressed to disk + opts->isSave3D = false; + opts->dirSearchDepth = 5; + opts->isProgress = 0; + opts->nameConflictBehavior = kNAME_CONFLICT_ADD_SUFFIX; +#ifdef myDisableZLib + opts->gzLevel = 6; +#else + opts->gzLevel = MZ_DEFAULT_LEVEL; //-1; +#endif + //opts->isMaximize16BitRange = kMaximize16BitRange_False; //e.g. if INT16 image has range 0..500 scale to be 0..50000 with hdr.scl_slope = hdr.scl_slope * 0.01 + opts->isMaximize16BitRange = kMaximize16BitRange_Raw; //future version will use this option as default: preserve UINT16 even if it can be losslessly converted to INT16 + opts->isFlipY = true; //false: images in raw DICOM orientation, true: image rows flipped to cartesian coordinates + opts->isRGBplanar = false; //false for NIfTI (RGBRGB...), true for Analyze (RRR..RGGG..GBBB..B) + opts->isCreateBIDS = true; + opts->isOnlyBIDS = false; + opts->isSortDTIbyBVal = false; +#ifdef myNoAnonymizeBIDS + opts->isAnonymizeBIDS = false; +#else + opts->isAnonymizeBIDS = true; +#endif + opts->isCreateText = false; +#ifdef myDebug + opts->isVerbose = true; +#else + opts->isVerbose = false; +#endif + opts->isTiltCorrect = true; + opts->numSeries = 0; + memset(opts->seriesNumber, 0, sizeof(opts->seriesNumber)); + strcpy(opts->filename, "%f_%p_%t_%s"); +} // setDefaultOpts() + +#if defined(_WIN64) || defined(_WIN32) +//windows has unusual file permissions for many users - lets save preferences to the registry +void saveIniFile(struct TDCMopts opts) { + HKEY hKey; + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\dcm2nii", 0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS) { + RegCloseKey(hKey); + return; + } + printMessage("Saving defaults to registry\n"); + DWORD dwValue = opts.isGz; + RegSetValueExA(hKey, "isGZ", 0, REG_DWORD, reinterpret_cast(&dwValue), sizeof(dwValue)); + dwValue = opts.isMaximize16BitRange; + RegSetValueExA(hKey, "isMaximize16BitRange", 0, REG_DWORD, reinterpret_cast(&dwValue), sizeof(dwValue)); + RegSetValueExA(hKey, "filename", 0, REG_SZ, (LPBYTE)opts.filename, strlen(opts.filename) + 1); + RegCloseKey(hKey); +} //saveIniFile() + +void readIniFile(struct TDCMopts *opts, const char *argv[]) { + setDefaultOpts(opts, argv); + HKEY hKey; + DWORD vSize = 0; + DWORD dwDataType = 0; + DWORD dwValue = 0; + //RegOpenKeyEx(RegOpenKeyEx, key, 0, accessRights, keyHandle); + //if(RegOpenKeyEx(HKEY_CURRENT_USER,(WCHAR)"Software\\dcm2nii", 0, KEY_QUERY_VALUE,&hKey) != ERROR_SUCCESS) { + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\dcm2nii", 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS) { + RegCloseKey(hKey); + return; + } + vSize = sizeof(dwValue); + //if(RegQueryValueExA(hKey,"isGZ", 0, (LPDWORD )&dwDataType, (&dwValue), &vSize) == ERROR_SUCCESS) + if (RegQueryValueExA(hKey, "isGZ", 0, (LPDWORD)&dwDataType, reinterpret_cast(&dwValue), &vSize) == ERROR_SUCCESS) + opts->isGz = dwValue; + if (RegQueryValueExA(hKey, "isMaximize16BitRange", 0, (LPDWORD)&dwDataType, reinterpret_cast(&dwValue), &vSize) == ERROR_SUCCESS) + opts->isMaximize16BitRange = dwValue; + vSize = 512; + char buffer[512]; + if (RegQueryValueExA(hKey, "filename", 0, NULL, (LPBYTE)buffer, &vSize) == ERROR_SUCCESS) + strcpy(opts->filename, buffer); + RegCloseKey(hKey); +} //readIniFile() + +#else +//for Unix we will save preferences in a hidden text file in the home directory +#define STATUSFILENAME "/.dcm2nii.ini" + +void readIniFile(struct TDCMopts *opts, const char *argv[]) { + setDefaultOpts(opts, argv); + sprintf(opts->optsname, "%s%s", getenv("HOME"), STATUSFILENAME); + FILE *fp = fopen(opts->optsname, "r"); + if (fp == NULL) + return; + char Setting[20], Value[255]; + //while ( fscanf(fp, "%[^=]=%s\n", Setting, Value) == 2 ) { + //while ( fscanf(fp, "%[^=]=%s\n", Setting, Value) == 2 ) { + while (fscanf(fp, "%[^=]=%[^\n]\n", Setting, Value) == 2) { + //printMessage(">%s<->'%s'\n",Setting,Value); + if (strcmp(Setting, "isGZ") == 0) + opts->isGz = atoi(Value); + if (strcmp(Setting, "isMaximize16BitRange") == 0) + opts->isMaximize16BitRange = atoi(Value); + else if (strcmp(Setting, "isBIDS") == 0) + opts->isCreateBIDS = atoi(Value); + else if (strcmp(Setting, "filename") == 0) + strcpy(opts->filename, Value); + } + fclose(fp); +} // readIniFile() + +void saveIniFile(struct TDCMopts opts) { + FILE *fp = fopen(opts.optsname, "w"); + //printMessage("%s\n",localfilename); + if (fp == NULL) + return; + printMessage("Saving defaults file %s\n", opts.optsname); + fprintf(fp, "isGZ=%d\n", opts.isGz); + fprintf(fp, "isMaximize16BitRange=%d\n", opts.isMaximize16BitRange); + fprintf(fp, "isBIDS=%d\n", opts.isCreateBIDS); + fprintf(fp, "filename=%s\n", opts.filename); + fclose(fp); +} //saveIniFile() + +#endif diff --git a/packages/dcm2niix/nii_dicom_batch.h b/packages/dcm2niix/nii_dicom_batch.h new file mode 100644 index 00000000000..01f2b945341 --- /dev/null +++ b/packages/dcm2niix/nii_dicom_batch.h @@ -0,0 +1,89 @@ +// +// + +#ifndef MRIpro_nii_batch_h +#define MRIpro_nii_batch_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include //requires VS 2015 or later +#include +#ifndef USING_R +#include "nifti1.h" +#endif +#include "nii_dicom.h" + +#ifdef USING_R + struct TDicomSeries { + std::string name; + TDICOMdata representativeData; + std::vector files; + }; +#endif + +#ifdef MGH_FREESURFER +typedef struct +{ + struct nifti_1_header hdr0; + + size_t imgsz; + unsigned char *imgM; + + struct TDICOMdata tdicomData; + + struct TDTI *tdti; + int numDti; +} MRIFSSTRUCT; + +MRIFSSTRUCT* nii_getMrifsStruct(); +void nii_clrMrifsStruct(); +#endif + +#define kNAME_CONFLICT_SKIP 0 //0 = write nothing for a file that exists with desired name +#define kNAME_CONFLICT_OVERWRITE 1 //1 = overwrite existing file with same name +#define kNAME_CONFLICT_ADD_SUFFIX 2 //default 2 = write with new suffix as a new file + +#define kMaximize16BitRange_False 0 //e.g. raw UINT16 values 0..4095 saved as INT16 (e.g. AFNI preserves INT16 "short", converts UINT16 to float32) +#define kMaximize16BitRange_True 1 //e.g. raw UINT16 values 0..4095 saved as 0..61425 UINT16 (SPM free precision) +#define kMaximize16BitRange_Raw 2 //e.g. raw UINT16 values 0..4095 saved as UINT16 (retains raw data type, AFNI would convert to float32) + //#define kMaximize16BitRange_Float32 3 //save 16-bit INT16 and UINT16 as FLOAT32 (AFNI will be happy, retain scale factors in Philips data where slope varies between slices) + +#define kSaveFormatNIfTI 0 +#define kSaveFormatNRRD 1 +#define kSaveFormatMGH 2 + +#define MAX_NUM_SERIES 16 + + struct TDCMopts { + bool isIgnoreTriggerTimes, isTestx0021x105E, isAddNamePostFixes, isSaveNativeEndian, isOneDirAtATime, isRenameNotConvert, isSave3D, isGz, isPipedGz, isFlipY, isCreateBIDS, isSortDTIbyBVal, isAnonymizeBIDS, isOnlyBIDS, isCreateText, isForceOnsetTimes,isIgnoreDerivedAnd2D, isPhilipsFloatNotDisplayScaling, isTiltCorrect, isRGBplanar, isOnlySingleFile, isForceStackDCE, isIgnoreSeriesInstanceUID, isRotate3DAcq, isCrop; + int saveFormat, isMaximize16BitRange, isForceStackSameSeries, nameConflictBehavior, isVerbose, isProgress, compressFlag, dirSearchDepth, gzLevel; //support for compressed data 0=none, + char filename[512], outdir[512], indir[512], pigzname[512], optsname[512], indirParent[512], imageComments[24]; + double seriesNumber[MAX_NUM_SERIES]; //requires double must store -1 (report but do not convert) as well as seriesUidCrc (uint32) + long numSeries; +#ifdef USING_R + bool isScanOnly; + void *imageList; + std::vector series; + + // Used when sorting a directory + std::vector sourcePaths; + std::vector targetPaths; + std::vector ignoredPaths; +#endif + }; + void saveIniFile (struct TDCMopts opts); + void setDefaultOpts (struct TDCMopts *opts, const char * argv[]); //either "setDefaultOpts(opts,NULL)" or "setDefaultOpts(opts,argv)" where argv[0] is path to search + void readIniFile (struct TDCMopts *opts, const char * argv[]); + int nii_saveNIIx(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts); + int nii_loadDir(struct TDCMopts *opts); + int nii_loadDirCore(char *indir, struct TDCMopts* opts); + void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename); + int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopts opts); + void nii_createDummyFilename(char * niiFilename, struct TDCMopts opts); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/dcm2niix/nii_foreign.cpp b/packages/dcm2niix/nii_foreign.cpp new file mode 100644 index 00000000000..5547a58c522 --- /dev/null +++ b/packages/dcm2niix/nii_foreign.cpp @@ -0,0 +1,457 @@ +#include "nii_foreign.h" +#include "nifti1.h" +#include "nifti1_io_core.h" +#include "nii_dicom.h" +#include "nii_dicom_batch.h" +//#include "nifti1_io_core.h" +#include "print.h" +#include +#include //requires VS 2015 or later +#include +#include +#include +#include +#ifdef _MSC_VER +#include +#define getcwd _getcwd +#define chdir _chrdir +#include "io.h" +#include +//#define snprintMessage _snprintMessage +//#define vsnprintMessage _vsnprintMessage +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#else +#include +#endif + +#ifndef Float32 +#define Float32 float +#endif +#ifndef uint32 +#define uint32 uint32_t +#endif + +#ifdef __GNUC__ +#define PACK(...) __VA_ARGS__ __attribute__((__packed__)) +#else +#define PACK(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop)) +#endif + +/*nii_readEcat7 2017 by Chris Rorden, BSD license + http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7_8h_source.html#l00060 + http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7r_8c_source.html#l00717 + http://xmedcon.sourcearchive.com/documentation/0.10.7-1build1/ecat7_8h_source.html + https://github.com/BIC-MNI/minc-tools/tree/master/conversion/ecattominc + https://github.com/nipy/nibabel/blob/ec4567fb09b4472c5a4bb9a13dbcc9eb0a63d875/nibabel/ecat.py +*/ + +void strClean(char *cString) { + int len = (int)strlen(cString); + if (len < 1) + return; + for (int i = 0; i < len; i++) { + char c = cString[i]; + if ((c < char(32)) || (c == char(127)) || (c == char(255))) + cString[i] = 0; + if ((c == ' ') || (c == ',') || (c == '^') || (c == '/') || (c == '\\') || (c == '%') || (c == '*')) + cString[i] = '_'; + } +} + +unsigned char *readEcat7(const char *fname, struct TDICOMdata *dcm, struct nifti_1_header *hdr, struct TDCMopts opts, bool isWarnIfNotEcat) { +//data type +#define ECAT7_BYTE 1 +#define ECAT7_VAXI2 2 +#define ECAT7_VAXI4 3 +#define ECAT7_VAXR4 4 +#define ECAT7_IEEER4 5 +#define ECAT7_SUNI2 6 +#define ECAT7_SUNI4 7 +//file types +//#define ECAT7_UNKNOWN 0 +#define ECAT7_2DSCAN 1 +#define ECAT7_IMAGE16 2 +#define ECAT7_ATTEN 3 +#define ECAT7_2DNORM 4 +#define ECAT7_POLARMAP 5 +#define ECAT7_VOLUME8 6 +#define ECAT7_VOLUME16 7 +#define ECAT7_PROJ 8 +#define ECAT7_PROJ16 9 +#define ECAT7_IMAGE8 10 +#define ECAT7_3DSCAN 11 +#define ECAT7_3DSCAN8 12 +#define ECAT7_3DNORM 13 +#define ECAT7_3DSCANFIT 14 + PACK(typedef struct { + char magic[14], original_filename[32]; + uint16_t sw_version, system_type, file_type; + char serial_number[10]; + uint32 scan_start_time; + char isotope_name[8]; + Float32 isotope_halflife; + char radiopharmaceutical[32]; + Float32 gantry_tilt, gantry_rotation, bed_elevation, intrinsic_tilt; + int16_t wobble_speed, transm_source_type; + Float32 distance_scanned, transaxial_fov; + uint16_t angular_compression, coin_samp_mode, axial_samp_mode; + Float32 ecat_calibration_factor; + uint16_t calibration_unitS, calibration_units_type, compression_code; + char study_type[12], patient_id[16], accession_number[16], patient_name[32], patient_sex, patient_dexterity; + Float32 patient_age, patient_height, patient_weight; + uint32 patient_birth_date; + char physician_name[32], operator_name[32], study_description[32]; + uint16_t acquisition_type, patient_orientation; + char facility_name[20]; + uint16_t num_planes, num_frames, num_gates, num_bed_pos; + Float32 init_bed_position; + Float32 bed_position[15]; + Float32 plane_separation; + uint16_t lwr_sctr_thres, lwr_true_thres, upr_true_thres; + char user_process_code[10]; + uint16_t acquisition_mode; + Float32 bin_size, branching_fraction; + uint32 dose_start_time; + Float32 dosage, well_counter_corr_factor; + char data_units[32]; + uint16_t septa_state; + char fill[12]; + }) + ecat_main_hdr; + PACK(typedef struct { + int16_t data_type, num_dimensions, x_dimension, y_dimension, z_dimension; + Float32 x_offset, y_offset, z_offset, recon_zoom, scale_factor; + int16_t image_min, image_max; + Float32 x_pixel_size, y_pixel_size, z_pixel_size; + int32_t frame_duration, frame_start_time; + int16_t filter_code; + Float32 x_resolution, y_resolution, z_resolution, num_r_elements, num_angles, z_rotation_angle, decay_corr_fctr; + int32_t processing_code, gate_duration, r_wave_offset, num_accepted_beats; + Float32 filter_cutoff_frequenc, filter_resolution, filter_ramp_slope; + int16_t filter_order; + Float32 filter_scatter_fraction, filter_scatter_slope; + char annotation[40]; + Float32 mtx[9], rfilter_cutoff, rfilter_resolution; + int16_t rfilter_code, rfilter_order; + Float32 zfilter_cutoff, zfilter_resolution; + int16_t zfilter_code, zfilter_order; + Float32 mtx_1_4, mtx_2_4, mtx_3_4; + int16_t scatter_type, recon_type, recon_views, fill_cti[87], fill_user[49]; + }) + ecat_img_hdr; + PACK(typedef struct { + int32_t hdr[4], r[31][4]; + }) + ecat_list_hdr; + bool swapEndian = false; + size_t n; + FILE *f; + ecat_main_hdr mhdr; + f = fopen(fname, "rb"); + if (f) + n = fread(&mhdr, sizeof(mhdr), 1, f); + if (!f || n != 1) { + printMessage("Problem reading ECAT7 file!\n"); + fclose(f); + return NULL; + } + if ((mhdr.magic[0] != 'M') || (mhdr.magic[1] != 'A') || (mhdr.magic[2] != 'T') || (mhdr.magic[3] != 'R') || (mhdr.magic[4] != 'I') || (mhdr.magic[5] != 'X')) { + if (isWarnIfNotEcat) + printMessage("Signature not 'MATRIX' (ECAT7): '%s'\n", fname); + fclose(f); + return NULL; + } + swapEndian = mhdr.file_type > 255; + if (swapEndian) { + nifti_swap_2bytes(2, &mhdr.sw_version); + nifti_swap_2bytes(1, &mhdr.file_type); + //nifti_swap_2bytes(1, &mhdr.num_frames); + nifti_swap_4bytes(1, &mhdr.ecat_calibration_factor); + nifti_swap_4bytes(1, &mhdr.isotope_halflife); + nifti_swap_4bytes(2, &mhdr.dosage); + } + if ((mhdr.file_type < ECAT7_2DSCAN) || (mhdr.file_type > ECAT7_3DSCANFIT)) { + printMessage("Unknown ECAT file type %d\n", mhdr.file_type); + fclose(f); + return NULL; + } + //read list matrix + ecat_list_hdr lhdr; + fseek(f, 512, SEEK_SET); + size_t nRead = fread(&lhdr, sizeof(lhdr), 1, f); + if (nRead != 1) { + printMessage("Error reading ECAT file (list header)\n"); + fclose(f); + return NULL; + } + if (swapEndian) + nifti_swap_4bytes(128, &lhdr.hdr[0]); + //offset to first image + int img_StartBytes = lhdr.r[0][1] * 512; + //load image header for first image + fseek(f, img_StartBytes - 512, SEEK_SET); //image header is block immediately before image + ecat_img_hdr ihdr; + nRead = fread(&ihdr, sizeof(ihdr), 1, f); + if (nRead != 1) { + printMessage("Error reading ECAT file (image header)\n"); + fclose(f); + return NULL; + } + if (swapEndian) { + nifti_swap_2bytes(5, &ihdr.data_type); + nifti_swap_4bytes(5, &ihdr.x_offset); + nifti_swap_2bytes(2, &ihdr.image_min); + nifti_swap_4bytes(5, &ihdr.x_pixel_size); + nifti_swap_2bytes(1, &ihdr.filter_code); + nifti_swap_4bytes(14, &ihdr.x_resolution); + nifti_swap_2bytes(1, &ihdr.filter_order); + nifti_swap_4bytes(2, &ihdr.filter_scatter_fraction); + nifti_swap_4bytes(11, &ihdr.mtx); + nifti_swap_2bytes(2, &ihdr.rfilter_code); + nifti_swap_4bytes(2, &ihdr.zfilter_cutoff); + nifti_swap_2bytes(2, &ihdr.zfilter_code); + nifti_swap_4bytes(3, &ihdr.mtx_1_4); + nifti_swap_2bytes(3, &ihdr.scatter_type); + } + if ((ihdr.data_type != ECAT7_BYTE) && (ihdr.data_type != ECAT7_SUNI2) && (ihdr.data_type != ECAT7_SUNI4)) { + printMessage("Unknown or unsupported ECAT data type %d\n", ihdr.data_type); + fclose(f); + return NULL; + } + int bytesPerVoxel = 2; + if (ihdr.data_type == ECAT7_BYTE) + bytesPerVoxel = 1; + if (ihdr.data_type == ECAT7_SUNI4) + bytesPerVoxel = 4; + //next: read offsets for each volume: data not saved sequentially (each volume preceded by its own ecat_img_hdr) + int num_vol = 0; + bool isAbort = false; + bool isScaleFactorVaries = false; +#define kMaxVols 16000 + size_t *imgOffsets = (size_t *)malloc(sizeof(size_t) * (kMaxVols)); + float *imgSlopes = (float *)malloc(sizeof(float) * (kMaxVols)); + ecat_img_hdr ihdrN; + while ((lhdr.hdr[0] + lhdr.hdr[3]) == 31) { //while valid list + if (num_vol > 0) { //read the next list + fseek(f, 512 * (lhdr.hdr[1] - 1), SEEK_SET); + nRead = fread(&lhdr, 512, 1, f); + if (nRead != 1) { + printMessage("Error reading ECAT file (yet another list header)\n"); + fclose(f); + return NULL; + } + if (swapEndian) + nifti_swap_4bytes(128, &lhdr.hdr[0]); + } + if ((lhdr.hdr[0] + lhdr.hdr[3]) != 31) + break; //if valid list + if (lhdr.hdr[3] < 1) + break; + for (int k = 0; k < lhdr.hdr[3]; k++) { + //check images' ecat_img_hdr matches first + fseek(f, (lhdr.r[k][1] - 1) * 512, SEEK_SET); //image header is block immediately before image + nRead = fread(&ihdrN, sizeof(ihdrN), 1, f); + if (nRead != 1) { + printMessage("Error reading ECAT file (yet another image header)\n"); + fclose(f); + return NULL; + } + if (swapEndian) { + nifti_swap_2bytes(5, &ihdrN.data_type); + nifti_swap_4bytes(5, &ihdrN.x_offset); + nifti_swap_2bytes(2, &ihdrN.image_min); + nifti_swap_4bytes(5, &ihdrN.x_pixel_size); + nifti_swap_2bytes(1, &ihdrN.filter_code); + nifti_swap_4bytes(14, &ihdrN.x_resolution); + nifti_swap_2bytes(1, &ihdrN.filter_order); + nifti_swap_4bytes(2, &ihdrN.filter_scatter_fraction); + nifti_swap_4bytes(11, &ihdrN.mtx); + nifti_swap_2bytes(2, &ihdrN.rfilter_code); + nifti_swap_4bytes(2, &ihdrN.zfilter_cutoff); + nifti_swap_2bytes(2, &ihdrN.zfilter_code); + nifti_swap_4bytes(3, &ihdrN.mtx_1_4); + nifti_swap_2bytes(3, &ihdrN.scatter_type); + } + if (ihdr.scale_factor != ihdrN.scale_factor) + isScaleFactorVaries = true; + if ((ihdr.data_type != ihdrN.data_type) || (ihdr.x_dimension != ihdrN.x_dimension) || (ihdr.y_dimension != ihdrN.y_dimension) || (ihdr.z_dimension != ihdrN.z_dimension)) { + printError("Error: ECAT volumes have varying image dimensions\n"); + isAbort = true; + } + if (num_vol < kMaxVols) { + imgOffsets[num_vol] = (size_t)lhdr.r[k][1]; + imgSlopes[num_vol] = ihdrN.scale_factor; + } + num_vol++; + } + if ((lhdr.hdr[0] > 0) || (isAbort)) + break; //this list contains empty volumes: all lists have been read + } //read all image offsets + //report error reading image offsets + if ((num_vol < 1) || (isAbort) || (num_vol >= kMaxVols)) { + printMessage("Failure to extract ECAT7 images\n"); + if (num_vol >= kMaxVols) + printMessage("Increase kMaxVols"); + fclose(f); + free(imgOffsets); + free(imgSlopes); + return NULL; + } + if ((isScaleFactorVaries) && (bytesPerVoxel != 2)) { + printError("ECAT scale factor varies between volumes (check for updates) '%s'\n", fname); + fclose(f); + free(imgOffsets); + free(imgSlopes); + return NULL; + } + //load image data + unsigned char *img = NULL; + if ((isScaleFactorVaries) && (bytesPerVoxel == 2)) { //we need to convert volumes from 16-bit to 32-bit to preserve scaling factors + int num_vox = ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension; + size_t bytesPerVolumeIn = (size_t)(num_vox * bytesPerVoxel); //bytesPerVoxel == 2 + unsigned char *imgIn = (unsigned char *)malloc(bytesPerVolumeIn); + int16_t *img16i = (int16_t *)imgIn; + bytesPerVoxel = 4; + size_t bytesPerVolume = (size_t)(num_vox * bytesPerVoxel); + img = (unsigned char *)malloc((size_t)(bytesPerVolume * num_vol)); + float *img32 = (float *)img; + for (int v = 0; v < num_vol; v++) { + fseek(f, imgOffsets[v] * 512, SEEK_SET); + nRead = fread(&imgIn[0], 1, bytesPerVolumeIn, f); + if (nRead != bytesPerVolumeIn) { + printMessage("%zu Error reading ECAT file (offset %zu bytes %zu)\n", nRead, imgOffsets[v] * 512, bytesPerVolumeIn); + fclose(f); + return NULL; + } + if (swapEndian) + nifti_swap_2bytes(num_vox, imgIn); + int volOffset = v * num_vox; + float scale = imgSlopes[v] * mhdr.ecat_calibration_factor; + for (int i = 0; i < num_vox; i++) + img32[i + volOffset] = (img16i[i] * scale); + } + //we have applied the scale factors to the data, so eliminate them + ihdr.scale_factor = 1.0; + mhdr.ecat_calibration_factor = 1.0; + + } else { //if isScaleFactorVaries else simple conversion + size_t bytesPerVolume = ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * bytesPerVoxel; + img = (unsigned char *)malloc(bytesPerVolume * num_vol); + for (int v = 0; v < num_vol; v++) { + fseek(f, imgOffsets[v] * 512, SEEK_SET); + size_t sz = fread(&img[v * bytesPerVolume], 1, bytesPerVolume, f); + if (sz != bytesPerVolume) { + free(img); + return NULL; + } + } + if ((swapEndian) && (bytesPerVoxel == 2)) + nifti_swap_2bytes(ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * num_vol, img); + if ((swapEndian) && (bytesPerVoxel == 4)) + nifti_swap_4bytes(ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * num_vol, img); + } + printWarning("ECAT support VERY experimental (Spatial transforms unknown)\n"); + free(imgOffsets); + free(imgSlopes); + fclose(f); + //fill DICOM header + float timeBetweenVolumes = ihdr.frame_duration; + if (num_vol > 1) + timeBetweenVolumes = (float)(ihdrN.frame_start_time - ihdr.frame_start_time) / (float)(num_vol - 1); + //copy and clean strings (ECAT can use 0x0D as a string terminator) + strncpy(dcm->patientName, mhdr.patient_name, 32); + strncpy(dcm->patientID, mhdr.patient_id, 16); + strncpy(dcm->accessionNumber, mhdr.accession_number, 16); + strncpy(dcm->seriesDescription, mhdr.study_description, 32); + strncpy(dcm->protocolName, mhdr.study_type, 12); + strncpy(dcm->imageComments, mhdr.isotope_name, 8); + strncpy(dcm->procedureStepDescription, mhdr.radiopharmaceutical, 32); + strClean(dcm->patientName); + strClean(dcm->patientID); + strClean(dcm->accessionNumber); + strClean(dcm->seriesDescription); + strClean(dcm->protocolName); + strClean(dcm->imageComments); + strClean(dcm->procedureStepDescription); + dcm->ecat_dosage = mhdr.dosage; + dcm->ecat_isotope_halflife = mhdr.isotope_halflife; + if (opts.isVerbose) { + printMessage("ECAT7 details for '%s'\n", fname); + printMessage(" Software version %d\n", mhdr.sw_version); + printMessage(" System Type %d\n", mhdr.system_type); + printMessage(" Frame duration %dms\n", ihdr.frame_duration); + printMessage(" Time between volumes %gms\n", timeBetweenVolumes); + printMessage(" Patient name '%s'\n", dcm->patientName); + printMessage(" Patient ID '%s'\n", dcm->patientID); + printMessage(" Accession number '%s'\n", dcm->accessionNumber); + printMessage(" Study description '%s'\n", dcm->seriesDescription); + printMessage(" Study type '%s'\n", dcm->protocolName); + printMessage(" Isotope name '%s'\n", dcm->imageComments); + printMessage(" Isotope halflife %gs\n", mhdr.isotope_halflife); + printMessage(" Radiopharmaceutical '%s'\n", dcm->procedureStepDescription); + printMessage(" Dosage %gbequerels/cc\n", mhdr.dosage); + if (!isScaleFactorVaries) { + printMessage(" Scale factor %12.12g\n", ihdr.scale_factor); + printMessage(" ECAT calibration factor %8.12g\n", mhdr.ecat_calibration_factor); + } + printMessage(" NIfTI scale slope %12.12g\n", ihdr.scale_factor * mhdr.ecat_calibration_factor); + } + dcm->manufacturer = kMANUFACTURER_SIEMENS; + //dcm->manufacturersModelName = itoa(mhdr.system_type); + sprintf(dcm->manufacturersModelName, "%d", mhdr.system_type); + dcm->bitsAllocated = bytesPerVoxel * 8; + if (isScaleFactorVaries) + dcm->isFloat = true; + dcm->bitsStored = 15; //ensures 16-bit images saved as INT16 not UINT16 + dcm->samplesPerPixel = 1; + dcm->xyzMM[1] = ihdr.x_pixel_size * 10.0; //cm -> mm + dcm->xyzMM[2] = ihdr.y_pixel_size * 10.0; //cm -> mm + dcm->xyzMM[3] = ihdr.z_pixel_size * 10.0; //cm -> mm + dcm->TR = timeBetweenVolumes; + dcm->xyzDim[1] = ihdr.x_dimension; + dcm->xyzDim[2] = ihdr.y_dimension; + dcm->xyzDim[3] = ihdr.z_dimension; + dcm->xyzDim[4] = num_vol; + //create a NIfTI header + headerDcm2Nii(*dcm, hdr, false); + //here we mimic SPM's spatial starting estimate SForm + mat44 m44; + LOAD_MAT44(m44, -hdr->pixdim[1], 0.0f, 0.0f, ((float)dcm->xyzDim[1] - 2.0) / 2.0 * dcm->xyzMM[1], + 0.0f, -hdr->pixdim[2], 0.0f, ((float)dcm->xyzDim[2] - 2.0) / 2.0 * dcm->xyzMM[2], + 0.0f, 0.0f, -hdr->pixdim[3], ((float)dcm->xyzDim[3] - 2.0) / 2.0 * dcm->xyzMM[3]); + setQSForm(hdr, m44, false); + //make sure image does not include a spatial matrix + bool isMatrix = false; + for (int i = 0; i < 9; i++) + if (ihdr.mtx[i] != 0.0) + isMatrix = true; + if (isMatrix) + printWarning("ECAT volume appears to store spatial transformation matrix (please check for updates)\n"); + hdr->scl_slope = ihdr.scale_factor * mhdr.ecat_calibration_factor; + if (mhdr.gantry_tilt != 0.0) + printMessage("Warning: ECAT gantry tilt not supported %g\n", mhdr.gantry_tilt); + return img; +} + +int convert_foreign(const char *fn, struct TDCMopts opts) { + struct nifti_1_header hdr; + struct TDICOMdata dcm = clear_dicom_data(); + unsigned char *img = NULL; + img = readEcat7(fn, &dcm, &hdr, opts, false); //false: silent, do not report if file is not ECAT format + if (!img) + return EXIT_FAILURE; + char niiFilename[1024]; + int ret = nii_createFilename(dcm, niiFilename, opts); + if (ret != EXIT_SUCCESS) { + printError("Failed to save ECAT as '%s'\n", niiFilename); + return ret; + } + printMessage("Saving ECAT as '%s'\n", niiFilename); + //struct TDTI4D dti4D; + //nii_SaveBIDS(niiFilename, dcm, opts, &dti4D, &hdr, fn); + nii_SaveBIDS(niiFilename, dcm, opts, &hdr, fn); + ret = nii_saveNIIx(niiFilename, hdr, img, opts); + free(img); + return ret; +} // convert_foreign() diff --git a/packages/dcm2niix/nii_foreign.h b/packages/dcm2niix/nii_foreign.h new file mode 100644 index 00000000000..9249a4c99aa --- /dev/null +++ b/packages/dcm2niix/nii_foreign.h @@ -0,0 +1,19 @@ +//Attempt to open non-DICOM image + +#ifndef _NII_FOREIGN_ +#define _NII_FOREIGN_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "nii_dicom_batch.h" + +//int open_foreign (const char *fn); +int convert_foreign (const char *fn, struct TDCMopts opts); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/dcm2niix/nii_ortho.cpp b/packages/dcm2niix/nii_ortho.cpp new file mode 100644 index 00000000000..47ca13e7e7e --- /dev/null +++ b/packages/dcm2niix/nii_ortho.cpp @@ -0,0 +1,411 @@ +#ifndef USING_R +#include "nifti1.h" +#endif +#include "nifti1_io_core.h" +#include "nii_ortho.h" +#include +#include +#include +#include //requires VS 2015 or later +#include +#include +#include +#include +#include +//#include +#include +#ifndef _MSC_VER + +#include + +#endif +//#define MY_DEBUG //verbose text reporting + +#include "print.h" + +typedef struct { + int v[3]; +} vec3i; + +mat33 matDotMul33(mat33 a, mat33 b) +// in Matlab: ret = a'.*b +{ + mat33 ret; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + ret.m[i][j] = a.m[i][j] * b.m[j][i]; + } + } + return ret; +} + +mat33 matMul33(mat33 a, mat33 b) +// mult = a * b +{ + mat33 mult; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + mult.m[j][i] = 0; + for (int k = 0; k < 3; k++) + mult.m[j][i] += a.m[j][k] * b.m[k][i]; + } + } + return mult; +} + +float getOrthoResidual(mat33 orig, mat33 transform) { + mat33 mat = matDotMul33(orig, transform); + float ret = 0; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + ret = ret + (mat.m[i][j]); + } + } + return ret; +} + +mat33 getBestOrient(mat44 R, vec3i flipVec) +//flipVec reports flip: [1 1 1]=no flips, [-1 1 1] flip X dimension +{ + mat33 ret, newmat, orig; + LOAD_MAT33(orig, R.m[0][0], R.m[0][1], R.m[0][2], + R.m[1][0], R.m[1][1], R.m[1][2], + R.m[2][0], R.m[2][1], R.m[2][2]); + float best = 0; //FLT_MAX; + float newval; + for (int rot = 0; rot < 6; rot++) { //6 rotations + switch (rot) { + case 0: + LOAD_MAT33(newmat, flipVec.v[0], 0, 0, 0, flipVec.v[1], 0, 0, 0, flipVec.v[2]); + break; + case 1: + LOAD_MAT33(newmat, flipVec.v[0], 0, 0, 0, 0, flipVec.v[1], 0, flipVec.v[2], 0); + break; + case 2: + LOAD_MAT33(newmat, 0, flipVec.v[0], 0, flipVec.v[1], 0, 0, 0, 0, flipVec.v[2]); + break; + case 3: + LOAD_MAT33(newmat, 0, flipVec.v[0], 0, 0, 0, flipVec.v[1], flipVec.v[2], 0, 0); + break; + case 4: + LOAD_MAT33(newmat, 0, 0, flipVec.v[0], flipVec.v[1], 0, 0, 0, flipVec.v[2], 0); + break; + case 5: + LOAD_MAT33(newmat, 0, 0, flipVec.v[0], 0, flipVec.v[1], 0, flipVec.v[2], 0, 0); + break; + } + newval = getOrthoResidual(orig, newmat); + if (newval > best) { + best = newval; + ret = newmat; + } + } + return ret; +} + +bool isMat44Canonical(mat44 R) +//returns true if diagonals >0 and all others =0 +// no rotation is necessary - already in perfect orthogonal alignment +{ + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if ((i == j) && (R.m[i][j] <= 0)) + return false; + if ((i != j) && (R.m[i][j] != 0)) + return false; + } //j + } //i + return true; +} + +vec3i setOrientVec(mat33 m) +// Assumes isOrthoMat NOT computed on INVERSE, hence return INVERSE of solution... +//e.g. [-1,2,3] means reflect x axis, [2,1,3] means swap x and y dimensions +{ + vec3i ret = {{0, 0, 0}}; + //mat33 m = {-1,0,0, 0,1,0, 0,0,1}; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (m.m[i][j] > 0) + ret.v[j] = i + 1; + if (m.m[i][j] < 0) + ret.v[j] = -(i + 1); + } //j + } //i + return ret; +} + +mat44 setMat44Vec(mat33 m33, vec3 Translations) +//convert a 3x3 rotation matrix to a 4x4 matrix where the last column stores translations and the last row is 0 0 0 1 +{ + mat44 m44; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + m44.m[i][j] = m33.m[i][j]; + } + } + m44.m[0][3] = Translations.v[0]; + m44.m[1][3] = Translations.v[1]; + m44.m[2][3] = Translations.v[2]; + m44.m[3][0] = 0; + m44.m[3][1] = 0; + m44.m[3][2] = 0; + m44.m[3][3] = 1; + return m44; +} + +mat44 sFormMat(struct nifti_1_header *h) { + mat44 s; + s.m[0][0] = h->srow_x[0]; + s.m[0][1] = h->srow_x[1]; + s.m[0][2] = h->srow_x[2]; + s.m[0][3] = h->srow_x[3]; + s.m[1][0] = h->srow_y[0]; + s.m[1][1] = h->srow_y[1]; + s.m[1][2] = h->srow_y[2]; + s.m[1][3] = h->srow_y[3]; + s.m[2][0] = h->srow_z[0]; + s.m[2][1] = h->srow_z[1]; + s.m[2][2] = h->srow_z[2]; + s.m[2][3] = h->srow_z[3]; + s.m[3][0] = 0; + s.m[3][1] = 0; + s.m[3][2] = 0; + s.m[3][3] = 1; + return s; +} + +void mat2sForm(struct nifti_1_header *h, mat44 s) { + h->srow_x[0] = s.m[0][0]; + h->srow_x[1] = s.m[0][1]; + h->srow_x[2] = s.m[0][2]; + h->srow_x[3] = s.m[0][3]; + h->srow_y[0] = s.m[1][0]; + h->srow_y[1] = s.m[1][1]; + h->srow_y[2] = s.m[1][2]; + h->srow_y[3] = s.m[1][3]; + h->srow_z[0] = s.m[2][0]; + h->srow_z[1] = s.m[2][1]; + h->srow_z[2] = s.m[2][2]; + h->srow_z[3] = s.m[2][3]; +} + +size_t *orthoOffsetArray(int dim, int stepBytesPerVox) { + //return lookup table of length dim with values incremented by stepBytesPerVox + // e.g. if Dim=10 and stepBytes=2: 0,2,4..18, is stepBytes=-2 18,16,14...0 + size_t *lut = (size_t *)malloc(dim * sizeof(size_t)); + if (stepBytesPerVox > 0) + lut[0] = 0; + else + lut[0] = -stepBytesPerVox * (dim - 1); + if (dim > 1) + for (int i = 1; i < dim; i++) + lut[i] = lut[i - 1] + (size_t)stepBytesPerVox; + return lut; +} //orthoOffsetArray() + +//void reOrientImg( unsigned char * restrict img, vec3i outDim, vec3i outInc, int bytePerVox, int nvol) { +void reOrientImg(unsigned char *img, vec3i outDim, vec3i outInc, int bytePerVox, int nvol) { + //reslice data to new orientation + //generate look up tables + size_t *xLUT = orthoOffsetArray(outDim.v[0], bytePerVox * outInc.v[0]); + size_t *yLUT = orthoOffsetArray(outDim.v[1], bytePerVox * outInc.v[1]); + size_t *zLUT = orthoOffsetArray(outDim.v[2], bytePerVox * outInc.v[2]); + //convert data + size_t bytePerVol = bytePerVox * outDim.v[0] * outDim.v[1] * outDim.v[2]; //number of voxels in spatial dimensions [1,2,3] + size_t o = 0; //output address + uint8_t *inbuf = (uint8_t *)malloc(bytePerVol); //we convert 1 volume at a time + uint8_t *outbuf = (uint8_t *)img; //source image + for (int vol = 0; vol < nvol; vol++) { + memcpy(&inbuf[0], &outbuf[vol * bytePerVol], bytePerVol); //copy source volume + for (int z = 0; z < outDim.v[2]; z++) + for (int y = 0; y < outDim.v[1]; y++) + for (int x = 0; x < outDim.v[0]; x++) { + memcpy(&outbuf[o], &inbuf[xLUT[x] + yLUT[y] + zLUT[z]], bytePerVox); + o = o + bytePerVox; + } //for each x + } //for each volume + //free arrays + free(inbuf); + free(xLUT); + free(yLUT); + free(zLUT); +} //reOrientImg + +unsigned char *reOrient(unsigned char *img, struct nifti_1_header *h, vec3i orientVec, mat33 orient, vec3 minMM) +//e.g. [-1,2,3] means reflect x axis, [2,1,3] means swap x and y dimensions +{ + size_t nvox = h->dim[1] * h->dim[2] * h->dim[3]; + if (nvox < 1) + return img; + vec3i outDim = {{0, 0, 0}}; + vec3i outInc = {{0, 0, 0}}; + for (int i = 0; i < 3; i++) { //set dimension, pixdim and + outDim.v[i] = h->dim[abs(orientVec.v[i])]; + if (abs(orientVec.v[i]) == 1) + outInc.v[i] = 1; + if (abs(orientVec.v[i]) == 2) + outInc.v[i] = h->dim[1]; + if (abs(orientVec.v[i]) == 3) + outInc.v[i] = h->dim[1] * h->dim[2]; + if (orientVec.v[i] < 0) + outInc.v[i] = -outInc.v[i]; //flip + } //for each dimension + int nvol = 1; //convert all non-spatial volumes from source to destination + for (int vol = 4; vol < 8; vol++) { + if (h->dim[vol] > 1) + nvol = nvol * h->dim[vol]; + } + reOrientImg(img, outDim, outInc, h->bitpix / 8, nvol); + //now change the header.... + vec3 outPix = {{h->pixdim[abs(orientVec.v[0])], h->pixdim[abs(orientVec.v[1])], h->pixdim[abs(orientVec.v[2])]}}; + for (int i = 0; i < 3; i++) { + h->dim[i + 1] = outDim.v[i]; + h->pixdim[i + 1] = outPix.v[i]; + } + mat44 s = sFormMat(h); + mat33 mat; //computer transform + LOAD_MAT33(mat, s.m[0][0], s.m[0][1], s.m[0][2], + s.m[1][0], s.m[1][1], s.m[1][2], + s.m[2][0], s.m[2][1], s.m[2][2]); + mat = matMul33(mat, orient); + s = setMat44Vec(mat, minMM); //add offset + mat2sForm(h, s); + h->qform_code = h->sform_code; //apply to the quaternion as well + float dumdx, dumdy, dumdz; + nifti_mat44_to_quatern(s, &h->quatern_b, &h->quatern_c, &h->quatern_d, &h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz, &h->pixdim[0]); + return img; +} //reOrient() + +float getDistance(vec3 v, vec3 min) +//scalar distance between two 3D points - Pythagorean theorem +{ + return sqrt(pow((v.v[0] - min.v[0]), 2) + pow((v.v[1] - min.v[1]), 2) + pow((v.v[2] - min.v[2]), 2)); +} + +vec3 xyz2mm(mat44 R, vec3 v) { + vec3 ret; + for (int i = 0; i < 3; i++) { + ret.v[i] = ((R.m[i][0] * v.v[0]) + (R.m[i][1] * v.v[1]) + (R.m[i][2] * v.v[2]) + R.m[i][3]); + } + return ret; +} + +vec3 minCornerFlip(struct nifti_1_header *h, vec3i *flipVec) +//orthogonal rotations and reflections applied as 3x3 matrices will cause the origin to shift +// a simple solution is to first compute the most left, posterior, inferior voxel in the source image +// this voxel will be at location i,j,k = 0,0,0, so we can simply use this as the offset for the final 4x4 matrix... +{ + int i, j, minIndex; + vec3i flipVecs[8]; + vec3 corner[8], min; + mat44 s = sFormMat(h); + for (int i = 0; i < 8; i++) { + if (i & 1) + flipVecs[i].v[0] = -1; + else + flipVecs[i].v[0] = 1; + if (i & 2) + flipVecs[i].v[1] = -1; + else + flipVecs[i].v[1] = 1; + if (i & 4) + flipVecs[i].v[2] = -1; + else + flipVecs[i].v[2] = 1; + corner[i] = setVec3(0, 0, 0); //assume no reflections + if ((flipVecs[i].v[0]) < 1) + corner[i].v[0] = h->dim[1] - 1; //reflect X + if ((flipVecs[i].v[1]) < 1) + corner[i].v[1] = h->dim[2] - 1; //reflect Y + if ((flipVecs[i].v[2]) < 1) + corner[i].v[2] = h->dim[3] - 1; //reflect Z + corner[i] = xyz2mm(s, corner[i]); + } + //find extreme edge from ALL corners.... + min = corner[0]; + for (i = 1; i < 8; i++) { + for (j = 0; j < 3; j++) { + if (corner[i].v[j] < min.v[j]) + min.v[j] = corner[i].v[j]; + } + } + float dx; //observed distance from corner + float min_dx = getDistance(corner[0], min); + minIndex = 0; //index of corner closest to min + //see if any corner is closer to absmin than the first one... + for (i = 1; i < 8; i++) { + dx = getDistance(corner[i], min); + if (dx < min_dx) { + min_dx = dx; + minIndex = i; + } + } + min = corner[minIndex]; //this is the single corner closest to min from all + *flipVec = flipVecs[minIndex]; + return min; +} + +#ifdef MY_DEBUG +void reportMat44o(char *str, mat44 A) { + printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n", str, + A.m[0][0], A.m[0][1], A.m[0][2], A.m[0][3], + A.m[1][0], A.m[1][1], A.m[1][2], A.m[1][3], + A.m[2][0], A.m[2][1], A.m[2][2], A.m[2][3]); +} +#endif + +unsigned char *nii_setOrtho(unsigned char *img, struct nifti_1_header *h) { + if ((h->dim[1] < 1) || (h->dim[2] < 1) || (h->dim[3] < 1)) + return img; + if ((h->sform_code == NIFTI_XFORM_UNKNOWN) && (h->qform_code != NIFTI_XFORM_UNKNOWN)) { //only q-form provided + mat44 q = nifti_quatern_to_mat44(h->quatern_b, h->quatern_c, h->quatern_d, + h->qoffset_x, h->qoffset_y, h->qoffset_z, + h->pixdim[1], h->pixdim[2], h->pixdim[3], h->pixdim[0]); + mat2sForm(h, q); //convert q-form to s-form + h->sform_code = h->qform_code; + } + if (h->sform_code == NIFTI_XFORM_UNKNOWN) { +#ifdef MY_DEBUG + printMessage("No Q or S spatial transforms - assuming canonical orientation"); +#endif + return img; + } + mat44 s = sFormMat(h); + if (isMat44Canonical(s)) { +#ifdef MY_DEBUG + printMessage("Image in perfect alignment: no need to reorient"); +#endif + return img; + } + vec3i flipV; + vec3 minMM = minCornerFlip(h, &flipV); + mat33 orient = getBestOrient(s, flipV); + vec3i orientVec = setOrientVec(orient); + if ((orientVec.v[0] == 1) && (orientVec.v[1] == 2) && (orientVec.v[2] == 3)) { +#ifdef MY_DEBUG + printMessage("Image already near best orthogonal alignment: no need to reorient\n"); +#endif + return img; + } + bool is24 = false; + if (h->bitpix == 24) { //RGB stored as planar data. treat as 3 8-bit slices + return img; + /*is24 = true; + h->bitpix = 8; + h->dim[3] = h->dim[3] * 3;*/ + } + img = reOrient(img, h, orientVec, orient, minMM); + if (is24) { + h->bitpix = 24; + h->dim[3] = h->dim[3] / 3; + } +#ifdef MY_DEBUG + printMessage("NewRotation= %d %d %d\n", orientVec.v[0], orientVec.v[1], orientVec.v[2]); + printMessage("MinCorner= %.2f %.2f %.2f\n", minMM.v[0], minMM.v[1], minMM.v[2]); + reportMat44o((char *)"input", s); + s = sFormMat(h); + reportMat44o((char *)"output", s); +#endif + return img; +} diff --git a/packages/dcm2niix/nii_ortho.h b/packages/dcm2niix/nii_ortho.h new file mode 100644 index 00000000000..a0c2186927b --- /dev/null +++ b/packages/dcm2niix/nii_ortho.h @@ -0,0 +1,19 @@ +#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 diff --git a/packages/dcm2niix/notarize.sh b/packages/dcm2niix/notarize.sh new file mode 100755 index 00000000000..9a61a0b76f6 --- /dev/null +++ b/packages/dcm2niix/notarize.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -e + +#com.${COMPANY_NAME}.${APP_NAME} e.g. com.mricro.niimath +COMPANY_NAME=mycompany +APP_NAME=dcm2niix +APP_SPECIFIC_PASSWORD=abcd-efgh-ijkl-mnop +APPLE_ID_USER=myname@gmail.com +APPLE_ID_INSTALL="Developer ID Installer: My Name" +APPLE_ID_APP="Developer ID Application: My Name" + +if [[ "$APPLE_ID_USER" == "myname@gmail.com" ]] +then + echo "You need to set your personal IDs and password" + exit 1 +fi + +g++ -O3 -sectcreate __TEXT __info_plist Info.plist -I. main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niixX86 -DmyDisableOpenJPEG -target x86_64-apple-macos10.12 -mmacosx-version-min=10.12 +g++ -O3 -sectcreate __TEXT __info_plist Info.plist -I. main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niixARM -DmyDisableOpenJPEG -target arm64-apple-macos11 -mmacosx-version-min=11.0 +# Create the universal binary. +strip ./dcm2niixARM; strip ./dcm2niixX86 +lipo -create -output ${APP_NAME} dcm2niixARM dcm2niixX86 +rm ./dcm2niixARM; rm ./dcm2niixX86 +# Create a staging area for the installer package. +mkdir -p usr/local/bin +# Move the binary into the staging area. +mv ${APP_NAME} usr/local/bin +# Sign the binary. +codesign --timestamp --options=runtime -s "${APPLE_ID_APP}" -v usr/local/bin/${APP_NAME} +# Build the package. +pkgbuild --identifier "com.${COMPANY_NAME}.${APP_NAME}.pkg" --sign "${APPLE_ID_INSTALL}" --timestamp --root usr/local --install-location /usr/local/ ${APP_NAME}.pkg +# Submit the package to the notarization service. + +xcrun altool --notarize-app --primary-bundle-id "com.${COMPANY_NAME}.${APP_NAME}.pkg" --username $APPLE_ID_USER --password $APP_SPECIFIC_PASSWORD --file ${APP_NAME}.pkg --output-format xml > upload_log_file.txt + +# now we need to query apple's server to the status of notarization +# when the "xcrun altool --notarize-app" command is finished the output plist +# will contain a notarization-upload->RequestUUID key which we can use to check status +echo "Checking status..." +sleep 50 +REQUEST_UUID=`/usr/libexec/PlistBuddy -c "Print :notarization-upload:RequestUUID" upload_log_file.txt` +while true; do + xcrun altool --notarization-info $REQUEST_UUID -u $APPLE_ID_USER -p $APP_SPECIFIC_PASSWORD --output-format xml > request_log_file.txt + # parse the request plist for the notarization-info->Status Code key which will + # be set to "success" if the package was notarized + STATUS=`/usr/libexec/PlistBuddy -c "Print :notarization-info:Status" request_log_file.txt` + if [ "$STATUS" != "in progress" ]; then + break + fi + # echo $STATUS + echo "$STATUS" + sleep 10 +done + +# download the log file to view any issues +/usr/bin/curl -o log_file.txt `/usr/libexec/PlistBuddy -c "Print :notarization-info:LogFileURL" request_log_file.txt` + +# staple +echo "Stapling..." +xcrun stapler staple ${APP_NAME}.pkg +xcrun stapler validate ${APP_NAME}.pkg + +open log_file.txt diff --git a/packages/dcm2niix/print.h b/packages/dcm2niix/print.h new file mode 100644 index 00000000000..b73d2f1c48c --- /dev/null +++ b/packages/dcm2niix/print.h @@ -0,0 +1,55 @@ +//This unit allows us to re-direct text messages +// For standard C programs send text messages to the console via "printf" +// The XCode project shows how you can re-direct these messages to a NSTextView +// For QT programs, we can sent text messages to the cout buffer +// The QT project shows how you can re-direct these to a Qtextedit +// For R programs, we can intercept these messages. + +#ifndef _R_PRINT_H_ + #define _R_PRINT_H_ + #include + #ifdef USING_R + #define R_USE_C99_IN_CXX + #include + #define printMessage(...) do { Rprintf("[dcm2niix info] "); Rprintf(__VA_ARGS__); } while (0) + #define printWarning(...) do { Rprintf("[dcm2niix WARNING] "); Rprintf(__VA_ARGS__); } while (0) + #define printError(...) do { Rprintf("[dcm2niix ERROR] "); Rprintf(__VA_ARGS__); } while (0) + #define printError(frac) do { Rprintf("[dcm2niix PROGRESS] %g", frac); } while (0) + #else + #ifdef myUseCOut + //for piping output to Qtextedit + // printf and cout buffers are not the same + // #define printMessage(...) ({fprintf(stdout,__VA_ARGS__);}) + #include + template< typename... Args > + void printMessage( const char* format, Args... args ) { + //std::printf( format, args... ); + //fprintf(stdout,"Short read on %s: Expected 512, got %zd\n",path, bytes_read); + int length = std::snprintf( nullptr, 0, format, args... ); + if ( length <= 0 ) return; + char* buf = new char[length + 1]; + std::snprintf( buf, length + 1, format, args... ); + std::cout << buf; + delete[] buf; + } + #define printError(...) do { printMessage("Error: "); printMessage(__VA_ARGS__);} while(0) + #define printProgress(frac) do { printMessage("Progress: %g\n", frac);} while(0) + #else + #include + #define printMessage printf + //#define printMessageError(...) fprintf (stderr, __VA_ARGS__) + #define printProgress(frac) do { printMessage("Progress: %g\n", frac);} while(0) + #ifdef myErrorStdOut //for XCode MRIcro project, pipe errors to stdout not stderr + #define printError(...) do { printMessage("Error: "); printMessage(__VA_ARGS__);} while(0) + #else + #define printError(...) do { fprintf (stderr,"Error: "); fprintf (stderr, __VA_ARGS__);} while(0) + #endif + #endif //myUseCOut + //n.b. use ({}) for multi-line macros http://www.geeksforgeeks.org/multiline-macros-in-c/ + //these next lines work on GCC but not _MSC_VER + // #define printWarning(...) ({printMessage("Warning: "); printMessage(__VA_ARGS__);}) + // #define printError(...) ({ printMessage("Error: "); printMessage(__VA_ARGS__);}) + #define printWarning(...) do {printMessage("Warning: "); printMessage(__VA_ARGS__);} while(0) + + #endif //USING_R +#endif //_R_PRINT_H_ diff --git a/packages/dcm2niix/tinydir.h b/packages/dcm2niix/tinydir.h new file mode 100644 index 00000000000..82d4183735c --- /dev/null +++ b/packages/dcm2niix/tinydir.h @@ -0,0 +1,443 @@ +/* +Copyright (c) 2013-2014, Cong Xu +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef TINYDIR_H +#define TINYDIR_H + +#include +#include +#include +#ifdef _MSC_VER +#define WIN32_LEAN_AND_MEAN +#include +#pragma warning (disable : 4996) +#else +#include +#include +#endif + + +/* types */ + +#define _TINYDIR_PATH_MAX 4096 +#ifdef _MSC_VER +/* extra chars for the "\\*" mask */ +#define _TINYDIR_PATH_EXTRA 2 +#else +#define _TINYDIR_PATH_EXTRA 0 +#endif +#define _TINYDIR_FILENAME_MAX 256 + +#ifdef _MSC_VER +#define _TINYDIR_FUNC static __inline +#else +#define _TINYDIR_FUNC static __inline__ +#endif + +typedef struct +{ + char path[_TINYDIR_PATH_MAX]; + char name[_TINYDIR_FILENAME_MAX]; + int is_dir; + int is_reg; + +#ifdef _MSC_VER +#else + struct stat _s; +#endif +} tinydir_file; + +typedef struct +{ + char path[_TINYDIR_PATH_MAX]; + int has_next; + size_t n_files; + + tinydir_file *_files; +#ifdef _MSC_VER + HANDLE _h; + WIN32_FIND_DATA _f; +#else + DIR *_d; + struct dirent *_e; +#endif +} tinydir_dir; + + +/* declarations */ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const char *path); +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const char *path); +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir); + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir); +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); + +_TINYDIR_FUNC +int _tinydir_file_cmp(const void *a, const void *b); + + +/* definitions*/ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const char *path) +{ + if (dir == NULL || path == NULL || strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + /* initialise dir */ + dir->_files = NULL; +#ifdef _MSC_VER + dir->_h = INVALID_HANDLE_VALUE; +#else + dir->_d = NULL; +#endif + tinydir_close(dir); + + strcpy(dir->path, path); +#ifdef _MSC_VER + strcat(dir->path, "\\*"); + dir->_h = FindFirstFile(dir->path, &dir->_f); + dir->path[strlen(dir->path) - 2] = '\0'; + if (dir->_h == INVALID_HANDLE_VALUE) +#else + dir->_d = opendir(path); + if (dir->_d == NULL) +#endif + { + errno = ENOENT; + goto bail; + } + /* read first file */ + dir->has_next = 1; +#ifndef _MSC_VER + dir->_e = readdir(dir->_d); + if (dir->_e == NULL) + { + dir->has_next = 0; + } +#endif + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const char *path) +{ + /* Count the number of files first, to pre-allocate the files array */ + size_t n_files = 0; + if (tinydir_open(dir, path) == -1) + { + return -1; + } + while (dir->has_next) + { + n_files++; + if (tinydir_next(dir) == -1) + { + goto bail; + } + } + tinydir_close(dir); + + if (tinydir_open(dir, path) == -1) + { + return -1; + } + + dir->n_files = 0; + dir->_files = (tinydir_file *)malloc(sizeof *dir->_files * n_files); + if (dir->_files == NULL) + { + errno = ENOMEM; + goto bail; + } + while (dir->has_next) + { + tinydir_file *p_file; + dir->n_files++; + + p_file = &dir->_files[dir->n_files - 1]; + if (tinydir_readfile(dir, p_file) == -1) + { + goto bail; + } + + if (tinydir_next(dir) == -1) + { + goto bail; + } + + /* Just in case the number of files has changed between the first and + second reads, terminate without writing into unallocated memory */ + if (dir->n_files == n_files) + { + break; + } + } + + qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir) +{ + if (dir == NULL) + { + return; + } + + memset(dir->path, 0, sizeof(dir->path)); + dir->has_next = 0; + dir->n_files = 0; + if (dir->_files != NULL) + { + free(dir->_files); + } + dir->_files = NULL; +#ifdef _MSC_VER + if (dir->_h != INVALID_HANDLE_VALUE) + { + FindClose(dir->_h); + } + dir->_h = INVALID_HANDLE_VALUE; +#else + if (dir->_d) + { + closedir(dir->_d); + } + dir->_d = NULL; + dir->_e = NULL; +#endif +} + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir) +{ + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (!dir->has_next) + { + errno = ENOENT; + return -1; + } + +#ifdef _MSC_VER + if (FindNextFile(dir->_h, &dir->_f) == 0) +#else + dir->_e = readdir(dir->_d); + if (dir->_e == NULL) +#endif + { + dir->has_next = 0; +#ifdef _MSC_VER + if (GetLastError() != ERROR_SUCCESS && + GetLastError() != ERROR_NO_MORE_FILES) + { + tinydir_close(dir); + errno = EIO; + return -1; + } +#endif + } + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) +{ + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } +#ifdef _MSC_VER + if (dir->_h == INVALID_HANDLE_VALUE) +#else + if (dir->_e == NULL) +#endif + { + errno = ENOENT; + return -1; + } + if (strlen(dir->path) + + strlen( +#ifdef _MSC_VER + dir->_f.cFileName +#else + dir->_e->d_name +#endif + ) + 1 + _TINYDIR_PATH_EXTRA >= + _TINYDIR_PATH_MAX) + { + /* the path for the file will be too long */ + errno = ENAMETOOLONG; + return -1; + } + if (strlen( +#ifdef _MSC_VER + dir->_f.cFileName +#else + dir->_e->d_name +#endif + ) >= _TINYDIR_FILENAME_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + strcpy(file->path, dir->path); + strcat(file->path, "/"); + strcpy(file->name, +#ifdef _MSC_VER + dir->_f.cFileName +#else + dir->_e->d_name +#endif + ); + /* Limit the number of bytes copied to the maximum length of the name, + to avoid spurious compiler warnings about possible overlap */ + strncat(file->path, file->name, _TINYDIR_FILENAME_MAX); +#ifndef _MSC_VER + if (stat(file->path, &file->_s) == -1) + { + return -1; + } +#endif + file->is_dir = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); +#else + S_ISDIR(file->_s.st_mode); +#endif + file->is_reg = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || + ( + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && +#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && +#endif +#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && +#endif + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); +#else + S_ISREG(file->_s.st_mode); +#endif + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) +{ + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files) + { + errno = ENOENT; + return -1; + } + + memcpy(file, &dir->_files[i], sizeof(tinydir_file)); + + return 0; +} + +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) +{ + char path[_TINYDIR_PATH_MAX]; + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files || !dir->_files[i].is_dir) + { + errno = ENOENT; + return -1; + } + + strcpy(path, dir->_files[i].path); + tinydir_close(dir); + if (tinydir_open_sorted(dir, path) == -1) + { + return -1; + } + + return 0; +} + +_TINYDIR_FUNC +int _tinydir_file_cmp(const void *a, const void *b) +{ + const tinydir_file *fa = (const tinydir_file *)a; + const tinydir_file *fb = (const tinydir_file *)b; + if (fa->is_dir != fb->is_dir) + { + return -(fa->is_dir - fb->is_dir); + } + return strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); +} + +#endif diff --git a/packages/dcm2niix/ucm.cmake b/packages/dcm2niix/ucm.cmake new file mode 100644 index 00000000000..e6e7fc8f1dc --- /dev/null +++ b/packages/dcm2niix/ucm.cmake @@ -0,0 +1,636 @@ +# +# 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() diff --git a/packages/dcm2niix/ujpeg.cpp b/packages/dcm2niix/ujpeg.cpp new file mode 100644 index 00000000000..b082427b052 --- /dev/null +++ b/packages/dcm2niix/ujpeg.cpp @@ -0,0 +1,928 @@ +// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder +// version 1.3.5 (2016-11-14) +// Copyright (c) 2009-2016 Martin J. Fiedler +// published under the terms of the MIT license +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + + +/////////////////////////////////////////////////////////////////////////////// +// DOCUMENTATION SECTION // +// read this if you want to know what this is all about // +/////////////////////////////////////////////////////////////////////////////// + +// INTRODUCTION +// ============ +// +// This is a minimal decoder for baseline JPEG images. It accepts memory dumps +// of JPEG files as input and generates either 8-bit grayscale or packed 24-bit +// RGB images as output. It does not parse JFIF or Exif headers; all JPEG files +// are assumed to be either grayscale or YCbCr. CMYK or other color spaces are +// not supported. All YCbCr subsampling schemes with power-of-two ratios are +// supported, as are restart intervals. Progressive or lossless JPEG is not +// supported. +// Summed up, NanoJPEG should be able to decode all images from digital cameras +// and most common forms of other non-progressive JPEG images. +// The decoder is not optimized for speed, it's optimized for simplicity and +// small code. Image quality should be at a reasonable level. A bicubic chroma +// upsampling filter ensures that subsampled YCbCr images are rendered in +// decent quality. The decoder is not meant to deal with broken JPEG files in +// a graceful manner; if anything is wrong with the bitstream, decoding will +// simply fail. +// The code should work with every modern C compiler without problems and +// should not emit any warnings. It uses only (at least) 32-bit integer +// arithmetic and is supposed to be endianness independent and 64-bit clean. +// However, it is not thread-safe. + + +// COMPILE-TIME CONFIGURATION +// ========================== +// +// The following aspects of NanoJPEG can be controlled with preprocessor +// defines: +// +// _NJ_EXAMPLE_PROGRAM = Compile a main() function with an example +// program. +// _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header +// file for NanoJPEG. Example: +// #define _NJ_INCLUDE_HEADER_ONLY +// #include "nanojpeg.c" +// int main(void) { +// njInit(); +// // your code here +// njDone(); +// } +// NJ_USE_LIBC=1 = Use the malloc(), free(), memset() and memcpy() +// functions from the standard C library (default). +// NJ_USE_LIBC=0 = Don't use the standard C library. In this mode, +// external functions njAlloc(), njFreeMem(), +// njFillMem() and njCopyMem() need to be defined +// and implemented somewhere. +// NJ_USE_WIN32=0 = Normal mode (default). +// NJ_USE_WIN32=1 = If compiling with MSVC for Win32 and +// NJ_USE_LIBC=0, NanoJPEG will use its own +// implementations of the required C library +// functions (default if compiling with MSVC and +// NJ_USE_LIBC=0). +// NJ_CHROMA_FILTER=1 = Use the bicubic chroma upsampling filter +// (default). +// NJ_CHROMA_FILTER=0 = Use simple pixel repetition for chroma upsampling +// (bad quality, but faster and less code). + + +// API +// === +// +// For API documentation, read the "header section" below. + + +// EXAMPLE +// ======= +// +// A few pages below, you can find an example program that uses NanoJPEG to +// convert JPEG files into PGM or PPM. To compile it, use something like +// gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c +// You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want :) +// The only thing you might need is -Wno-shift-negative-value, because this +// code relies on the target machine using two's complement arithmetic, but +// the C standard does not, even though *any* practically useful machine +// nowadays uses two's complement. + + +/////////////////////////////////////////////////////////////////////////////// +// HEADER SECTION // +// copy and paste this into nanojpeg.h if you want // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _NANOJPEG_H +#define _NANOJPEG_H + +// nj_result_t: Result codes for njDecode(). +typedef enum _nj_result { + NJ_OK = 0, // no error, decoding successful + NJ_NO_JPEG, // not a JPEG file + NJ_UNSUPPORTED, // unsupported format + NJ_OUT_OF_MEM, // out of memory + NJ_INTERNAL_ERR, // internal error + NJ_SYNTAX_ERROR, // syntax error + __NJ_FINISHED, // used internally, will never be reported +} nj_result_t; + +// njInit: Initialize NanoJPEG. +// For safety reasons, this should be called at least one time before using +// using any of the other NanoJPEG functions. +void njInit(void); + +// njDecode: Decode a JPEG image. +// Decodes a memory dump of a JPEG file into internal buffers. +// Parameters: +// jpeg = The pointer to the memory dump. +// size = The size of the JPEG file. +// Return value: The error code in case of failure, or NJ_OK (zero) on success. +nj_result_t njDecode(const void* jpeg, const int size); + +// njGetWidth: Return the width (in pixels) of the most recently decoded +// image. If njDecode() failed, the result of njGetWidth() is undefined. +int njGetWidth(void); + +// njGetHeight: Return the height (in pixels) of the most recently decoded +// image. If njDecode() failed, the result of njGetHeight() is undefined. +int njGetHeight(void); + +// njIsColor: Return 1 if the most recently decoded image is a color image +// (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result +// of njGetWidth() is undefined. +int njIsColor(void); + +// njGetImage: Returns the decoded image data. +// Returns a pointer to the most recently image. The memory layout it byte- +// oriented, top-down, without any padding between lines. Pixels of color +// images will be stored as three consecutive bytes for the red, green and +// blue channels. This data format is thus compatible with the PGM or PPM +// file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8. +// If njDecode() failed, the result of njGetImage() is undefined. +unsigned char* njGetImage(void); + +// njGetImageSize: Returns the size (in bytes) of the image data returned +// by njGetImage(). If njDecode() failed, the result of njGetImageSize() is +// undefined. +int njGetImageSize(void); + +// njDone: Uninitialize NanoJPEG. +// Resets NanoJPEG's internal state and frees all memory that has been +// allocated at run-time by NanoJPEG. It is still possible to decode another +// image after a njDone() call. +void njDone(void); + +#endif//_NANOJPEG_H + + +/////////////////////////////////////////////////////////////////////////////// +// CONFIGURATION SECTION // +// adjust the default settings for the NJ_ defines here // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef NJ_USE_LIBC + #define NJ_USE_LIBC 1 +#endif + +#ifndef NJ_USE_WIN32 + #ifdef _MSC_VER + #define NJ_USE_WIN32 (!NJ_USE_LIBC) + #else + #define NJ_USE_WIN32 0 + #endif +#endif + +#ifndef NJ_CHROMA_FILTER + #define NJ_CHROMA_FILTER 1 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// EXAMPLE PROGRAM // +// just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC) // +/////////////////////////////////////////////////////////////////////////////// + +#ifdef _NJ_EXAMPLE_PROGRAM + +#include +#include +#include + +int main(int argc, char* argv[]) { + int size; + char *buf; + FILE *f; + + if (argc < 2) { + printf("Usage: %s []\n", argv[0]); + return 2; + } + f = fopen(argv[1], "rb"); + if (!f) { + printf("Error opening the input file.\n"); + return 1; + } + fseek(f, 0, SEEK_END); + size = (int) ftell(f); + buf = (char*) malloc(size); + fseek(f, 0, SEEK_SET); + size = (int) fread(buf, 1, size, f); + fclose(f); + + njInit(); + if (njDecode(buf, size)) { + free((void*)buf); + printf("Error decoding the input file.\n"); + return 1; + } + free((void*)buf); + + f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb"); + if (!f) { + printf("Error opening the output file.\n"); + return 1; + } + fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight()); + fwrite(njGetImage(), 1, njGetImageSize(), f); + fclose(f); + njDone(); + return 0; +} + +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// IMPLEMENTATION SECTION // +// you may stop reading here // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _NJ_INCLUDE_HEADER_ONLY + +#ifdef _MSC_VER + #define NJ_INLINE static __inline + #define NJ_FORCE_INLINE static __forceinline +#else + #define NJ_INLINE static inline + #define NJ_FORCE_INLINE static inline +#endif + +#if NJ_USE_LIBC + #include + #include + #define njAllocMem malloc + #define njFreeMem free + #define njFillMem memset + #define njCopyMem memcpy +#elif NJ_USE_WIN32 + #include + #define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size))) + #define njFreeMem(block) ((void) LocalFree((HLOCAL) block)) + NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm { + mov edi, block + mov al, value + mov ecx, count + rep stosb + } } + NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm { + mov edi, dest + mov esi, src + mov ecx, count + rep movsb + } } +#else + extern void* njAllocMem(int size); + extern void njFreeMem(void* block); + extern void njFillMem(void* block, unsigned char byte, int size); + extern void njCopyMem(void* dest, const void* src, int size); +#endif + +typedef struct _nj_code { + unsigned char bits, code; +} nj_vlc_code_t; + +typedef struct _nj_cmp { + int cid; + int ssx, ssy; + int width, height; + int stride; + int qtsel; + int actabsel, dctabsel; + int dcpred; + unsigned char *pixels; +} nj_component_t; + +typedef struct _nj_ctx { + nj_result_t error; + const unsigned char *pos; + int size; + int length; + int width, height; + int mbwidth, mbheight; + int mbsizex, mbsizey; + int ncomp; + nj_component_t comp[3]; + int qtused, qtavail; + unsigned char qtab[4][64]; + nj_vlc_code_t vlctab[4][65536]; + int buf, bufbits; + int block[64]; + int rstinterval; + unsigned char *rgb; +} nj_context_t; + +static nj_context_t nj; + +static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, +11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, +42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, +38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; + +NJ_FORCE_INLINE unsigned char njClip(const int x) { + return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x); +} + +#define W1 2841 +#define W2 2676 +#define W3 2408 +#define W5 1609 +#define W6 1108 +#define W7 565 + +#ifdef USING_R +NJ_INLINE int shiftLeft(int i, int p) { + unsigned u = *(unsigned *)(&i); + u <<= p; + return int(u); +} +#else +#define shiftLeft(i,p) (i) << (p) +#endif + +NJ_INLINE void njRowIDCT(int* blk) { + int x0, x1, x2, x3, x4, x5, x6, x7, x8; + if (!((x1 = shiftLeft(blk[4], 11)) + | (x2 = blk[6]) + | (x3 = blk[2]) + | (x4 = blk[1]) + | (x5 = blk[7]) + | (x6 = blk[5]) + | (x7 = blk[3]))) + { + blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = shiftLeft(blk[0], 3); + return; + } + x0 = shiftLeft(blk[0], 11); + x0 += 128; + x8 = W7 * (x4 + x5); + x4 = x8 + (W1 - W7) * x4; + x5 = x8 - (W1 + W7) * x5; + x8 = W3 * (x6 + x7); + x6 = x8 - (W3 - W5) * x6; + x7 = x8 - (W3 + W5) * x7; + x8 = x0 + x1; + x0 -= x1; + x1 = W6 * (x3 + x2); + x2 = x1 - (W2 + W6) * x2; + x3 = x1 + (W2 - W6) * x3; + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = (181 * (x4 + x5) + 128) >> 8; + x4 = (181 * (x4 - x5) + 128) >> 8; + blk[0] = (x7 + x1) >> 8; + blk[1] = (x3 + x2) >> 8; + blk[2] = (x0 + x4) >> 8; + blk[3] = (x8 + x6) >> 8; + blk[4] = (x8 - x6) >> 8; + blk[5] = (x0 - x4) >> 8; + blk[6] = (x3 - x2) >> 8; + blk[7] = (x7 - x1) >> 8; +} + +NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) { + int x0, x1, x2, x3, x4, x5, x6, x7, x8; + if (!((x1 = shiftLeft(blk[8*4], 8)) + | (x2 = blk[8*6]) + | (x3 = blk[8*2]) + | (x4 = blk[8*1]) + | (x5 = blk[8*7]) + | (x6 = blk[8*5]) + | (x7 = blk[8*3]))) + { + x1 = njClip(((blk[0] + 32) >> 6) + 128); + for (x0 = 8; x0; --x0) { + *out = (unsigned char) x1; + out += stride; + } + return; + } + x0 = shiftLeft(blk[0], 8); + x0 += 8192; + x8 = W7 * (x4 + x5) + 4; + x4 = (x8 + (W1 - W7) * x4) >> 3; + x5 = (x8 - (W1 + W7) * x5) >> 3; + x8 = W3 * (x6 + x7) + 4; + x6 = (x8 - (W3 - W5) * x6) >> 3; + x7 = (x8 - (W3 + W5) * x7) >> 3; + x8 = x0 + x1; + x0 -= x1; + x1 = W6 * (x3 + x2) + 4; + x2 = (x1 - (W2 + W6) * x2) >> 3; + x3 = (x1 + (W2 - W6) * x3) >> 3; + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = (181 * (x4 + x5) + 128) >> 8; + x4 = (181 * (x4 - x5) + 128) >> 8; + *out = njClip(((x7 + x1) >> 14) + 128); out += stride; + *out = njClip(((x3 + x2) >> 14) + 128); out += stride; + *out = njClip(((x0 + x4) >> 14) + 128); out += stride; + *out = njClip(((x8 + x6) >> 14) + 128); out += stride; + *out = njClip(((x8 - x6) >> 14) + 128); out += stride; + *out = njClip(((x0 - x4) >> 14) + 128); out += stride; + *out = njClip(((x3 - x2) >> 14) + 128); out += stride; + *out = njClip(((x7 - x1) >> 14) + 128); +} + +#define njThrow(e) do { nj.error = e; return; } while (0) +#define njCheckError() do { if (nj.error) return; } while (0) + +static int njShowBits(int bits) { + unsigned char newbyte; + if (!bits) return 0; + while (nj.bufbits < bits) { + if (nj.size <= 0) { + nj.buf = shiftLeft(nj.buf, 8) | 0xFF; + nj.bufbits += 8; + continue; + } + newbyte = *nj.pos++; + nj.size--; + nj.bufbits += 8; + nj.buf = shiftLeft(nj.buf, 8) | newbyte; + if (newbyte == 0xFF) { + if (nj.size) { + unsigned char marker = *nj.pos++; + nj.size--; + switch (marker) { + case 0x00: + case 0xFF: + break; + case 0xD9: nj.size = 0; break; + default: + if ((marker & 0xF8) != 0xD0) + nj.error = NJ_SYNTAX_ERROR; + else { + nj.buf = shiftLeft(nj.buf, 8) | marker; + nj.bufbits += 8; + } + } + } else + nj.error = NJ_SYNTAX_ERROR; + } + } + return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1); +} + +NJ_INLINE void njSkipBits(int bits) { + if (nj.bufbits < bits) + (void) njShowBits(bits); + nj.bufbits -= bits; +} + +NJ_INLINE int njGetBits(int bits) { + int res = njShowBits(bits); + njSkipBits(bits); + return res; +} + +NJ_INLINE void njByteAlign(void) { + nj.bufbits &= 0xF8; +} + +static void njSkip(int count) { + nj.pos += count; + nj.size -= count; + nj.length -= count; + if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR; +} + +NJ_INLINE unsigned short njDecode16(const unsigned char *pos) { + return (pos[0] << 8) | pos[1]; +} + +static void njDecodeLength(void) { + if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR); + nj.length = njDecode16(nj.pos); + if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR); + njSkip(2); +} + +NJ_INLINE void njSkipMarker(void) { + njDecodeLength(); + njSkip(nj.length); +} + +NJ_INLINE void njDecodeSOF(void) { + int i, ssxmax = 0, ssymax = 0; + nj_component_t* c; + njDecodeLength(); + njCheckError(); + if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR); + if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED); + nj.height = njDecode16(nj.pos+1); + nj.width = njDecode16(nj.pos+3); + if (!nj.width || !nj.height) njThrow(NJ_SYNTAX_ERROR); + nj.ncomp = nj.pos[5]; + njSkip(6); + switch (nj.ncomp) { + case 1: + case 3: + break; + default: + njThrow(NJ_UNSUPPORTED); + } + if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR); + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { + c->cid = nj.pos[0]; + if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR); + if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two + if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR); + if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two + if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR); + njSkip(3); + nj.qtused |= 1 << c->qtsel; + if (c->ssx > ssxmax) ssxmax = c->ssx; + if (c->ssy > ssymax) ssymax = c->ssy; + } + if (nj.ncomp == 1) { + c = nj.comp; + c->ssx = c->ssy = ssxmax = ssymax = 1; + } + nj.mbsizex = shiftLeft(ssxmax, 3); + nj.mbsizey = shiftLeft(ssymax, 3); + nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex; + nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey; + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { + c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax; + c->height = (nj.height * c->ssy + ssymax - 1) / ssymax; + c->stride = nj.mbwidth * shiftLeft(c->ssx, 3); + if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED); + if (!(c->pixels = (unsigned char*) njAllocMem(c->stride * nj.mbheight * shiftLeft(c->ssy, 3)))) njThrow(NJ_OUT_OF_MEM); + } + if (nj.ncomp == 3) { + nj.rgb = (unsigned char*) njAllocMem(nj.width * nj.height * nj.ncomp); + if (!nj.rgb) njThrow(NJ_OUT_OF_MEM); + } + njSkip(nj.length); +} + +NJ_INLINE void njDecodeDHT(void) { + int codelen, currcnt, remain, spread, i, j; + nj_vlc_code_t *vlc; + static unsigned char counts[16]; + njDecodeLength(); + njCheckError(); + while (nj.length >= 17) { + i = nj.pos[0]; + if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR); + if (i & 0x02) njThrow(NJ_UNSUPPORTED); + i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value + for (codelen = 1; codelen <= 16; ++codelen) + counts[codelen - 1] = nj.pos[codelen]; + njSkip(17); + vlc = &nj.vlctab[i][0]; + remain = spread = 65536; + for (codelen = 1; codelen <= 16; ++codelen) { + spread >>= 1; + currcnt = counts[codelen - 1]; + if (!currcnt) continue; + if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR); + remain -= shiftLeft(currcnt, 16 - codelen); + if (remain < 0) njThrow(NJ_SYNTAX_ERROR); + for (i = 0; i < currcnt; ++i) { + unsigned char code = nj.pos[i]; + for (j = spread; j; --j) { + vlc->bits = (unsigned char) codelen; + vlc->code = code; + ++vlc; + } + } + njSkip(currcnt); + } + while (remain--) { + vlc->bits = 0; + ++vlc; + } + } + if (nj.length) njThrow(NJ_SYNTAX_ERROR); +} + +NJ_INLINE void njDecodeDQT(void) { + int i; + unsigned char *t; + njDecodeLength(); + njCheckError(); + while (nj.length >= 65) { + i = nj.pos[0]; + if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR); + nj.qtavail |= 1 << i; + t = &nj.qtab[i][0]; + for (i = 0; i < 64; ++i) + t[i] = nj.pos[i + 1]; + njSkip(65); + } + if (nj.length) njThrow(NJ_SYNTAX_ERROR); +} + +NJ_INLINE void njDecodeDRI(void) { + njDecodeLength(); + njCheckError(); + if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR); + nj.rstinterval = njDecode16(nj.pos); + njSkip(nj.length); +} + +static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) { + int value = njShowBits(16); + int bits = vlc[value].bits; + if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; } + njSkipBits(bits); + value = vlc[value].code; + if (code) *code = (unsigned char) value; + bits = value & 15; + if (!bits) return 0; + value = njGetBits(bits); + if (value < (1 << (bits - 1))) + value += (shiftLeft(-1, bits)) + 1; + return value; +} + +NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) { + unsigned char code = 0; + int value, coef = 0; + njFillMem(nj.block, 0, sizeof(nj.block)); + c->dcpred += njGetVLC(&nj.vlctab[c->dctabsel][0], NULL); + nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0]; + do { + value = njGetVLC(&nj.vlctab[c->actabsel][0], &code); + if (!code) break; // EOB + if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR); + coef += (code >> 4) + 1; + if (coef > 63) njThrow(NJ_SYNTAX_ERROR); + nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef]; + } while (coef < 63); + for (coef = 0; coef < 64; coef += 8) + njRowIDCT(&nj.block[coef]); + for (coef = 0; coef < 8; ++coef) + njColIDCT(&nj.block[coef], &out[coef], c->stride); +} + +NJ_INLINE void njDecodeScan(void) { + int i, mbx, mby, sbx, sby; + int rstcount = nj.rstinterval, nextrst = 0; + nj_component_t* c; + njDecodeLength(); + njCheckError(); + if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR); + if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED); + njSkip(1); + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { + if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR); + if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR); + c->dctabsel = nj.pos[1] >> 4; + c->actabsel = (nj.pos[1] & 1) | 2; + njSkip(2); + } + if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED); + njSkip(nj.length); + for (mbx = mby = 0;;) { + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) + for (sby = 0; sby < c->ssy; ++sby) + for (sbx = 0; sbx < c->ssx; ++sbx) { + njDecodeBlock(c, &c->pixels[shiftLeft((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx, 3)]); + njCheckError(); + } + if (++mbx >= nj.mbwidth) { + mbx = 0; + if (++mby >= nj.mbheight) break; + } + if (nj.rstinterval && !(--rstcount)) { + njByteAlign(); + i = njGetBits(16); + if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR); + nextrst = (nextrst + 1) & 7; + rstcount = nj.rstinterval; + for (i = 0; i < 3; ++i) + nj.comp[i].dcpred = 0; + } + } + nj.error = __NJ_FINISHED; +} + +#if NJ_CHROMA_FILTER + +#define CF4A (-9) +#define CF4B (111) +#define CF4C (29) +#define CF4D (-3) +#define CF3A (28) +#define CF3B (109) +#define CF3C (-9) +#define CF3X (104) +#define CF3Y (27) +#define CF3Z (-3) +#define CF2A (139) +#define CF2B (-11) +#define CF(x) njClip(((x) + 64) >> 7) + +NJ_INLINE void njUpsampleH(nj_component_t* c) { + const int xmax = c->width - 3; + unsigned char *out, *lin, *lout; + int x, y; + out = (unsigned char*) njAllocMem((c->width * c->height) << 1); + if (!out) njThrow(NJ_OUT_OF_MEM); + lin = c->pixels; + lout = out; + for (y = c->height; y; --y) { + lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]); + lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]); + lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]); + for (x = 0; x < xmax; ++x) { + lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]); + lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]); + } + lin += c->stride; + lout += c->width << 1; + lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]); + lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]); + lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]); + } + c->width <<= 1; + c->stride = c->width; + njFreeMem((void*)c->pixels); + c->pixels = out; +} + +NJ_INLINE void njUpsampleV(nj_component_t* c) { + const int w = c->width, s1 = c->stride, s2 = s1 + s1; + unsigned char *out, *cin, *cout; + int x, y; + out = (unsigned char*) njAllocMem((c->width * c->height) << 1); + if (!out) njThrow(NJ_OUT_OF_MEM); + for (x = 0; x < w; ++x) { + cin = &c->pixels[x]; + cout = &out[x]; + *cout = CF(CF2A * cin[0] + CF2B * cin[s1]); cout += w; + *cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]); cout += w; + *cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]); cout += w; + cin += s1; + for (y = c->height - 3; y; --y) { + *cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]); cout += w; + *cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]); cout += w; + cin += s1; + } + cin += s1; + *cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]); cout += w; + *cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]); cout += w; + *cout = CF(CF2A * cin[0] + CF2B * cin[-s1]); + } + c->height <<= 1; + c->stride = c->width; + njFreeMem((void*) c->pixels); + c->pixels = out; +} + +#else + +NJ_INLINE void njUpsample(nj_component_t* c) { + int x, y, xshift = 0, yshift = 0; + unsigned char *out, *lin, *lout; + while (c->width < nj.width) { c->width <<= 1; ++xshift; } + while (c->height < nj.height) { c->height <<= 1; ++yshift; } + out = (unsigned char*) njAllocMem(c->width * c->height); + if (!out) njThrow(NJ_OUT_OF_MEM); + lin = c->pixels; + lout = out; + for (y = 0; y < c->height; ++y) { + lin = &c->pixels[(y >> yshift) * c->stride]; + for (x = 0; x < c->width; ++x) + lout[x] = lin[x >> xshift]; + lout += c->width; + } + c->stride = c->width; + njFreeMem((void*) c->pixels); + c->pixels = out; +} + +#endif + +NJ_INLINE void njConvert(void) { + int i; + nj_component_t* c; + for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { + #if NJ_CHROMA_FILTER + while ((c->width < nj.width) || (c->height < nj.height)) { + if (c->width < nj.width) njUpsampleH(c); + njCheckError(); + if (c->height < nj.height) njUpsampleV(c); + njCheckError(); + } + #else + if ((c->width < nj.width) || (c->height < nj.height)) + njUpsample(c); + #endif + if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR); + } + if (nj.ncomp == 3) { + // convert to RGB + int x, yy; + unsigned char *prgb = nj.rgb; + const unsigned char *py = nj.comp[0].pixels; + const unsigned char *pcb = nj.comp[1].pixels; + const unsigned char *pcr = nj.comp[2].pixels; + for (yy = nj.height; yy; --yy) { + for (x = 0; x < nj.width; ++x) { + int y = py[x] << 8; + int cb = pcb[x] - 128; + int cr = pcr[x] - 128; + *prgb++ = njClip((y + 359 * cr + 128) >> 8); + *prgb++ = njClip((y - 88 * cb - 183 * cr + 128) >> 8); + *prgb++ = njClip((y + 454 * cb + 128) >> 8); + } + py += nj.comp[0].stride; + pcb += nj.comp[1].stride; + pcr += nj.comp[2].stride; + } + } else if (nj.comp[0].width != nj.comp[0].stride) { + // grayscale -> only remove stride + unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride]; + unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width]; + int y; + for (y = nj.comp[0].height - 1; y; --y) { + njCopyMem(pout, pin, nj.comp[0].width); + pin += nj.comp[0].stride; + pout += nj.comp[0].width; + } + nj.comp[0].stride = nj.comp[0].width; + } +} + +void njInit(void) { + njFillMem(&nj, 0, sizeof(nj_context_t)); +} + +void njDone(void) { + int i; + for (i = 0; i < 3; ++i) + if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels); + if (nj.rgb) njFreeMem((void*) nj.rgb); + njInit(); +} + +nj_result_t njDecode(const void* jpeg, const int size) { + njDone(); + nj.pos = (const unsigned char*) jpeg; + nj.size = size & 0x7FFFFFFF; + if (nj.size < 2) return NJ_NO_JPEG; + if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG; + njSkip(2); + while (!nj.error) { + if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR; + njSkip(2); + switch (nj.pos[-1]) { + case 0xC0: njDecodeSOF(); break; + case 0xC4: njDecodeDHT(); break; + case 0xDB: njDecodeDQT(); break; + case 0xDD: njDecodeDRI(); break; + case 0xDA: njDecodeScan(); break; + case 0xFE: njSkipMarker(); break; + default: + if ((nj.pos[-1] & 0xF0) == 0xE0) + njSkipMarker(); + else + return NJ_UNSUPPORTED; + } + } + if (nj.error != __NJ_FINISHED) return nj.error; + nj.error = NJ_OK; + njConvert(); + return nj.error; +} + +int njGetWidth(void) { return nj.width; } +int njGetHeight(void) { return nj.height; } +int njIsColor(void) { return (nj.ncomp != 1); } +unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; } +int njGetImageSize(void) { return nj.width * nj.height * nj.ncomp; } + +#endif // _NJ_INCLUDE_HEADER_ONLY \ No newline at end of file diff --git a/packages/dcm2niix/ujpeg.h b/packages/dcm2niix/ujpeg.h new file mode 100644 index 00000000000..a692b228366 --- /dev/null +++ b/packages/dcm2niix/ujpeg.h @@ -0,0 +1,61 @@ +#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 diff --git a/packages/nifti/nifti1_io.c b/packages/nifti/nifti1_io.c index 32ac8e86ca1..07333330ab0 100644 --- a/packages/nifti/nifti1_io.c +++ b/packages/nifti/nifti1_io.c @@ -1739,7 +1739,7 @@ void nifti_mat44_to_quatern(mat44 R, b = -b; c = -c; d = -d; - a = -a; + a = -a; // dcm2niix discarded this } } @@ -2365,7 +2365,7 @@ void nifti_mat44_to_orientation(mat44 R, int *icod, int *jcod, int *kcod) * Fixes http://bugs.debian.org/446893 Yaroslav * */ /*--------------------------------------------------------------------*/ -void nifti_swap_2bytes(int n, void *ar) /* 2 bytes at a time */ +void nifti_swap_2bytes(size_t n, void *ar) /* 2 bytes at a time */ { register int ii; unsigned char *cp1 = (unsigned char *)ar, *cp2; @@ -2384,7 +2384,7 @@ void nifti_swap_2bytes(int n, void *ar) /* 2 bytes at a time */ /*----------------------------------------------------------------------*/ /*! swap 4 bytes at a time from the given list of n sets of 4 bytes */ /*--------------------------------------------------------------------*/ -void nifti_swap_4bytes(int n, void *ar) /* 4 bytes at a time */ +void nifti_swap_4bytes(size_t n, void *ar) /* 4 bytes at a time */ { register int ii; unsigned char *cp0 = (unsigned char *)ar, *cp1, *cp2; @@ -2411,7 +2411,7 @@ void nifti_swap_4bytes(int n, void *ar) /* 4 bytes at a time */ * * perhaps use this style for the general Nbytes, as Yaroslav suggests */ /*--------------------------------------------------------------------*/ -void nifti_swap_8bytes(int n, void *ar) /* 8 bytes at a time */ +void nifti_swap_8bytes(size_t n, void *ar) /* 8 bytes at a time */ { register int ii; unsigned char *cp0 = (unsigned char *)ar, *cp1, *cp2; @@ -2436,7 +2436,7 @@ void nifti_swap_8bytes(int n, void *ar) /* 8 bytes at a time */ /*----------------------------------------------------------------------*/ /*! swap 16 bytes at a time from the given list of n sets of 16 bytes */ /*--------------------------------------------------------------------*/ -void nifti_swap_16bytes(int n, void *ar) /* 16 bytes at a time */ +void nifti_swap_16bytes(size_t n, void *ar) /* 16 bytes at a time */ { register int ii; unsigned char *cp0 = (unsigned char *)ar, *cp1, *cp2; @@ -2462,7 +2462,7 @@ void nifti_swap_16bytes(int n, void *ar) /* 16 bytes at a time */ /*----------------------------------------------------------------------*/ /*! based on siz, call the appropriate nifti_swap_Nbytes() function */ /*--------------------------------------------------------------------*/ -void nifti_swap_Nbytes(int n, int siz, void *ar) /* subsuming case */ +void nifti_swap_Nbytes(size_t n, int siz, void *ar) /* subsuming case */ { switch (siz) { case 2: @@ -2512,6 +2512,10 @@ void swap_nifti_header(struct nifti_1_header *h, int is_nifti) swap_4(h->cal_min); /* this stuff is NIFTI specific */ +#if 0 // dcm2niix addition + nifti_swap_4bytes(1, &h->extents); + nifti_swap_2bytes(1, &h->session_error); +#endif if (is_nifti) { swap_4(h->intent_p1); @@ -2526,6 +2530,11 @@ void swap_nifti_header(struct nifti_1_header *h, int is_nifti) swap_4(h->slice_duration); swap_4(h->toffset); +#if 0 // dcm2niix addition + nifti_swap_4bytes(1, &h->glmax); + nifti_swap_4bytes(1, &h->glmin); +#endif + swap_2(h->qform_code); swap_2(h->sform_code); swap_4(h->quatern_b); diff --git a/packages/nifti/nifti1_io.h b/packages/nifti/nifti1_io.h index 4ea8af8066d..1d268542c0b 100644 --- a/packages/nifti/nifti1_io.h +++ b/packages/nifti/nifti1_io.h @@ -16,6 +16,7 @@ #define DONT_INCLUDE_ANALYZE_STRUCT /*** not needed herein ***/ #endif #include "nifti1.h" /*** NIFTI-1 header specification ***/ +#include "nifti1_io_core.h" #include @@ -53,15 +54,6 @@ extern "C" { Modified and added many routines for I/O. */ -/********************** Some sample data structures **************************/ - -typedef struct { /** 4x4 matrix struct **/ - float m[4][4] ; -} mat44 ; - -typedef struct { /** 3x3 matrix struct **/ - float m[3][3] ; -} mat33 ; /*...........................................................................*/ @@ -191,25 +183,10 @@ char *nifti_orientation_string( int ii ) ; int nifti_is_inttype( int dt ) ; -mat44 nifti_mat44_inverse( mat44 R ) ; - -mat33 nifti_mat33_inverse( mat33 R ) ; -mat33 nifti_mat33_polar ( mat33 A ) ; -float nifti_mat33_rownorm( mat33 A ) ; -float nifti_mat33_colnorm( mat33 A ) ; -float nifti_mat33_determ ( mat33 R ) ; -mat33 nifti_mat33_mul ( mat33 A , mat33 B ) ; - -void nifti_swap_2bytes ( int n , void *ar ) ; -void nifti_swap_4bytes ( int n , void *ar ) ; -void nifti_swap_8bytes ( int n , void *ar ) ; -void nifti_swap_16bytes( int n , void *ar ) ; -void nifti_swap_Nbytes ( int n , int siz , void *ar ) ; int nifti_datatype_from_string(const char * name); char * nifti_datatype_to_string (int dtype); -void swap_nifti_header ( struct nifti_1_header *h , int is_nifti ) ; int nifti_get_filesize( const char *pathname ) ; /* main read/write routines */ @@ -286,32 +263,10 @@ znzFile nifti_write_ascii_image(nifti_image *nim, const nifti_brick_list * NBL, void nifti_datatype_sizes( int datatype , int *nbyper, int *swapsize ) ; -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 ); - -mat44 nifti_make_orthog_mat44( float r11, float r12, float r13 , - float r21, float r22, float r23 , - float r31, float r32, float r33 ) ; int nifti_short_order(void) ; /* CPU byte order */ -/* Orientation codes that might be returned from nifti_mat44_to_orientation().*/ - -#define NIFTI_L2R 1 /* Left to Right */ -#define NIFTI_R2L 2 /* Right to Left */ -#define NIFTI_P2A 3 /* Posterior to Anterior */ -#define NIFTI_A2P 4 /* Anterior to Posterior */ -#define NIFTI_I2S 5 /* Inferior to Superior */ -#define NIFTI_S2I 6 /* Superior to Inferior */ - -void nifti_mat44_to_orientation( mat44 R , int *icod, int *jcod, int *kcod ) ; /*--------------------- Low level IO routines ------------------------------*/ diff --git a/packages/nifti/nifti1_io_core.h b/packages/nifti/nifti1_io_core.h new file mode 100644 index 00000000000..037d8c250ef --- /dev/null +++ b/packages/nifti/nifti1_io_core.h @@ -0,0 +1,113 @@ +/** \file nifti1_io_core.h + \brief Data structures for using nifti1_io API. + - Written by Bob Cox, SSCC NIMH + - Revisions by Rick Reynolds, SSCC NIMH + */ +#ifndef _NIFTI_IO_CORE_HEADER_ +#define _NIFTI_IO_CORE_HEADER_ + +#include +#include +#include +#include +#include + +#include "nifti1.h" /*** NIFTI-1 header specification ***/ + +/*=================*/ +#ifdef __cplusplus +extern "C" { +#endif +/*=================*/ + +/*****===================================================================*****/ +/***** File nifti1_io.h == Declarations for nifti1_io.c *****/ +/*****...................................................................*****/ +/***** 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. *****/ +/*****===================================================================*****/ + +/* + Modified by: Mark Jenkinson (FMRIB Centre, University of Oxford, UK) + Date: July/August 2004 + + Mainly adding low-level IO and changing things to allow gzipped files + to be read and written + Full backwards compatability should have been maintained + + Modified by: Rick Reynolds (SSCC/DIRP/NIMH, National Institutes of Health) + Date: December 2004 + + Modified and added many routines for I/O. +*/ + +/********************** Some sample data structures **************************/ + +typedef struct { /** 4x4 matrix struct **/ + float m[4][4] ; +} mat44 ; + +typedef struct { /** 3x3 matrix struct **/ + float m[3][3] ; +} mat33 ; + + + +mat44 nifti_mat44_inverse( mat44 R ) ; + +mat33 nifti_mat33_inverse( mat33 R ) ; +mat33 nifti_mat33_polar ( mat33 A ) ; +float nifti_mat33_rownorm( mat33 A ) ; +float nifti_mat33_colnorm( mat33 A ) ; +float nifti_mat33_determ ( mat33 R ) ; +mat33 nifti_mat33_mul ( mat33 A , mat33 B ) ; + +void nifti_swap_2bytes ( size_t n , void *ar ) ; +void nifti_swap_4bytes ( size_t n , void *ar ) ; +void nifti_swap_8bytes ( size_t n , void *ar ) ; +void nifti_swap_16bytes( size_t n , void *ar ) ; +void nifti_swap_Nbytes ( size_t n , int siz , void *ar ) ; + +void swap_nifti_header ( struct nifti_1_header *h , int is_nifti ) ; + + +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 ); + +mat44 nifti_make_orthog_mat44( float r11, float r12, float r13 , + float r21, float r22, float r23 , + float r31, float r32, float r33 ) ; + + + +/* Orientation codes that might be returned from nifti_mat44_to_orientation().*/ + +#define NIFTI_L2R 1 /* Left to Right */ +#define NIFTI_R2L 2 /* Right to Left */ +#define NIFTI_P2A 3 /* Posterior to Anterior */ +#define NIFTI_A2P 4 /* Anterior to Posterior */ +#define NIFTI_I2S 5 /* Inferior to Superior */ +#define NIFTI_S2I 6 /* Superior to Inferior */ + +void nifti_mat44_to_orientation( mat44 R , int *icod, int *jcod, int *kcod ) ; + +/*=================*/ +#ifdef __cplusplus +} +#endif +/*=================*/ + +#endif /* _NIFTI_IO_CORE_HEADER_ */ diff --git a/swi_processing/CMakeLists.txt b/swi_processing/CMakeLists.txt index 27846f7a8e0..89b72c863d5 100644 --- a/swi_processing/CMakeLists.txt +++ b/swi_processing/CMakeLists.txt @@ -1,6 +1,6 @@ project(swi_processing) -include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom) +include_directories(${FS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/packages/dicom ${CMAKE_SOURCE_DIR}/packages/dcm2niix) add_executable(swi_preprocess swi_preprocess.cpp cmd_line_interface.cpp) target_link_libraries(swi_preprocess utils) diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 68a27520d43..1f8e16fd644 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -17,6 +17,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/packages/netcdf ${CMAKE_SOURCE_DIR}/packages/nrrdio ${CMAKE_SOURCE_DIR}/packages/dicom + ${CMAKE_SOURCE_DIR}/packages/dcm2niix ${CMAKE_SOURCE_DIR}/packages/cephes ${CMAKE_SOURCE_DIR}/packages/nifti ${CMAKE_SOURCE_DIR}/packages/gifti @@ -217,6 +218,7 @@ target_link_libraries(utils dicom cephes nifti + dcm2niixfs gifti expat ${ZLIB_LIBRARIES} diff --git a/utils/DICOMRead.cpp b/utils/DICOMRead.cpp index e1ee300ecbd..f28be782e01 100644 --- a/utils/DICOMRead.cpp +++ b/utils/DICOMRead.cpp @@ -44,6 +44,8 @@ void *malloc(size_t size); #include "mosaic.h" #include "mri_identify.h" +#include "dcm2fsWrapper.h" + // #include "affine.h" #define _DICOMRead_SRC @@ -5926,6 +5928,41 @@ MRI *DICOMRead2(const char *dcmfile, int LoadVolume) return (mri); } + +/*-------------------------------------------------------------- + DICOMRead3() - generic dicom reader using dcm2fsWrapper. + --------------------------------------------------------------*/ +MRIFSSTRUCT *DICOMRead3(const char *dcmfile, int LoadVolume) +{ + printf("Starting DICOMRead3()\n"); + + if (!fio_FileExistsReadable(dcmfile)) { + printf("ERROR: file %s does not exist.\n", dcmfile); + exit(1); + } + char *dcmdir = fio_dirname(dcmfile); + printf("dcmfile = %s\n", dcmfile); + printf("dcmdir = %s\n", dcmdir); + if (!dcm2fsWrapper::isDICOM(dcmfile)) { + setenv("FS_DICOM_DEBUG", "1", 1); + //dcm2fsWrapper::isDICOM(dcmfile); + printf("ERROR: %s is not a dicom file or some other problem\n", dcmfile); + exit(1); + } + + dcm2fsWrapper::setOpts(dcmdir, NULL); + int ret = dcm2fsWrapper::dcm2NiiOneSeries(dcmfile); + + MRIFSSTRUCT *mrifsStruct = NULL; + if (ret == EXIT_SUCCESS) { + mrifsStruct = dcm2fsWrapper::getMrifsStruct(); + //dcm2fsWrapper::saveNii("fs.nii"); + } + + return mrifsStruct; +} + + /*------------------------------------------------------------------ CompareDCMFileInfo() - function to compare two DCM files for use with qsort. The order that files will be sorted is actually the @@ -6788,7 +6825,7 @@ int dcmGetDWIParams(DCM_OBJECT *dcm, double *pbval, double *pxbvec, double *pybv return (10); } - if (strcmp(e->d.string, "SIEMENS") == 0 || strcmp(e->d.string, "SIEMENS ") == 0) { + if (stricmp(e->d.string, "SIEMENS") == 0 || stricmp(e->d.string, "SIEMENS ") == 0) { if (Gdiag_no > 0) printf("Attempting to get DWI Parameters from Siemens DICOM\n"); err = dcmGetDWIParamsSiemens(dcm, pbval, pxbvec, pybvec, pzbvec); if (err) return (err); diff --git a/utils/mriio.cpp b/utils/mriio.cpp index 44ab7d942da..87c2285e3f7 100644 --- a/utils/mriio.cpp +++ b/utils/mriio.cpp @@ -78,6 +78,8 @@ #include "romp_support.h" #include "NrrdIO.h" +#include "nii_dicom.h" +#include "nii_dicom_batch.h" static int niiPrintHdr(FILE *fp, struct nifti_1_header *hdr); @@ -134,6 +136,7 @@ static MRI *ximgRead(const char *fname, int read_volume); static MRI *nifti1Read(const char *fname, int read_volume); static int nifti1Write(MRI *mri, const char *fname); static MRI *niiRead(const char *fname, int read_volume); +static MRI *niiRead3(MRIFSSTRUCT *mrifsStruct); static int niiWrite(MRI *mri, const char *fname); static int itkMorphWrite(MRI *mri, const char *fname); static int niftiQformToMri(MRI *mri, struct nifti_1_header *hdr); @@ -678,19 +681,42 @@ MRI *mri_read(const char *fname, int type, int volume_flag, int start_frame, int else if (type == GDF_FILE) { mri = gdfRead(fname_copy, volume_flag); } - else if (type == DICOM_FILE) { - if (!UseDICOMRead2) + else if (type == DICOM_FILE) { + if (UseDICOMRead3) { + MRIFSSTRUCT *mrifsStruct = DICOMRead3(fname_copy, volume_flag); + if (mrifsStruct == NULL) + return NULL; + + mri = niiRead3(mrifsStruct); + if (mri != NULL && mri->ti < 0) + mri->ti = 0; + + free(mrifsStruct->imgM); + free(mrifsStruct->tdti); + } + else if (!UseDICOMRead2) DICOMRead(fname_copy, &mri, volume_flag); else mri = DICOMRead2(fname_copy, volume_flag); } else if (type == SIEMENS_DICOM_FILE) { + if (UseDICOMRead3) { + MRIFSSTRUCT *mrifsStruct = DICOMRead3(fname_copy, volume_flag); + if (mrifsStruct == NULL) + return NULL; + + mri = niiRead3(mrifsStruct); + + free(mrifsStruct->imgM); + free(mrifsStruct->tdti); + } else { // mri_convert -nth option sets start_frame = nth. otherwise -1 mri = sdcmLoadVolume(fname_copy, volume_flag, start_frame); start_frame = -1; // in order to avoid the later processing on start_frame and end_frame // read the comment later on end_frame = 0; + } } else if (type == BRUKER_FILE) { mri = brukerRead(fname_copy, volume_flag); @@ -8729,6 +8755,423 @@ static MRI *niiRead(const char *fname, int read_volume) } /* end niiRead() */ + + +/*------------------------------------------------------------------ + niiRead3() - note: there are also nifti1Read() and niiRead(). Make sure to + edit all. Automatically detects whether an input is Ico7 + and reshapes. + This function is used with DICOMRead3(). + -----------------------------------------------------------------*/ +static MRI *niiRead3(MRIFSSTRUCT *mrifsStruct) +{ + MRI *mri, *mritmp; + int nslices; + int fs_type; + float time_units_factor, space_units_factor; + int swapped_flag; + int i, j, k, t; + int bytes_per_voxel, time_units, space_units; + int ncols, IsIco7 = 0; + + struct nifti_1_header *hdr = &(mrifsStruct->hdr0); + const unsigned char *img = mrifsStruct->imgM; + struct TDICOMdata *tdicomData = &mrifsStruct->tdicomData; + + swapped_flag = FALSE; + if (hdr->dim[0] < 1 || hdr->dim[0] > 7) { + swapped_flag = TRUE; + swap_nifti_1_header(hdr); + if (hdr->dim[0] < 1 || hdr->dim[0] > 7) { + ErrorReturn(NULL, (ERROR_BADFILE, "niiRead3(): bad number of dimensions (%hd)", hdr->dim[0])); + } + } + + if (memcmp(hdr->magic, NII_MAGIC, 4) != 0) { + ErrorReturn(NULL, (ERROR_BADFILE, "niiRead3(): bad magic number")); + } + + // if (hdr.dim[0] != 2 && hdr.dim[0] != 3 && hdr.dim[0] != 4){ + if (hdr->dim[0] < 1 || hdr->dim[0] > 5) { + ErrorReturn(NULL, (ERROR_UNSUPPORTED, "niiRead3(): %hd dimensions; unsupported", hdr->dim[0])); + } + + if (hdr->datatype == DT_NONE || hdr->datatype == DT_UNKNOWN) { + ErrorReturn(NULL, (ERROR_UNSUPPORTED, "niiRead3(): unknown or no data type; bailing out")); + } + if (hdr->dim[4] == 0) { + printf("WARNING: hdr->dim[4] = 0 (nframes), setting to 1\n"); + hdr->dim[4] = 1; + } + + space_units = XYZT_TO_SPACE(hdr->xyzt_units); + if (space_units == NIFTI_UNITS_METER) + space_units_factor = 1000.0; + else if (space_units == NIFTI_UNITS_MM) + space_units_factor = 1.0; + else if (space_units == NIFTI_UNITS_MICRON) + space_units_factor = 0.001; + else if (space_units == NIFTI_UNITS_UNKNOWN) { + space_units_factor = 1.0; + } + else + ErrorReturn(NULL, (ERROR_BADFILE, "niiRead3(): unknown space units %d", space_units)); + + time_units = XYZT_TO_TIME(hdr->xyzt_units); + if (time_units == NIFTI_UNITS_SEC) + time_units_factor = 1000.0; + else if (time_units == NIFTI_UNITS_MSEC) + time_units_factor = 1.0; + else if (time_units == NIFTI_UNITS_USEC) + time_units_factor = 0.001; + else { + // This can be a tricky situation because the time units may not mean + // anything even with multiple frames (eg, BO mag and phase). + if (hdr->dim[4] > 1) printf("WARNING: niiRead(): unknown time units %d\n", time_units); + time_units_factor = 0; + } + // printf("hdr.xyzt_units = %d, time_units = %d, %g, %g\n", + // hdr.xyzt_units,time_units,hdr.pixdim[4],time_units_factor); + + if (hdr->slice_code != 0 && DIM_INFO_TO_SLICE_DIM(hdr->dim_info) != 0 && hdr->slice_duration > 0.0) { + if (hdr->slice_code != NIFTI_SLICE_SEQ_INC && hdr->slice_code != NIFTI_SLICE_SEQ_DEC && + hdr->slice_code != NIFTI_SLICE_ALT_INC && hdr->slice_code != NIFTI_SLICE_ALT_DEC && + hdr->slice_code != NIFTI_SLICE_ALT_INC2 && hdr->slice_code != NIFTI_SLICE_ALT_DEC2) { + ErrorReturn( + NULL, (ERROR_UNSUPPORTED, "nifti1Read(): unsupported slice timing pattern %d", hdr->slice_code)); + } + } + + if (hdr->dim[0] < 4) + nslices = 1; + else + nslices = hdr->dim[4]; + + // put extra dims in frames + if (hdr->dim[0] > 4 && hdr->dim[5] > 0) nslices *= hdr->dim[5]; + if (Gdiag_no > 0) + printf("niiRead3(): hdr->dim %d %d %d %d %d %d\n", + hdr->dim[0], + hdr->dim[1], + hdr->dim[2], + hdr->dim[3], + hdr->dim[4], + hdr->dim[5]); + + // check whether data needs to be scaled + bool scaledata = (hdr->scl_slope != 0) && !((hdr->scl_slope == 1) && (hdr->scl_inter == 0)); + + if (!scaledata) { + // voxel values are unscaled -- we use the file's data type + if (hdr->datatype == DT_UNSIGNED_CHAR) { + fs_type = MRI_UCHAR; + bytes_per_voxel = 1; + } + else if (hdr->datatype == DT_SIGNED_SHORT) { + fs_type = MRI_SHORT; + bytes_per_voxel = 2; + } + else if (hdr->datatype == DT_UINT16) { + // This will not always work ... + printf("INFO: this is an unsiged short. I'll try to read it, but\n"); + printf(" it might not work if there are values over 32k\n"); + fs_type = MRI_SHORT; + bytes_per_voxel = 2; + } + else if (hdr->datatype == DT_SIGNED_INT) { + fs_type = MRI_INT; + bytes_per_voxel = 4; + } + else if (hdr->datatype == DT_FLOAT) { + fs_type = MRI_FLOAT; + bytes_per_voxel = 4; + } + else if (hdr->datatype == DT_DOUBLE) { + fs_type = MRI_FLOAT; + bytes_per_voxel = 8; + printf("niiRead3(): detected input as 64 bit double, reading in as 32 bit float\n"); + } +#if 0 // MRI_RBG not support in mghWrite + else if (hdr->datatype == DT_RGB) { + fs_type = MRI_UCHAR; //MRI_RGB; + bytes_per_voxel = 3; + printf("niiRead3(): DT_RGB, MRI_RGB\n"); + } +#endif + else { + ErrorReturn( + NULL, + (ERROR_UNSUPPORTED, "niiRead3(): unsupported datatype %d (with scl_slope = 0)", hdr->datatype)); + } + } + else { + // we must scale the voxel values + if (hdr->datatype != DT_UNSIGNED_CHAR && hdr->datatype != DT_SIGNED_SHORT && hdr->datatype != DT_SIGNED_INT && + hdr->datatype != DT_FLOAT && hdr->datatype != DT_DOUBLE && hdr->datatype != DT_INT8 && hdr->datatype != DT_UINT16 && + hdr->datatype != DT_UINT32) { + ErrorReturn( + NULL, + (ERROR_UNSUPPORTED, "niiRead3(): unsupported datatype %d (with scl_slope != 0)", hdr->datatype)); + } + fs_type = MRI_FLOAT; + bytes_per_voxel = 0; /* set below -- avoid the compiler warning */ + } + + // Check whether dim[1] is less than 0. This can happen when FreeSurfer + // writes a nifti volume where the number of columns is more than shortmax. + // In this case, dim[1] is set to -1, and glmin is set to the number of cols. + if (hdr->dim[1] > 0) + ncols = hdr->dim[1]; + else + ncols = hdr->glmin; + + if (ncols * hdr->dim[2] * hdr->dim[3] == 163842) IsIco7 = 1; + + // process image + int read_volume = 1; // ????should it be 1 or 0???? + if (read_volume) + mri = MRIallocSequence(ncols, hdr->dim[2], hdr->dim[3], fs_type, nslices); + else { + if (!IsIco7) + mri = MRIallocHeader(ncols, hdr->dim[2], hdr->dim[3], fs_type, nslices); + else + mri = MRIallocHeader(163842, 1, 1, fs_type, nslices); + mri->nframes = nslices; + } + if (mri == NULL) return (NULL); + + mri->xsize = hdr->pixdim[1]; + mri->ysize = hdr->pixdim[2]; + mri->zsize = hdr->pixdim[3]; + mri->tr = hdr->pixdim[4]; + + // set MRI values from struct TDICOMdata + mri->te = tdicomData->TE; + mri->ti = tdicomData->TI; + mri->flip_angle = M_PI * tdicomData->flipAngle / 180.0; //tdicomData->flipAngle; + mri->FieldStrength = tdicomData->fieldStrength; + if (tdicomData->phaseEncodingRC == 'R') + mri->pedir = strcpyalloc("ROW"); + else if (tdicomData->phaseEncodingRC == 'C') + mri->pedir = strcpyalloc("COL"); + + /* start setting of bvals and bvecs */ + // set bvals and bvecs + struct TDTI* tdti = mrifsStruct->tdti; + int numDti = mrifsStruct->numDti; + if (numDti > 0) + { + mri->bvals = MatrixAlloc(numDti, 1, MATRIX_REAL); + mri->bvecs = MatrixAlloc(numDti, 3, MATRIX_REAL); + if (DIAG_VERBOSE_ON) + { + for (int i = 0; i < numDti; i++) + printf("bvals: %f\n", tdti[i].V[0]); //mri->bvals->rptr[i][1] = tdti[i].V[v]; + + for (int i = 0; i < numDti; i++) + printf("bvecs: %16.14f %16.14f %16.14f\n", tdti[i].V[1], tdti[i].V[2], tdti[i].V[3]); + } + + // mri->bvals, mri->bvecs start at index 1; tdti[].V[] starts at index 0 + for (int i = 0; i < numDti; i++) + { + for (int v = 0; v < 4; v++) + { + if (v == 0) + mri->bvals->rptr[i+1][1] = tdti[i].V[v]; + else // v = 1, 2, 3 + mri->bvecs->rptr[i+1][v] = tdti[i].V[v]; + } + } + + // not sure how to assign bvec_space + //mri->bvec_space = BVEC_SPACE_VOXEL; + //if (IsPhilipsDWI) mri->bvec_space = BVEC_SPACE_SCANNER; + } + /* end setting of bvals and bvecs */ + + // Set the vox2ras matrix + if (hdr->sform_code != 0) { + // First, use the sform, if that is ok. Using the sform + // first makes it more compatible with FSL. + // fprintf(stderr,"INFO: using NIfTI-1 sform \n"); + if (niftiSformToMri(mri, hdr) != NO_ERROR) { + MRIfree(&mri); + return (NULL); + } + mri->ras_good_flag = 1; + } + else if (hdr->qform_code != 0) { + // Then, try the qform, if that is ok + fprintf(stderr, "INFO: using NIfTI-1 qform \n"); + if (niftiQformToMri(mri, hdr) != NO_ERROR) { + MRIfree(&mri); + return (NULL); + } + mri->ras_good_flag = 1; + } + else { + // Should probably just die here. + fprintf(stderr, "WARNING: neither NIfTI-1 qform or sform are valid\n"); + fprintf(stderr, "WARNING: your volume will probably be incorrectly oriented\n"); + mri->x_r = -1.0; + mri->x_a = 0.0; + mri->x_s = 0.0; + mri->y_r = 0.0; + mri->y_a = 1.0; + mri->y_s = 0.0; + mri->z_r = 0.0; + mri->z_a = 0.0; + mri->z_s = 1.0; + mri->c_r = mri->xsize * mri->width / 2.0; + mri->c_a = mri->ysize * mri->height / 2.0; + mri->c_s = mri->zsize * mri->depth / 2.0; + mri->ras_good_flag = 0; + } + + mri->xsize = mri->xsize * space_units_factor; + mri->ysize = mri->ysize * space_units_factor; + mri->zsize = mri->zsize * space_units_factor; + mri->c_r = mri->c_r * space_units_factor; + mri->c_a = mri->c_a * space_units_factor; + mri->c_s = mri->c_s * space_units_factor; + mri->tr = mri->tr * time_units_factor; + + mri->fov = mri->xsize * mri->width; + mri->xstart = -(mri->xsize * mri->width) / 2.0; + mri->xend = (mri->xsize * mri->width) / 2.0; + mri->ystart = -(mri->ysize * mri->height) / 2.0; + mri->yend = (mri->ysize * mri->height) / 2.0; + mri->zstart = -(mri->zsize * mri->depth) / 2.0; + mri->zend = (mri->zsize * mri->depth) / 2.0; + + if (Gdiag_no > 0) { + printf("nifti header ---------------------------------\n"); + niiPrintHdr(stdout, hdr); + printf("-----------------------------------------\n"); + } + + if (!read_volume) return (mri); + + if (!scaledata) { + // no voxel value scaling needed + void *buf; + float *fbuf; + double *dbuf; + int nn; + fbuf = (float *)calloc(mri->width, sizeof(float)); + dbuf = (double *)calloc(mri->width, sizeof(double)); + + int bytesRead = 0; + for (t = 0; t < mri->nframes; t++) { + for (k = 0; k < mri->depth; k++) { + for (j = 0; j < mri->height; j++) { + buf = &MRIseq_vox(mri, 0, j, k, t); + + if (hdr->datatype != DT_DOUBLE) + { + memcpy(buf, img+bytesRead, bytes_per_voxel*mri->width); + } + else + { + memcpy(dbuf, img+bytesRead, bytes_per_voxel*mri->width); + } + bytesRead += bytes_per_voxel*mri->width; + + if (swapped_flag) { + if (bytes_per_voxel == 2) byteswapbufshort(buf, bytes_per_voxel * mri->width); + if (bytes_per_voxel == 4) byteswapbuffloat(buf, bytes_per_voxel * mri->width); + if (bytes_per_voxel == 8) byteswapbuffloat(dbuf, bytes_per_voxel * mri->width); + } + if (hdr->datatype == DT_DOUBLE) { + for (nn = 0; nn < mri->width; nn++) fbuf[nn] = (float)dbuf[nn]; + memcpy(buf, fbuf, 4 * mri->width); + } + } // height + exec_progress_callback(k, mri->depth, t, mri->nframes); + } // depth + } // nframes + free(fbuf); + free(dbuf); + } + else { + int bytesRead = 0; + + if (hdr->datatype == DT_UNSIGNED_CHAR) + bytes_per_voxel = 1; + else if (hdr->datatype == DT_SIGNED_SHORT) + bytes_per_voxel = 2; + else if (hdr->datatype == DT_SIGNED_INT) + bytes_per_voxel = 4; + else if (hdr->datatype == DT_FLOAT) + bytes_per_voxel = 4; + else if (hdr->datatype == DT_DOUBLE) + bytes_per_voxel = 8; + else if (hdr->datatype == DT_UINT8) + bytes_per_voxel = 1; + else if (hdr->datatype == DT_UINT16) + bytes_per_voxel = 2; + else if (hdr->datatype == DT_UINT32) + bytes_per_voxel = 4; + + + // voxel value scaling needed + unsigned char *buf = (unsigned char *)malloc(mri->width * bytes_per_voxel); + for (t = 0; t < mri->nframes; t++) { + for (k = 0; k < mri->depth; k++) { + for (j = 0; j < mri->height; j++) { + memcpy(buf, img+bytesRead, bytes_per_voxel*mri->width); + bytesRead += bytes_per_voxel*mri->width; + + if (swapped_flag) { + if (hdr->datatype == DT_SIGNED_SHORT) + byteswapbufshort(buf, bytes_per_voxel * mri->width); + else if (hdr->datatype == DT_SIGNED_INT) + byteswapbuffloat(buf, bytes_per_voxel * mri->width); + else if (hdr->datatype == DT_FLOAT) + byteswapbuffloat(buf, bytes_per_voxel * mri->width); + else if (hdr->datatype == DT_DOUBLE) { + unsigned char *cbuf, ccbuf[8]; + for (i = 0; i < mri->width; i++) { + cbuf = (unsigned char *)&buf[i]; + memmove(ccbuf, cbuf, 8); + cbuf[0] = ccbuf[7]; + cbuf[1] = ccbuf[6]; + cbuf[2] = ccbuf[5]; + cbuf[3] = ccbuf[4]; + cbuf[4] = ccbuf[3]; + cbuf[5] = ccbuf[2]; + cbuf[6] = ccbuf[1]; + cbuf[7] = ccbuf[0]; + } + } + else if (hdr->datatype == DT_UINT16) + byteswapbufshort(buf, bytes_per_voxel * mri->width); + else if (hdr->datatype == DT_UINT32) + byteswapbuffloat(buf, bytes_per_voxel * mri->width); + } + + for (i = 0; i < mri->width; i++) + MRIFseq_vox(mri, i, j, k, t) = hdr->scl_slope * (float)(buf[i]) + hdr->scl_inter; + } + exec_progress_callback(k, mri->depth, t, mri->nframes); + } + } + free(buf); + } + + // Check for ico7 surface + if (IsIco7) { + // printf("niiRead: reshaping\n"); + mritmp = mri_reshape(mri, 163842, 1, 1, mri->nframes); + MRIfree(&mri); + mri = mritmp; + } + + return (mri); + +} /* end niiRead3() */ + /*------------------------------------------------------------------ niiWrite() - note: there is also an nifti1Write(). Make sure to edit both. Automatically detects whether an input is Ico7