New Upstream Release - dcm2niix

Ready changes

Summary

Merged new upstream version: 1.0.20230411 (was: 1.0.20220720).

Diff

diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 0000000..a1aa35b
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,96 @@
+environment:
+  matrix:
+    - job_name: win
+      appveyor_build_worker_image: Visual Studio 2022
+
+    - job_name: linux
+      appveyor_build_worker_image: Ubuntu1604
+
+    - job_name: mac
+      appveyor_build_worker_image: macos-catalina
+
+matrix:
+  fast_finish: true
+
+version: build-{build}
+
+configuration: Release
+
+platform: x64
+
+clone_depth: 1
+
+init:
+  - ps: >-
+      $env:DATE = $(Get-Date -Format d-MMM-yyyy)
+
+      $githash = $env:APPVEYOR_REPO_COMMIT.Substring(0, 7)
+
+      $gittag = if ($env:APPVEYOR_REPO_TAG -eq $True) {"_$($env:APPVEYOR_REPO_TAG_NAME)"} else {""}
+
+      Update-AppveyorBuild -Version "$($env:DATE)_g${githash}${gittag}"
+
+      $env:RELEASE_VERSION = $(Get-Date -Format d-MMMM-yyyy)
+
+for:
+  -
+    matrix:
+      only:
+        - job_name: win
+
+    build_script:
+      - cmake -Wno-dev -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_OPENJPEG=GitHub -DUSE_JPEGLS=ON -B build
+      - cmake --build build --config %configuration%
+
+    after_build:
+      - 7z a dcm2niix_win.zip .\build\bin\* >$null
+      - appveyor PushArtifact dcm2niix_win.zip
+
+  -
+    matrix:
+      only:
+        - job_name: linux
+
+    build_script:
+      - export CC=gcc-8 CXX=g++-8
+      - cmake -Wno-dev -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_OPENJPEG=GitHub -DUSE_JPEGLS=ON -B build
+      - cmake --build build
+
+    after_build:
+      - strip -sx build/bin/*
+      - 7z a dcm2niix_lnx.zip ./build/bin/* &>/dev/null
+      - appveyor PushArtifact dcm2niix_lnx.zip
+
+  -
+    matrix:
+      only:
+        - job_name: mac
+
+    build_script:
+      - sudo xcode-select -s /Applications/Xcode-11.3.1.app
+      - cmake -Wno-dev -DCMAKE_OSX_ARCHITECTURES=x86_64 -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_OPENJPEG=GitHub -DUSE_JPEGLS=ON -B intel
+      - cmake --build intel
+      - sudo xcode-select -s /Applications/Xcode-12.3.app
+      - cmake -Wno-dev -DCMAKE_OSX_ARCHITECTURES=arm64 -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_OPENJPEG=GitHub -DUSE_JPEGLS=ON -B apple
+      - cmake --build apple
+
+    after_build:
+      - mkdir -p build/bin
+      - lipo -create -output build/bin/dcm2niix intel/bin/dcm2niix apple/bin/dcm2niix
+      - strip -Sx build/bin/*
+      - 7z a dcm2niix_macos.zip ./build/bin/* &>/dev/null
+      - appveyor PushArtifact dcm2niix_macos.zip
+
+deploy:
+  - provider: GitHub
+    tag: $(APPVEYOR_REPO_TAG_NAME)
+    release: version $(RELEASE_VERSION) ($(APPVEYOR_REPO_TAG_NAME))
+    description: ""
+    auth_token:
+      secure: gCltVLQEWsjSTRlsi8qw7FGP54ujBq60apjXkWTV954b65bOHl95hXMxxkQ734L4
+    artifact: /dcm2niix_.*\.zip/
+    draft: false
+    prerelease: false
+    on:
+      branch: master
+      APPVEYOR_REPO_TAG: true
diff --git a/.codespellrc b/.codespellrc
new file mode 100644
index 0000000..a9022c8
--- /dev/null
+++ b/.codespellrc
@@ -0,0 +1,11 @@
+[codespell]
+skip = .git,*.json,dcm_qa*
+# te - the TE used in the code often
+# clen - another common variable for length of smth
+# tage - for \tAge, inline ignores are yet to be released
+# nd - there is some kind of ND whi
+# ❯ grep -e 'trace or MD ' -e 'Trace/ND' ./console/nii_dicom_batch.cpp
+#		// the isotropic trace or MD can be calculated) often come as
+#			/*if (!dcmList[indx0].isDerived) //no need to warn if images are derived Trace/ND pair
+# ser - used in  printMessage(" acq %d img %d ser %ld ...
+ignore-words-list = te,clen,tage,nd,ser
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index a69ea02..dd0a5a4 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -42,6 +42,6 @@ Please try the following steps to resolve your issue:
 ```
 git clone --branch development https://github.com/rordenlab/dcm2niix.git
 cd dcm2niix/console
-g++  -I.  main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp  -o dcm2niix -DmyDisableOpenJPEG
+make
 ./dcm2niix ....
 ```
diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
new file mode 100644
index 0000000..c26aab6
--- /dev/null
+++ b/.github/workflows/codespell.yml
@@ -0,0 +1,19 @@
+---
+name: Codespell
+
+on:
+  push:
+    branches: [development,master]
+  pull_request:
+    branches: [development,master]
+
+jobs:
+  codespell:
+    name: Check for spelling errors
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: Codespell
+        uses: codespell-project/actions-codespell@v1
diff --git a/.gitignore b/.gitignore
index 0b9be92..0b86f87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,14 @@
 /build/
 /bin/
 /console/dcm2niix
+# Python wrapper
+*.py[co]
+*.so
+__pycache__/
+/_skbuild/
+/_cmake_test_compile/
+/dcm2niix/_dist_ver.py
+/dcm2niix/dcm2niix
+MANIFEST
+/*.egg*/
+/dist/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 8775c60..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-language: cpp
-
-git:
-  depth: 1
-
-matrix:
-  include:
-    - os: linux
-      dist: trusty
-      addons:
-        apt:
-          sources:
-            - ubuntu-toolchain-r-test
-          packages:
-            - g++-5 # support c++14
-      env:
-         - MATRIX_EVAL="CC=gcc-5 && CXX=g++-5"
-         - TARGET=lnx
-    - os: osx
-      osx_image: xcode8.3 # Tracvis default: OS X 10.12.6 and Xcode 8.3.3
-      env: TARGET=mac
-
-before_install:
-  - eval "${MATRIX_EVAL}"
-  - git submodule update --init --remote --depth=3
-
-script:
-  # - mkdir build && cd build && cmake -DBATCH_VERSION=ON -DUSE_OPENJPEG=ON -DUSE_JPEGLS=true -DZLIB_IMPLEMENTATION=Cloudflare .. && make && cd -
-  - mkdir build && cd build && cmake -DBATCH_VERSION=OFF -DUSE_OPENJPEG=ON -DUSE_JPEGLS=true -DZLIB_IMPLEMENTATION=Cloudflare .. && make && cd -
-  - export PATH=$PWD/build/bin:$PATH
-  - cd dcm_qa && ./batch.sh && cd -
-  - cd dcm_qa_nih && ./batch.sh && cd -
-  - cd dcm_qa_uih && ./batch.sh && cd -
-
-before_deploy:
-  - zip -j dcm2niix_${TARGET}.zip build/bin/*
-  - sleep 300 # make sure appveyor deployment is done, thus proper release name is set
-
-deploy:
-  provider: releases
-  api_key:
-    secure: sVIYRakcEQdMPEdGSSePtMVCMQvaohqV7NNzEErAgZ+b/4ofv2aPpJb5kNTv3JRl2FrPy7iXJ8lOUQ/95pqvimX6jv5ztksTNXtSMnHZNbjjWwIc99enPY+mSdWMO2lb9vGBWQ9GNfXjmk7MgtDHPjjygbuZfUw9fmGy4ocxkws=
-  file_glob: true
-  file: dcm2niix*.zip
-  skip_cleanup: true
-  on:
-    tags: true
diff --git a/BIDS/README.md b/BIDS/README.md
index d018fa5..37200c5 100644
--- a/BIDS/README.md
+++ b/BIDS/README.md
@@ -140,8 +140,9 @@ Fields specific to MRI scans.
 | EstimatedEffectiveEchoSpacing      | s    |                                                                                         | D          |
 | EstimatedTotalReadoutTime          | s    |                                                                                         | D          |
 | FlipAngle                          | deg  | DICOM tag 0018,1314                                                                     | B          |
+| VariableFlipAngleFlag              | b    | DICOM tag 0018,1315                                                                     | D          |
 | ImageOrientationPatientDICOM       |      | DICOM tag 0020,0037                                                                     | D          |
-| ImagingFrequency                   | MHz  | DICOM tag 0018,0084                                                                     | D          |
+| ImagingFrequency                   | MHz  | DICOM tag 0018,0084 or 0018,9098                                                        | D          |
 | InPlanePhaseEncodingDirectionDICOM |      | DICOM tag 0018,1312                                                                     | D          |
 | InversionTime                      | s    | DICOM tag 0018,0082                                                                     | B          |
 | MagneticFieldStrength              | T    | DICOM tag 0018,0087                                                                     | B          |
@@ -151,19 +152,21 @@ Fields specific to MRI scans.
 | NumberOfAverages                   |      | DICOM tag 0018,0083                                                                     | D          |
 | ParallelAcquisitionTechnique       |      | DICOM tag 0018, 9078, aka `SENSE`, `GRAPPA`                                             | B          |
 | ParallelReductionFactorInPlane     |      | DICOM tag 0018,9069                                                                     | B          |
-| ParallelReductionOutOfPlane        |      | DICOM tag 0018,9155                                                                     | D          |
+| ParallelReductionFactorOutOfPlane  |      | DICOM tag 0018,9155                                                                     | B          |
 | PartialFourierDirection            |      | DICOM tag 0018,9036                                                                     | B          |
 | PercentPhaseFOV                    |      | DICOM tag 0018,0094                                                                     | D          |
 | PercentSampling                    |      | DICOM tag 0018,0093                                                                     | D          |
 | PhaseEncodingAxis                  |      | When polarity unknown                                                                   | B          |
 | PhaseEncodingDirection             |      | When polarity known                                                                     | B          |
-| PhaseEncodingSteps                 |      | DICOM tag 0018,0089                                                                     | D          |
+| FrequencyEncodingSteps             |      | DICOM tag 0018,9058                                                        | D          |
+| PhaseEncodingSteps                 |      | DICOM tag 0018,0089 or 0018,9231 aka PhaseEncodingStepsInPlane                          | D          |
+| PhaseEncodingStepsOutOfPlane       |      | DICOM tag 0018,9232                                                        | D          |
 | PixelBandwidth                     | Hz   | DICOM tag 0018,0095                                                                     | D          |
 | ReceiveCoilName                    |      | DICOM tag 0018,1250                                                                     | B          |
 | RepetitionTime                     | s    | DICOM tag 0018,0080                                                                     | B          |
 | RepetitionTimeExcitation           | s    | DICOM tag 0018, 0080 for some manufacturers                                             | B          |
 | RepetitionTimeInversion            | s    |                                                                                         | D          |
-| SAR                                |      | DICOM tag 0018,1316                                                                     | D          |
+| SAR                                |      | DICOM tag 0018,1316 or 0018,9181 defined by 0018,9179                                   | D          |
 | SliceThickness                     | mm   | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D          |
 | SliceTiming                        | s    |                                                                                         | B          |
 | SpacingBetweenSlices               | mm   | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D          |
@@ -228,6 +231,9 @@ Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Deter
 | ASLContrastTechnique           |      | DICOM tag 0043,10A3      | D          |
 | ASLLabelingTechnique           |      | DICOM tag 0043,10A4      | D          |
 | LabelingDuration               | s    | DICOM tag 0043,10A5      | B          |
+| SliceTiming                    | s    | [see notes](https://github.com/rordenlab/dcm2niix/tree/master/GE#slice-timing)  | B          |
+| CompressedSensingFactor        |      | DICOM tag 0043,10b7      | D          |
+| DeepLearningFactor             |      | DICOM tag 0043,10ca      | D          |
 
 ### Manufacturer Philips
 
@@ -298,6 +304,7 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio
 | DelayTime                      | s                                               | Pause between EPI volumes, where TR is longer than required by TA (`sparse` imaging)                                                                                                                                                                                                                                                                                                                   | D          |
 | TxRefAmp                       | V                                               |                                                                                                                                                                                                                                                                                                                                                                                                        | D          |
 | ParallelReductionFactorInPlane |                                                 | DICOM tag 0021,1009                                                                                                                                                                                                                                                                                                                                                                                    | B          |
+| ParallelReductionFactorOutOfPlane |                                              | CSA `sPat.lAccelFact3D`                                                                                                                                                                                                                                                                                                                                                                                | B          |
 | PhaseResolution                | f                                               |                                                                                                                                                                                                                                                                                                                                                                                                        | D          |
 | PhaseOversampling              |                                                 |                                                                                                                                                                                                                                                                                                                                                                                                        | D          |
 | MultibandAccelerationFactor    |                                                 | DICOM tag 0021,1009                                                                                                                                                                                                                                                                                                                                                                                    | B          |
@@ -308,7 +315,6 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio
 | FmriExternalInfo               |                                                 |                                                                                                                                                                                                                                                                                                                                                                                                        | D          |
 | WipMemBlock                    |                                                 |                                                                                                                                                                                                                                                                                                                                                                                                        | D          |
 | AveragesDouble                 |                                                 | CSA `dAveragesDouble`, fractions possible, independent of DICOM `NumberOfAverages` (0018,0083)                                                                                                                                                                                                                                                                                                         | D          |
-| AccelFact3D                    |                                                 | 3D Acquisitions (Parallel Reduction Factor Across Slices)                                                                                                                                                                                                                                                                                                                                              | D          |
 | ProtocolName                   |                                                 | Check SeriesDescription - they might be switched around                                                                                                                                                                                                                                                                                                                                                | D          |
 | RefLinesPE                     |                                                 | # of reference lines in the phase encoding direction for acceleration (GRAPPA)                                                                                                                                                                                                                                                                                                                         | D          |
 | ConsistencyInfo                |                                                 | The more complete software version, e.g. VE11C or VE11E instead of just VE11.                                                                                                                                                                                                                                                                                                                          | D          |
@@ -321,15 +327,19 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio
 
 ### Manufacturer Siemens Magnetic Resonance Imaging (XA)
 
-Fields specific to Siemens XA-series MRI systems (Sola, Vida).
+Fields specific to [Siemens XA-series](https://github.com/rordenlab/dcm2niix/tree/master/Siemens#siemens-x-series) MRI systems (Sola, Vida).
 
-| Field                        | Unit | Comments               | Defined By |
-|------------------------------|------|------------------------|------------|
-| ReceiveCoilActiveElements    |      | DICOM tag 0021,114F    | B          |
-| BandwidthPerPixelPhaseEncode | Hz   | DICOM tag 0021,1153    | D          |
-| ScanningSequence             |      | DICOM tag 0021,105a    | D          |
-| PostLabelDelay               | s    | DICOM tag 0018,9258    | D          |
-| NonlinearGradientCorrection  | b    | 0008,0008 or 0021,1175 | B          |
+| Field                        | Unit | Comments                   | Defined By |
+|------------------------------|------|----------------------------|------------|
+| ReceiveCoilActiveElements    |      | DICOM tag 0021,114F        | B          |
+| BandwidthPerPixelPhaseEncode | Hz   | DICOM tag 0021,1153        | D          |
+| ScanningSequence             |      | DICOM tag 0021,105a        | D          |
+| PostLabelDelay               | s    | DICOM tag 0018,9258        | D          |
+| NonlinearGradientCorrection  | b    | 0008,0008 or 0021,1175     | B          |
+| PhaseEncodingDirection       |      | polarity from 0021,111c    | B          |
+| SpoilingState                |      | 0021,105B                  | B          |
+
+Siemens also includes some sequence information in the private MRPhoenixProtocol (0021,1019) tag. You can view this with [gdcmdump](https://gdcm.sourceforge.net/html/gdcmdump.html), e.g. `gdcmdump --mrprotocol img.dcm`. Fields that dcm2niix inspects include `sPat.lAccelFact3D `, `sPat.lAccelFactPE`, `sPat.lRefLinesPE` and `sPat.ucPATMode`. The behavior of dcm2niix will be more well documented as our understanding of this tag improves.
 
 ### Manufacturer UIH
 
diff --git a/COMPILE.md b/COMPILE.md
index b8bcc99..7e2fd14 100644
--- a/COMPILE.md
+++ b/COMPILE.md
@@ -8,12 +8,14 @@ Beyond the complexity of compiling the software, the only downside to adding opt
 
 The text below generally describes how to build dcm2niix using the [GCC](https://gcc.gnu.org) compiler using the `g++` command. However, the code is portable and you can use different compilers. For [clang/llvm](https://clang.llvm.org) compile using `clang++`.  If you have the [Intel C compiler](https://software.intel.com/en-us/c-compilers), you can substitute the `icc` command. The code is compatible with Microsoft's VS 2015 or later. For [Microsoft's C compiler](http://landinghub.visualstudio.com/visual-cpp-build-tools) you would use the `cl` command. In theory, the code should support other compilers, but this has not been tested. Be aware that if you do not have gcc installed the `g++` command may use a default to a compiler (e.g. clang). To check what compiler was used, run the dcm2niix software: it always reports the version and the compiler used for the build.
 
+Note that in the commands below we increase the [stack size](https://stackoverflow.com/questions/18909395/how-do-i-increase-the-stack-size-when-compiling-with-clang-on-os-x)zgit to 16mb, which is larger than the Unix (8mb) and Windows (1mb) defaults.
+
 ## Building the command line version without cmake
 
 You can also build the software without C-make. The easiest way to do this is to run the function "make" from the "console" folder. Note that this only creates the default version of dcm2niix, not the optional batch version described above. The make command simply calls the g++ compiler, and if you want you can tune this for your build. In essence, the make function simply calls
 
 ```
-g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG
+g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 ```
 
 The following sub-sections list how you can modify this basic recipe for your needs.
@@ -31,7 +33,7 @@ cmake -DUSE_OPENJPEG=ON -DCMAKE_CXX_FLAGS=-g .. && make
  If we have zlib, we can use it (-lz) and disable [miniz](https://code.google.com/p/miniz/) (-myDisableMiniZ)
 
 ```
-g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -dead_strip -o dcm2niix -lz -DmyDisableMiniZ
+g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -lz -DmyDisableMiniZ g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 ```
 
 ##### MINGW BUILD
@@ -39,7 +41,7 @@ g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cp
 If you use the (obsolete) compiler MinGW on Windows you will want to include the rare libgcc libraries with your executable so others can use it. Here I also demonstrate the optional "-DmyDisableZLib" to remove zip support.
 
 ```
-g++ -O3 -s -DmyDisableOpenJPEG -DmyDisableZLib -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -static-libgcc
+g++ -O3 -s -DmyDisableOpenJPEG -DmyDisableZLib -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -static-libgcc g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 ```
 
 ##### DISABLING CLASSIC JPEG
@@ -47,7 +49,7 @@ g++ -O3 -s -DmyDisableOpenJPEG -DmyDisableZLib -I. main_console.cpp nii_dicom.cp
 DICOM images can be stored as either raw data or compressed using one of many formats as described by the [transfer syntaxes](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Transfer_Syntaxes_and_Compressed_Images). One of the compressed formats is the lossy classic JPEG format (which is separate from and predates the lossy JPEG 2000 format). This software comes with the [NanoJPEG](http://keyj.emphy.de/nanojpeg/) library to handle these images. However, you can use the `myDisableClassicJPEG` compiler switch to remove this dependency. The resulting executable will be smaller but will not be able to convert images stored with this format.
 
 ```
-g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableClassicJPEG -DmyDisableOpenJPEG
+g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableClassicJPEG -DmyDisableOpenJPEG g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 ```
 
 ##### USING LIBJPEG-TURBO TO DECODE CLASSIC JPEG
@@ -55,18 +57,18 @@ g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp nifti1_io_co
 By default, classic JPEG images will be decoded using the [compact NanoJPEG decoder](http://keyj.emphy.de/nanojpeg/). However, the compiler directive `myTurboJPEG`  will create an executable based on the [libjpeg-turbo](http://www.libjpeg-turbo.org) library. This library is a faster decoder and is the standard for many Linux distributions. On the other hand, the lossy classic JPEG is rarely used for DICOM images, so this compilation has extra dependencies and can result in a larger executable size (for static builds).
 
 ```
-g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -DmyTurboJPEG -I/opt/libjpeg-turbo/include /opt/libjpeg-turbo/lib/libturbojpeg.a
+g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -DmyTurboJPEG -I/opt/libjpeg-turbo/include /opt/libjpeg-turbo/lib/libturbojpeg.a g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 ```
 
 ##### JPEG-LS BUILD
 
 You can compile dcm2niix to convert DICOM images compressed with the [JPEG-LS](https://en.wikipedia.org/wiki/JPEG_2000) [transfer syntaxes 1.2.840.10008.1.2.4.80 and 1.2.840.10008.1.2.4.81](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Transfer_Syntaxes_and_Compressed_Images). Decoding this format is handled by the [CharLS library](https://github.com/team-charls/charls), which is included with dcm2niix in the `charls` folder. The included code was downloaded from the CharLS website on 6 June 2018. To enable support you will need to include the `myEnableJPEGLS` compiler flag as well as a few file sin the `charls` folder. Therefore, a minimal compile (with just JPEG-LS and without JPEG2000) should look like this:
 
-`g++ -I. -DmyEnableJPEGLS  charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp  charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp  -o dcm2niix -DmyDisableOpenJPEG`
-
-Alternatively, you can decompress an image in JPEG-LS to an uncompressed DICOM using [gdcmconv](https://github.com/malaterre/GDCM)(e.g. `gdcmconv -w 3691459 3691459.dcm`). Or you can use gdcmconv compress a DICOM to JPEG-LS (e.g. `gdcmconv -L 3691459 3691459.dcm`). Alternatively, the DCMTK tool [dcmcjpls](https://support.dcmtk.org/docs/dcmcjpls.html) provides JPEG-LS support.
-
+```
+g++ -I. -DmyEnableJPEGLS  charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp  charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp  -o dcm2niix -DmyDisableOpenJPEG g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
+```
 
+Alternatively, you can decompress an image in JPEG-LS to an uncompressed DICOM using [gdcmconv](https://github.com/malaterre/GDCM) (e.g. `gdcmconv -w 3691459 3691459.dcm`). Or you can use gdcmconv compress a DICOM to JPEG-LS (e.g. `gdcmconv -L 3691459 3691459.dcm`). Alternatively, the DCMTK tool [dcmcjpls](https://support.dcmtk.org/docs/dcmcjpls.html) provides JPEG-LS support.
 
 ##### JPEG2000 BUILD
 
@@ -81,53 +83,45 @@ You can build dcm2niix with JPEG2000 decompression support using OpenJPEG 2.1.0.
 You should then be able to run:
 
 ```
-g++ -O3 -dead_strip -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -lopenjp2
+g++ -O3 -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -lopenjp2 g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 ```
 
 But in my experience this works best if you explicitly tell the software how to find the libraries, so your compile will probably look like one of these options:
 
 ```
 #for MacOS
-g++ -O3 -dead_strip -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -I/usr/local/include/openjpeg-2.1 /usr/local/lib/libopenjp2.a
-```
-```
+g++ -O3 -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -I/usr/local/include/openjpeg-2.1 /usr/local/lib/libopenjp2.a g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 #For older Linux
-g++ -O3 -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -I/usr/local/lib /usr/local/lib/libopenjp2.a
-```
-```
+g++ -O3 -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -I/usr/local/lib /usr/local/lib/libopenjp2.a g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 #For modern Linux
-g++ -O3 -s -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -lpthread -o dcm2niix -I/usr/local/include/openjpeg-2.2 ~/openjpeg-master/build/bin/libopenjp2.a
+g++ -O3 -s -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -lpthread -o dcm2niix -I/usr/local/include/openjpeg-2.2 ~/openjpeg-master/build/bin/libopenjp2.a g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 ```
 
  If you want to build this with JPEG2000 decompression support using Jasper: You will need to have the Jasper (http://www.ece.uvic.ca/~frodo/jasper/) and libjpeg (http://www.ijg.org) libraries installed which for Linux users may be as easy as running 'sudo apt-get install libjasper-dev' (otherwise, see http://www.ece.uvic.ca/~frodo/jasper/#doc). You can then run:
 
 ```
-g++ -O3 -DmyDisableOpenJPEG -DmyEnableJasper -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -s -o dcm2niix -ljasper -ljpeg
+g++ -O3 -DmyDisableOpenJPEG -DmyEnableJasper -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -s -o dcm2niix -ljasper -ljpeg g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 ```
 
 ##### VISUAL STUDIO BUILD
 
-This software can be compiled with VisualStudio 2015. This example assumes the compiler is in your path.
+This software can be compiled with [Microsoft's Visual Studio C compiler](http://landinghub.visualstudio.com/visual-cpp-build-tools). This example assumes the compiler is in your path (For Windows 11 you can run the `x64 Native Tools Command Prompt`).
+
+Crucially, you will want to [set a large stack allocation](https://learn.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=msvc-170). This allows dcm2niix to convert a huge number of DICOM images in a single pass (which requires a large amount of memory).
 
 ```
-vcvarsall amd64
-cl /EHsc main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -DmyDisableOpenJPEG /o dcm2niix
+cl /wd4018 /wd4068 /wd4101 /wd4244 /wd4267 /wd4305 /wd4308 /wd4334 /wd4800 /wd4819 /wd4996  base64.cpp cJSON.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 /Fe:dcm2niix.exe -DmyDisableOpenJPEG /link /STACK:8388608
 ```
 
-##### OSX BUILD WITH BOTH 32 AND 64-BIT SUPPORT
+##### MacOS BUILD UNIVERSAl BINARIES SUPPORT
 
-Building command line version universal binary from OSX 64 bit system:
- This requires a C compiler. With a terminal, change directory to the 'conosle' folder and run the following:
+On MacOS you can create Universal binaries, that bundle optimized code for different architectures. For example, supporting PowerPC, Intel and Apple Silicon (e.g. M1) CPUs. Further, you can optimize Intel code for either 32-bit or 64-bit operation. More details on Universal binaries and notarization is provided  [here](https://github.com/neurolabusc/NotarizeC).  
 
-```
-g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -dead_strip -arch i386 -o dcm2niix32
-```
-
-```
-g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -dead_strip -o dcm2niix64
-```
+Here is a simple example of creating independent 32-bit and 64-bit executables and then using `lipo` to create a single universal executable:
 
 ```
+g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -arch i386 -o dcm2niix32 g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
+g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix64 g++ -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -Wl,-stack_size -Wl,3f00000
 lipo -create dcm2niix32 dcm2niix64 -o dcm2niix
 ```
 
diff --git a/Canon/README.md b/Canon/README.md
index 945ef84..6a9186a 100644
--- a/Canon/README.md
+++ b/Canon/README.md
@@ -37,8 +37,7 @@ In contrast, Canon software [V6.1](https://github.com/neurolabusc/dcm_qa_canon_6
 The [BIDS format](https://bids.neuroimaging.io) can record several sequence properties that are useful for processing MRI data. The DICOM headers created by Toshiba scanners are very clean and minimalistic, and do not report several of these advanced properties. Therefore, dcm2niix is unable to populate these properties of the JSON file. This reflects a limitation of the DICOM images, not of dcm2niix.
 
  - SliceTiming is not recorded. This can be useful for slice time correction.
- - Phase encoding polarity is not record. This is useful for undistortion with [TOPUP](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup).
-
+ - Phase encoding polarity is not recorded. This is useful for undistortion with [TOPUP](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup). Clément Debacker notes that this parameter can be encoded into a XML field called `<VG>` inserted in the private tag 700D,1119. However, this requires the user to explicitly request these details and uses XML rather than DICOM so it is not easily parsed. To enable this, you must have the option "Private tag for MRI image" activated on the scanner. You can ask your local clinical scientist/application specialist to enable it.
 
 ## Sample Datasets
 
diff --git a/GE/README.md b/GE/README.md
index 0ac92e9..7efbdc9 100644
--- a/GE/README.md
+++ b/GE/README.md
@@ -28,7 +28,7 @@ Knowing the relative timing of the acquisition for each 2D slice in a 3D volume
 
 [Some sequences](https://afni.nimh.nih.gov/afni/community/board/read.php?1,154006) encode the RTIA Timer (0021,105E) element. For example, [this DV24 dataset](https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/ge-mr750-slice-timing) includes timing data, while [this DV26 dataset does not](https://github.com/neurolabusc/dcm_qa_nih). Be aware that different versions of GE software appear to use different units for 0021,105E. The DV24 example is reported in seconds, while [14.0 uses 1/10000 seconds](https://github.com/rordenlab/dcm2niix/issues/286). An example of the latter format can be found [here](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Archival_MRI). Even with the sequences that do encode the RTIA Timer, there is some debate regarding the accuracy of this element. In the example listed, the slice times are clearly wrong in the first volume. Therefore, dcm2niix always estimates slice times based on the 2nd volume in a time series.
 
-In general, fMRI acquired using GE product sequence (PSD) “epi” with the multiphase option will store slice timing in the Trigger Time (DICOM 0018,1060) element. In contrast, the popular PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics) does not save this tag (though in some cases it saves the RTIA Timer). Examples are [available](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Slice_timing_correction) for both the “epiRT” and “epi” sequences.
+In general, fMRI acquired using GE product sequence (PSD) “epi” with the multiphase option will store slice timing in the Trigger Time (DICOM 0018,1060) element. In contrast, the popular PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics) does not save this tag (though in some cases it saves the RTIA Timer). Examples are [available](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Slice_timing_correction) for both the “epiRT” and “epi” sequences. The [“epi2” is a special case](https://github.com/rordenlab/dcm2niix/issues/635).
 
 The “epiRT” sequences also allow the user to specify the `Group Delay`, which is 0 msec by default. Increasing this value will create a pause at the end of each volume, and this value is recorded in the DICOM header(as 0043,107C, reported in seconds). This option can be used for sparse designs, where one wants a pause after each value. Be aware that the `RepetitionTime` (0018,0080) reported in this header omits the group delay. So a study with a TR of 2000ms and a Group Delay of 55ms will report the values (0018,0080 = 2000, 0043,107C = 0.055), while the actual sampling rate will be 2055ms.  This is unintuitive, the TR with respect to tissue contrast is 2055ms, not the reported 2000.
 
@@ -86,7 +86,7 @@ While GE DICOMs will report if the image uses partial Fourier, it will not revea
 
 ## Phase-Encoding Polarity
 
-All EPI scans have spatial distortion, particularly those with longer readout times. Tools like [FSL topup](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide) can leverage data where two spin-echo images are acquired that are identical except for using opposite phase-encoding polarity (e.g. one uses A>P, the other P>A). Each image is distorted with the same magnitude, but in the opposite direction.  GE's Rx27 software version and later populate the [Rectilinear Phase Encode Reordering (0018,9034)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,9034)) tag which for EPI is set to either LINEAR or REVERSE_LINEAR.
+All EPI scans have spatial distortion, particularly those with longer readout times. Tools like [FSL topup](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide) can leverage data where two spin-echo images are acquired that are identical except for using opposite phase-encoding polarity (e.g. one uses A>P, the other P>A). Each image is distorted with the same magnitude, but in the opposite direction.  GE's Rx27 software version and later populate the [Rectilinear Phase Encode Reordering (0018,9034)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,9034)) tag which for EPI is set to either LINEAR or REVERSE_LINEAR. This should be given precedence to similar values in [User Define Data GE (0043,102A)](https://github.com/rordenlab/dcm2niix/issues/674).
 
 ## GE Protocol Data Block
 
diff --git a/Philips/README.md b/Philips/README.md
index 3ac7e35..3c2f81a 100644
--- a/Philips/README.md
+++ b/Philips/README.md
@@ -136,7 +136,7 @@ MyCustomDirections
 
 ```
 
-Important tags for Philips DICOM include b-value index (2005,1412) and gradient direction number (2005,1413). Knowing both 2005,1412 nd 2005,1413 uniquely identifies a volume in a series (two volumes can share either b-value or gradient direction, but can not be identical in both dimensions). With software release R5.6 and later there are additional useful tags: DIFFUSION2_KDTI (2005, 1595, Y/N) specifies whether acquisition ordering is enabled for a series. NR_OF_DIFFUSION_ORDER (2005,1599) specifies the number of vectors in a series.  DIFFUSION_ORDER (2005,1596) specifies the acquisition ordering of a series.
+Important tags for Philips DICOM include b-value index (2005,1412) and gradient direction number (2005,1413). Knowing both 2005,1412 and 2005,1413 uniquely identifies a volume in a series (two volumes can share either b-value or gradient direction, but can not be identical in both dimensions). With software release R5.6 and later there are additional useful tags: DIFFUSION2_KDTI (2005, 1595, Y/N) specifies whether acquisition ordering is enabled for a series. NR_OF_DIFFUSION_ORDER (2005,1599) specifies the number of vectors in a series.  DIFFUSION_ORDER (2005,1596) specifies the acquisition ordering of a series.
 
 ## Missing Information
 
@@ -152,7 +152,7 @@ Philips DICOMs do not allow one to determine the temporal order of volumes for d
 
 Likewise, the BIDS tag "PhaseEncodingDirection" allows tools like [eddy](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy) and [TOPUP](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup) to undistort images. While the Philips DICOM header distinguishes the phase encoding axis (e.g. anterior-posterior vs left-right) it does not encode the polarity (A->P vs P->A).
 
-Another value desirable for TOPUP is the "TotalReadoutTime". Again, one can not confidently calculate this from Philips DICOMs (though on can [approximate it if you make a few assumptions](https://github.com/nipreps/sdcflows/issues/5)). If you do decide to calculate this using values from the MRI console, be aware that the [FSL definition](https://github.com/rordenlab/dcm2niix/issues/130) is not intuitive for scans with interpolation, partial Fourier, parallel imaging, etc. However, it should be pointed out that the "TotalReadoutTime" only influences TOPUP's calibrated validation images that are typically ignored. The data used in subsequent steps will not be influenced by this value.
+Another value desirable for susceptibility distortion correction (e.g., TOPUP) is the "TotalReadoutTime". Be aware that the [FSL definition](https://github.com/rordenlab/dcm2niix/issues/130) is not intuitive for scans with parallel imaging, phase oversampling, partial Fourier, interpolation, etc. It has been challenging to establish and validate the correct equations to calculate "TotalReadoutTime" on Philips in the presence of all these possible factors, and the issue is further complicated by the fact that Philips have may changed the manner in which it reports the [relevant information over time](https://github.com/rordenlab/dcm2niix/issues/377#issuecomment-598665157). For this reason, for Philips data, `dcm2niix` currently reports `EstimatedTotalReadoutTime` and `EstimatedEffectiveEchoSpacing` (note the inclusion of `Estimated` as part of those variable names). See [issue 377](https://github.com/rordenlab/dcm2niix/issues/377) for a fuller discussion. However, it is also relevant to note that getting "TotalReadoutTime" correct is not critical to the TOPUP correction _if the readout time is identical for all the relevant images_ (i.e., the spin-echo EPI images to estimate the field, as well as any subsequent images being corrected). If that is not the case, or if you want TOPUP to return a valid estimate of the field itself (in Hz; `--fout` flag), then getting "Total Readout Time" correct is important.
 
 ## Partial Volumes
 
diff --git a/README.md b/README.md
index 39f46ff..1a9f26f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-[![Build Status](https://travis-ci.org/rordenlab/dcm2niix.svg?branch=master)](https://travis-ci.org/rordenlab/dcm2niix)
 [![Build status](https://ci.appveyor.com/api/projects/status/7o0xp2fgbhadkgn1?svg=true)](https://ci.appveyor.com/project/neurolabusc/dcm2niix)
 
 ## About
@@ -44,14 +43,15 @@ Command line usage is described in the [NITRC wiki](https://www.nitrc.org/plugin
 
 There are a couple ways to install dcm2niix
  - [Github Releases](https://github.com/rordenlab/dcm2niix/releases) provides the latest compiled executables. This is an excellent option for MacOS and Windows users. However, the provided Linux executable requires a recent version of Linux (e.g. Ubuntu 14.04 or later), so the provided Unix executable is not suitable for very old distributions. Specifically, it requires Glibc 2.19 (from 2014) or later. Users of older systems can compile their own copy of dcm2niix or download the compiled version included with MRIcroGL Glibc 2.12 (from 2011, see below).
- - Run the following command to get the latest version for Linux, Macintosh or Windows: 
+ - Run the following command to get the latest release version for Linux, Macintosh or Windows:
    * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_lnx.zip`
-   * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_mac.zip`
-   * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_mac_arm.pkg`
+   * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/macos_dcm2niix.pkg`
    * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_win.zip`
+ - Latest development version is available on [AppVeyor](https://ci.appveyor.com/project/neurolabusc/dcm2niix) for [Linux](https://ci.appveyor.com/api/projects/neurolabusc/dcm2niix/artifacts/dcm2niix_lnx.zip?job=linux), [Macintosh](https://ci.appveyor.com/api/projects/neurolabusc/dcm2niix/artifacts/dcm2niix_mac.zip?job=mac) or [Windows](https://ci.appveyor.com/api/projects/neurolabusc/dcm2niix/artifacts/dcm2niix_win.zip?job=win).
  - [MRIcroGL (NITRC)](https://www.nitrc.org/projects/mricrogl) or [MRIcroGL (GitHub)](https://github.com/rordenlab/MRIcroGL12/releases) includes dcm2niix that can be run from the command line or from the graphical user interface (select the Import menu item). The Linux version of dcm2niix is compiled on a [holy build box](https://github.com/phusion/holy-build-box), so it should run on any Linux distribution.
  - If you have a MacOS computer with Homebrew or MacPorts you can run `brew install dcm2niix` or `sudo port install dcm2niix`, respectively.
  - If you have Conda, [`conda install -c conda-forge dcm2niix`](https://anaconda.org/conda-forge/dcm2niix) on Linux, MacOS or Windows.
+ - If you have pip, `python -m pip install dcm2niix` on Linux, MacOS or Windows.
  - On Debian Linux computers you can run `sudo apt-get install dcm2niix`.
 
 
@@ -67,38 +67,30 @@ Ubuntu: `sudo apt-get install cmake pkg-config`
 
 MacOS: `brew install cmake pkg-config` or `sudo port install cmake pkgconfig`
 
-**Basic build:**
+Once these tools are available, you can compile with cmake:
+
 ```bash
 git clone https://github.com/rordenlab/dcm2niix.git
 cd dcm2niix
 mkdir build && cd build
-cmake ..
+cmake -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON ..
 make
 ```
 `dcm2niix` will be created in the `bin` subfolder. To install on the system run `make install` instead of `make` - this will copy the executable to your path so you do not have to provide the full path to the executable.
 
 In rare case if cmake fails with the message like `"Generator: execution of make failed"`, it could be fixed by ``sudo ln -s `which make` /usr/bin/gmake``.
 
-**Advanced build:**
+### Building the command line version without cmake
 
-As noted in the `Image Conversion and Compression Support` section, the software provides many optional modules with enhanced features. A common choice might be to include support for JPEG2000, [JPEG-LS](https://github.com/team-charls/charls) (this option requires a  c++14 compiler), as well as using the high performance Cloudflare zlib library (this option requires a CPU built after 2008). To build with these options simply request them when configuring cmake:
+This is the simplest way to compile dcm2niix on a Linux or MacOS computer. Be warned that this minimal version will not be able to extract DICOM images compressed with the (rarely used) JPEG2000 or JPEG-LS formats.
 
 ```bash
 git clone https://github.com/rordenlab/dcm2niix.git
-cd dcm2niix
-mkdir build && cd build
-cmake -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON ..
+cd dcm2niix/console
 make
+./dcm2niix
 ```
 
-**optional batch processing version:**
-
-The batch processing binary `dcm2niibatch` is optional. To build `dcm2niibatch` as well change the cmake command to `cmake -DBATCH_VERSION=ON ..`. This requires a compiler that supports c++11.
-
-### Building the command line version without cmake
-
-If you have any problems with the cmake build script described above or want to customize the software see the [COMPILE.md file for details on manual compilation](./COMPILE.md).
-
 ## Referencing
 
  - Li X, Morgan PS, Ashburner J, Smith J, Rorden C (2016) The first step for neuroimaging data analysis: DICOM to NIfTI conversion. J Neurosci Methods. 264:47-56. doi: 10.1016/j.jneumeth.2016.03.001. [PMID: 26945974](https://www.ncbi.nlm.nih.gov/pubmed/26945974) 
@@ -112,14 +104,14 @@ If you have any problems with the cmake build script described above or want to
  - [dicom2nifti](https://github.com/icometrix/dicom2nifti) uses the scriptable Python wrapper utilizes the [high performance  GDCMCONV](http://gdcm.sourceforge.net/wiki/index.php/Gdcmconv) executables.
  - [dicomtonifti](https://github.com/dgobbi/vtk-dicom/wiki/dicomtonifti) leverages [VTK](https://www.vtk.org/).
  - [dimon](https://afni.nimh.nih.gov/pub/dist/doc/program_help/Dimon.html) and [to3d](https://afni.nimh.nih.gov/pub/dist/doc/program_help/to3d.html) are included with AFNI.
- - [dinifti](http://as.nyu.edu/cbi/resources/Software/DINIfTI.html) is focused on conversion of Siemens data.
+ - [dinifti](https://as.nyu.edu/cbi/resources/Software/DINIfTI.html) is focused on conversion of classic Siemens DICOMs.
  - [DWIConvert](https://github.com/BRAINSia/BRAINSTools/tree/master/DWIConvert) converts DICOM images to NRRD and NIfTI formats.
- - [mcverter](http://lcni.uoregon.edu/%7Ejolinda/MRIConvert/) has great support for various vendors.
+ - [mcverter](http://lcni.uoregon.edu/%7Ejolinda/MRIConvert/) a great tool for classic DICOMs.
  - [mri_convert](https://surfer.nmr.mgh.harvard.edu/pub/docs/html/mri_convert.help.xml.html) is part of the popular FreeSurfer package. In my limited experience this tool works well for GE and Siemens data, but fails with Philips 4D datasets.
  - [MRtrix mrconvert](http://mrtrix.readthedocs.io/en/latest/reference/commands/mrconvert.html) is a useful general purpose image converter and handles DTI data well. It is an outstanding tool for modern Philips enhanced images.
  - [nanconvert](https://github.com/spinicist/nanconvert) uses the ITK library to convert DICOM from GE and proprietary Bruker to standard formats like DICOM.  
- - [PET CT viewer](http://petctviewer.org/index.php/feature/results-exports/nifti-export) for [Fiji](https://fiji.sc) can load DICOM images and export as NIfTI.
- - [Plastimatch](https://www.plastimatch.org/) is a Swiss Army knife - it computes registration, image processing, statistics and it has a basic image format converter that can convert some DICOM images to NIfTI or NRRD.
+ - [Plastimatch](https://plastimatch.org/) is a Swiss Army knife - it computes registration, image processing, 
+ statistics and it has a basic image format converter that can convert some DICOM images to NIfTI or NRRD.
  - [Simple Dicom Reader 2 (Sdr2)](http://ogles.sourceforge.net/sdr2-doc/index.html) uses [dcmtk](https://dicom.offis.de/dcmtk.php.en) to read DICOM images and convert them to the NIfTI format.
  - [SlicerHeart extension](https://github.com/SlicerHeart/SlicerHeart) is specifically designed to help 3D Slicer support ultra sound (US) images stored as DICOM.
  - [spec2nii](https://github.com/wexeee/spec2nii) converts MR spectroscopy to NIFTI.
@@ -141,14 +133,20 @@ The following tools exploit dcm2niix
   - [bidskit](https://github.com/jmtyszka/bidskit) uses dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets.
   - [BioImage Suite Web Project](https://github.com/bioimagesuiteweb/bisweb) is a JavaScript project that uses dcm2niix for its DICOM conversion module.
   - [birc-bids](https://github.com/bircibrain/birc-bids) provides a Docker/Singularity container with various BIDS conversion utilities.
+  - [BMAT](https://github.com/ColinVDB/BMAT) translates data from MRI scanners to the BIDS structure.
   - [BOLD5000_autoencoder](https://github.com/nmningmei/BOLD5000_autoencoder) uses dcm2niix to pipe imaging data into an unsupervised machine learning algorithm.
   - [boutiques-dcm2niix](https://github.com/lalet/boutiques-dcm2niix) is a dockerfile for installing and validating dcm2niix.
   - [Brain imAgiNg Analysis iN Arcana (Banana)](https://pypi.org/project/banana/) is a collection of brain imaging analysis workflows, it uses dcm2niix for format conversions.
   - [brainnetome DiffusionKit](http://diffusion.brainnetome.org/en/latest/) uses dcm2niix to convert images.
   - [BraTS-Preprocessor](https://neuronflow.github.io/BraTS-Preprocessor/) uses dcm2niix to import files for [Brain Tumor Segmentation](https://www.frontiersin.org/articles/10.3389/fnins.2020.00125/full).
+  - [CardioNIfTI](https://github.com/UK-Digital-Heart-Project/CardioNIfTI) processes cardiac MR DICOM datasets and converts them to NIfTI.
   - [clinica](https://github.com/aramis-lab/clinica) is a software platform for clinical neuroimaging studies that uses dcm2niix to convert DICOM images.
+  - [clinical_dicom2bids_smk](https://github.com/greydongilmore/clinical_dicom2bids_smk) Snakemake workflow to convert a clinical dicom directory into BIDS structure.
   - [clpipe](https://github.com/cohenlabUNC/clpipe) uses dcm2bids for DICOM import.
   - [conversion](https://github.com/pnlbwh/conversion) is a Python library that can convert dcm2niix created NIfTI files to the popular NRRD format (including DWI gradient tables). Note, recent versions of dcm2niix can directly convert DICOM images to NRRD.
+  - [convert_source](https://github.com/AdebayoBraimah/convert_source) to convert DICOM to BIDS directory layout.
+  - [CT-preprocess](https://github.com/GravO8/CT-preprocess) brain extract head CT scans.
+  - [d2b-dcm2niix](https://github.com/d2b-dev/d2b-dcm2niix) data to BIDS wrapper.
   - [DAC2BIDS](https://github.com/dangom/dac2bids) uses dcm2niibatch to create [BIDS](http://bids.neuroimaging.io/) datasets.
   - [Data2Bids](https://github.com/SIMEXP/Data2Bids) converts non-DICOM images with associated JSON files to BIDS. While this tool does not require dcm2niix, it can leverage dcm2niix output similar to niix2bids.
   - [Dcm2Bids](https://github.com/cbedetti/Dcm2Bids) uses dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. Here is a [tutorial](https://andysbrainbook.readthedocs.io/en/latest/OpenScience/OS/BIDS_Overview.html) describing usage.
@@ -162,23 +160,28 @@ The following tools exploit dcm2niix
   - [dicom2bids](https://github.com/Jolinda/lcnimodules) includes python modules for converting dicom files to nifti in a bids-compatible file structure that use dcm2niix.
   - [DICOM2BIDS](https://github.com/klsea/DICOM2BIDS) is a Python 2 script for creating BIDS files.
   - [dicom2nifti_batch](https://github.com/scanUCLA/dicom2nifti_batch) is a Matlab script for automating dcm2niix.
+  - [dicomConversionToNifti](https://github.com/bsmarine/dicomConversionToNifti) converts, de-identifies and assigns standardized naming convention to medical imaging.
   - [divest](https://github.com/jonclayden/divest) R interface to dcm2niix.
+  - [DPABI Data Processing & Analysis for Brain Imaging](http://rfmri.org/dpabi) includes dcm2niix.
   - [ExploreASL](https://sites.google.com/view/exploreasl/exploreasl) uses dcm2niix to import images.
+  - [ExploreASL-GUI](https://github.com/MauricePasternak/ExploreASL-GUI) uses dcm2niix for image conversion.
   - [ezBIDS](https://github.com/brainlife/ezbids) is a [web service](https://brainlife.io/ezbids/) for converting directory full of DICOM images into BIDS without users having to learn python nor custom configuration file.
   - [fmrif tools](https://github.com/nih-fmrif/fmrif_tools) uses dcm2niix for its [oxy2bids](https://fmrif-tools.readthedocs.io/en/latest/#) tool.
   - [fMRIprep.dcm2niix](https://github.com/BrettNordin/fMRIprep.dcm2niix) is designed to convert DICOM format to the NIfTI format.
   - [FreeSurfer](https://github.com/freesurfer/freesurfer) includes dcm2niix for image conversion.
   - [fsleyes](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FSLeyes) is a powerful Python-based image viewer. It uses dcm2niix to handle DICOM files through its fslpy libraries.
   - [Functional Real-Time Interactive Endogenous Neuromodulation and Decoding (FRIEND) Engine](https://github.com/InstitutoDOr/FriendENGINE) uses dcm2niix.
+  - [https://github.com/TamerGezici/HCF-bidser](https://github.com/TamerGezici/HCF-bidser) Jupyter notebook script for DICOM to BIDS format.
   - [heudiconv](https://github.com/nipy/heudiconv) can use dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. Data acquired using the [reproin](https://github.com/ReproNim/reproin) convention can be easily converted to BIDS.
   - [Horos (Osirix) Bids Output Extension](https://github.com/mslw/horos-bids-output) is a OsiriX / Horos plugin that uses dcm2niix for creating BIDS output.
   - [kipettools](https://github.com/mathesong/kipettools) uses dcm2niix to load PET data.
   - [LEAD-DBS](http://www.lead-dbs.org/) uses dcm2niix for [DICOM import](https://github.com/leaddbs/leaddbs/blob/master/ea_dicom_import.m).
   - [lin4neuro](http://www.lin4neuro.net/lin4neuro/18.04bionic/vm/) releases such as the English l4n-18.04.4-amd64-20200801-en.ova include MRIcroGL and dcm2niix pre-installed. This allows user with VirtualBox or VMWarePlayer to use these tools (and many other neuroimaging tools) in a graphical virtual machine.
   - [MRIcroGL](https://github.com/neurolabusc/MRIcroGL) is available for MacOS, Linux and Windows and provides a graphical interface for dcm2niix. You can get compiled copies from the [MRIcroGL NITRC web site](https://www.nitrc.org/projects/mricrogl/).
+  - [MrPyConvert](https://github.com/Jolinda/mrpyconvert) Python library dicom to bids conversion.
+  - [Nekton](https://github.com/deepc-health/nekton) is a python package for DICOM to NifTi and NifTi to DICOM-SEG and GSPS conversion.
   - [neuro_docker](https://github.com/Neurita/neuro_docker) includes dcm2niix as part of a single, static Dockerfile.
   - [NeuroDebian](http://neuro.debian.net/pkgs/dcm2niix.html) provides up-to-date version of dcm2niix for Debian-based systems.
-  - [neurodocker](https://github.com/kaczmarj/neurodocker) generates [custom](https://github.com/rordenlab/dcm2niix/issues/138) Dockerfiles given specific versions of neuroimaging software.
   - [neurodocker](https://github.com/kaczmarj/neurodocker) includes dcm2niix as a lean, minimal install Dockerfile.
   - [NeuroElf](http://neuroelf.net) can use dcm2niix to convert DICOM images.
   - [Neuroinformatics Database (NiDB)](https://github.com/gbook/nidb) is designed to store, retrieve, analyze, and share neuroimaging data. It uses dcm2niix for image QA and handling some formats. 
@@ -186,9 +189,11 @@ The following tools exploit dcm2niix
   - [niix2bids](https://github.com/benoitberanger/niix2bids ) attempts to automatically convert Siemens MRI images converted by dcm2niix to BIDS.
   - [nipype](https://github.com/nipy/nipype) can use dcm2niix to convert images.
   - [PET2BIDS](https://github.com/openneuropet/PET2BIDS) uses dcm2niix for DICOM images.
+  - [pl-dcm2niix](https://github.com/FNNDSC/pl-dcm2niix) is a ChRIS wrapper for dcm2niix.
   - [py2bids](https://github.com/Jolinda/py2bids) dcm2niix dicom to bids conversion wrapper.
   - [pyBIDSconv provides a graphical format for converting DICOM images to the BIDS format](https://github.com/DrMichaelLindner/pyBIDSconv). It includes clever default heuristics for identifying Siemens scans.
   - [pydcm2niix is a Python module for working with dcm2niix](https://github.com/jstutters/pydcm2niix).
+  - [pydra-dcm2bids](https://github.com/aramis-lab/pydra-dcm2bids) supports Pydra tasks for dcm2bids.
   - [pydra-dcm2niix](https://github.com/nipype/pydra-dcm2niix) is a contains Pydra task interface for dcm2niix.
   - [qsm](https://github.com/CAIsr/qsm) Quantitative Susceptibility Mapping software.
   - [reproin](https://github.com/ReproNim/reproin) is a setup for automatic generation of shareable, version-controlled BIDS datasets from MR scanners.
@@ -199,4 +204,5 @@ The following tools exploit dcm2niix
   - [tar2bids](https://github.com/khanlab/tar2bids) converts DICOM tarball(s) to BIDS using heudiconv which invokes dcm2niix.
   - [TORTOISE](https://tortoise.nibib.nih.gov) is used for processing diffusion MRI data, and uses dcm2niix to import DICOM images.
   - [TractoR (Tracto­graphy with R) uses dcm2niix for image conversion](http://www.tractor-mri.org.uk/TractoR-and-DICOM).
+  - [twice_exceptionality_repository](https://github.com/avery-water/twice_exceptionality_repository) converts DICOM to BIDS format, creates masks, and runs VBM.
   - [XNAT2BIDS](https://github.com/kamillipi/2bids) is a simple xnat pipeline to convert DICOM scans to BIDS-compatible output.
diff --git a/Siemens/README.md b/Siemens/README.md
index d387031..3e88320 100644
--- a/Siemens/README.md
+++ b/Siemens/README.md
@@ -6,7 +6,7 @@ dcm2niix attempts to convert Siemens DICOM format images to NIfTI. This page des
 
 Siemens MR is named by Series, Generation, Major Version and Minor Version. Prior to the Siemens Vida, all contemporary Siemens MRI systems (Trio, Prisma, Skyra, etc) were part of the V series. So a Trio might be on VB17, and a Prisma on VE11 (series 'V', generation 'E', major version '1', minor version '1'). The 3T Vida and 1.5T Sola introduce the X-series (XA10, XA11, XA20). Since the V-series was dominant for so long, most users simply omit the series, e.g. referring to a system as `B19`. However, Siemens has recently introduced a new X-series.
 
-The DICOM images exported by the X-series is radically different than the V-series. The images lack the proprietary CSA header with its rich meta data.  
+The DICOM images exported by the X-series is [radically different](https://wikis.utexas.edu/display/IRC/New+Enhanced+DICOM+format) than the V-series. The images lack the proprietary CSA header with its rich meta data.
 
 X-series users are strongly encouraged to export data using the "Enhanced" format and to not use any of the "Anonymize" features on the console. The consequences of these options is discussed in detail in [issue 236](https://github.com/rordenlab/dcm2niix/issues/236). Siemens notes `We highly recommend that the Enhanced DICOM format be used. This is because this format retains far more information in the header`. Failure to export data in this format has led to catastrophic data loss for numerous users (for publicly reported details see issues [203](https://github.com/rordenlab/dcm2niix/issues/203), [236](https://github.com/rordenlab/dcm2niix/issues/236), [240](https://github.com/rordenlab/dcm2niix/issues/240), [274](https://github.com/rordenlab/dcm2niix/issues/274), [303](https://github.com/rordenlab/dcm2niix/issues/303), [370](https://github.com/rordenlab/dcm2niix/issues/370), [394](https://github.com/rordenlab/dcm2niix/issues/394)). This reflects limitations of the DICOM data, not dcm2niix.
 
@@ -38,6 +38,7 @@ The private `ICE_Dims` (0021,1106) tag can prove useful for parsing data. The li
 (0021,1106) LO [X_4_1_1_1_1_160_1_1_1_1_1_277] #  ICE_Dims
 ```
 
+0. coi = [coil number](https://github.com/rordenlab/dcm2niix/issues/631) (X: combined from multiple coils)
 1. eco = echo number 
 2. phs = phase encode
 3. set = 
diff --git a/SuperBuild/External-CLOUDFLARE-ZLIB.cmake b/SuperBuild/External-CLOUDFLARE-ZLIB.cmake
index 9f064eb..4c6a6ac 100644
--- a/SuperBuild/External-CLOUDFLARE-ZLIB.cmake
+++ b/SuperBuild/External-CLOUDFLARE-ZLIB.cmake
@@ -1,13 +1,18 @@
 set(CLOUDFLARE_BRANCH gcc.amd64) # Cloudflare zlib branch
 
 ExternalProject_Add(zlib
-    GIT_REPOSITORY "${git_protocol}://github.com/ningfei/zlib.git"
+    GIT_REPOSITORY "https://github.com/ningfei/zlib.git"
     GIT_TAG "${CLOUDFLARE_BRANCH}"
     SOURCE_DIR cloudflare-zlib
     BINARY_DIR cloudflare-zlib-build
     CMAKE_ARGS
         -Wno-dev
-        -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+        ${EXTERNAL_PROJECT_BUILD_TYPE_CMAKE_ARGS}
+        ${OSX_ARCHITECTURES}
+        # Compiler settings
+        -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}
+        -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}
+        # Install directories
         -DCMAKE_INSTALL_PREFIX:PATH=${DEP_INSTALL_DIR}
 )
 
diff --git a/SuperBuild/External-OPENJPEG.cmake b/SuperBuild/External-OPENJPEG.cmake
index 565c14c..1d99ac2 100644
--- a/SuperBuild/External-OPENJPEG.cmake
+++ b/SuperBuild/External-OPENJPEG.cmake
@@ -1,14 +1,19 @@
 set(OPENJPEG_TAG  v2.1-static) # version v2.1-static
 
 ExternalProject_Add(openjpeg
-    GIT_REPOSITORY "${git_protocol}://github.com/ningfei/openjpeg.git"
+    GIT_REPOSITORY "https://github.com/ningfei/openjpeg.git"
     GIT_TAG "${OPENJPEG_TAG}"
     SOURCE_DIR openjpeg
     BINARY_DIR openjpeg-build
     CMAKE_ARGS
         -Wno-dev
         --no-warn-unused-cli
-        -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+        ${EXTERNAL_PROJECT_BUILD_TYPE_CMAKE_ARGS}
+        ${OSX_ARCHITECTURES}
+        # Compiler settings
+        -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}
+        # Not used -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}
+        # Install directories
         -DCMAKE_INSTALL_PREFIX:PATH=${DEP_INSTALL_DIR}
 )
 
diff --git a/SuperBuild/External-YAML-CPP.cmake b/SuperBuild/External-YAML-CPP.cmake
index 2e1aef1..ed85ca7 100644
--- a/SuperBuild/External-YAML-CPP.cmake
+++ b/SuperBuild/External-YAML-CPP.cmake
@@ -1,14 +1,19 @@
 set(YAML-CPP_TAG yaml-cpp-0.5.3) # version yaml-cpp-0.5.3
 
 ExternalProject_Add(yaml-cpp
-    GIT_REPOSITORY "${git_protocol}://github.com/ningfei/yaml-cpp.git"
+    GIT_REPOSITORY "https://github.com/ningfei/yaml-cpp.git"
     GIT_TAG "${YAML-CPP_TAG}"
     SOURCE_DIR yaml-cpp
     BINARY_DIR yaml-cpp-build
     CMAKE_ARGS
         -Wno-dev
         --no-warn-unused-cli
-        -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+        ${EXTERNAL_PROJECT_BUILD_TYPE_CMAKE_ARGS}
+        ${OSX_ARCHITECTURES}
+        # Compiler settings
+        -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}
+        -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}
+        # Install directories
         -DCMAKE_INSTALL_PREFIX:PATH=${DEP_INSTALL_DIR}
 )
 
diff --git a/SuperBuild/SuperBuild.cmake b/SuperBuild/SuperBuild.cmake
index e1057a0..98f20c6 100644
--- a/SuperBuild/SuperBuild.cmake
+++ b/SuperBuild/SuperBuild.cmake
@@ -4,20 +4,8 @@ if(NOT GIT_FOUND)
     message(FATAL_ERROR "Cannot find Git. Git is required for Superbuild")
 endif()
 
-# Use git protocol or not
-option(USE_GIT_PROTOCOL "If behind a firewall turn this off to use http instead." OFF)
-if(USE_GIT_PROTOCOL)
-    set(git_protocol "git")
-else()
-    set(git_protocol "https")
-endif()
+include(${CMAKE_SOURCE_DIR}/cmake/dcm2niixInitializeBuildType.cmake)
 
-# Basic CMake build settings
-if(NOT CMAKE_BUILD_TYPE)
-    set(CMAKE_BUILD_TYPE "Release" CACHE STRING
-        "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
-    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS  "Debug;Release;RelWithDebInfo;MinSizeRel")
-endif()
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
 
 option(USE_STATIC_RUNTIME "Use static runtime" ON)
@@ -36,9 +24,14 @@ if(USE_STATIC_RUNTIME)
     endif()
 endif()
 
+if(APPLE)
+    set(OSX_ARCHITECTURES "-DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}")
+endif()
+
 option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF)
 option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF)
-option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF)
+set(USE_OPENJPEG "OFF" CACHE STRING "Build with JPEG2000 support using OpenJPEG.")
+set_property(CACHE USE_OPENJPEG PROPERTY STRINGS  "OFF;GitHub;System;Custom")
 option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF)
 option(USE_JNIFTI "Build with JNIFTI support" ON)
 
@@ -67,56 +60,68 @@ else()
     set(DEP_INSTALL_DIR ${CMAKE_BINARY_DIR})
 endif()
 
-if(USE_OPENJPEG)
+if(NOT ${USE_OPENJPEG} STREQUAL "OFF")
     message("-- Build with OpenJPEG: ${USE_OPENJPEG}")
 
     if(OpenJPEG_DIR)
+        set(USE_OPENJPEG "Custom" CACHE STRING "Build with JPEG2000 support using OpenJPEG." FORCE)
         set(OpenJPEG_DIR "${OpenJPEG_DIR}" CACHE PATH "Path to OpenJPEG configuration file"  FORCE)
         message("--   Using OpenJPEG library from ${OpenJPEG_DIR}")
-    else()
+    elseif(${USE_OPENJPEG} STREQUAL "System")
         find_package(PkgConfig)
         if(PKG_CONFIG_FOUND)
             pkg_check_modules(OPENJPEG libopenjp2)
         endif()
 
-        if(OPENJPEG_FOUND AND NOT ${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg")
-	    set(OpenJPEG_DIR ${OPENJPEG_LIBDIR}/openjpeg-${OPENJPEG_VERSION} CACHE PATH "Path to OpenJPEG configuration file" FORCE)
-            message("--   Using OpenJPEG library from ${OpenJPEG_DIR}")
-        else()
-            if(${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg")
+        if(OPENJPEG_FOUND)
+            if(NOT ${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg")
+        	    set(OpenJPEG_DIR ${OPENJPEG_LIBDIR}/openjpeg-${OPENJPEG_VERSION} CACHE PATH "Path to OpenJPEG configuration file" FORCE)
+                message("--   Using OpenJPEG library from ${OpenJPEG_DIR}")
+            else()
                 message("--   Unable to use GDCM's internal OpenJPEG")
             endif()
-            include(${CMAKE_SOURCE_DIR}/SuperBuild/External-OPENJPEG.cmake)
-            list(APPEND DEPENDENCIES openjpeg)
-            set(BUILD_OPENJPEG TRUE)
-            message("--   Will build OpenJPEG library from github")
         endif()
     endif()
+
+    if(${USE_OPENJPEG} STREQUAL "GitHub" OR NOT OpenJPEG_DIR)
+        set(USE_OPENJPEG "GitHub" CACHE STRING "Build with JPEG2000 support using OpenJPEG." FORCE)
+        include(${CMAKE_SOURCE_DIR}/SuperBuild/External-OPENJPEG.cmake)
+        list(APPEND DEPENDENCIES openjpeg)
+        set(BUILD_OPENJPEG TRUE)
+        message("--   Will build OpenJPEG library from GitHub")
+    endif()
 endif()
 
 if(BATCH_VERSION)
     message("-- Build dcm2niibatch: ${BATCH_VERSION}")
 
+    set(YAML-CPP_IMPLEMENTATION "GitHub" CACHE STRING "Choose yaml-cpp implementation.")
+    set_property(CACHE YAML-CPP_IMPLEMENTATION PROPERTY STRINGS  "GitHub;System;Custom")
+
     if(YAML-CPP_DIR)
+        set(YAML-CPP_IMPLEMENTATION "Custom" CACHE STRING "Choose yaml-cpp implementation." FORCE)
         set(YAML-CPP_DIR ${YAML-CPP_DIR} CACHE PATH "Path to yaml-cpp configuration file"  FORCE)
         message("--   Using yaml-cpp library from ${YAML-CPP_DIR}")
-    else()
+    elseif(${YAML-CPP_IMPLEMENTATION} STREQUAL "System")
         find_package(PkgConfig)
         if(PKG_CONFIG_FOUND)
             pkg_check_modules(YAML-CPP yaml-cpp)
         endif()
 
-        # Build from github if not found or version < 0.5.3
+        # Build from GitHub if not found or version < 0.5.3
         if(YAML-CPP_FOUND AND NOT (YAML-CPP_VERSION VERSION_LESS "0.5.3"))
             set(YAML-CPP_DIR ${YAML-CPP_LIBDIR}/cmake/yaml-cpp CACHE PATH "Path to yaml-cpp configuration file"  FORCE)
             message("--   Using yaml-cpp library from ${YAML-CPP_DIR}")
-        else()
-            include(${CMAKE_SOURCE_DIR}/SuperBuild/External-YAML-CPP.cmake)
-            list(APPEND DEPENDENCIES yaml-cpp)
-            set(BUILD_YAML-CPP TRUE)
-            message("--   Will build yaml-cpp library from github")
         endif()
     endif()
+
+    if(${YAML-CPP_IMPLEMENTATION} STREQUAL "GitHub" OR NOT YAML-CPP_DIR)
+        set(YAML-CPP_IMPLEMENTATION "GitHub" CACHE STRING "Choose yaml-cpp implementation." FORCE)
+        include(${CMAKE_SOURCE_DIR}/SuperBuild/External-YAML-CPP.cmake)
+        list(APPEND DEPENDENCIES yaml-cpp)
+        set(BUILD_YAML-CPP TRUE)
+        message("--   Will build yaml-cpp library from GitHub")
+    endif()
 endif()
 
 set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.")
@@ -126,7 +131,7 @@ if(${ZLIB_IMPLEMENTATION} STREQUAL "Cloudflare")
     include(${CMAKE_SOURCE_DIR}/SuperBuild/External-CLOUDFLARE-ZLIB.cmake)
     list(APPEND DEPENDENCIES zlib)
     set(BUILD_CLOUDFLARE-ZLIB TRUE)
-    message("--   Will build Cloudflare zlib from github")
+    message("--   Will build Cloudflare zlib from GitHub")
 elseif(${ZLIB_IMPLEMENTATION} STREQUAL "Custom")
     set(ZLIB_ROOT ${ZLIB_ROOT} CACHE PATH "Specify custom zlib root directory.")
     if(NOT ZLIB_ROOT)
@@ -142,16 +147,23 @@ ExternalProject_Add(console
     CMAKE_ARGS
         -Wno-dev
         --no-warn-unused-cli
-        -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+        ${EXTERNAL_PROJECT_BUILD_TYPE_CMAKE_ARGS}
+        ${OSX_ARCHITECTURES}
+        # Install directories
         -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}
+        # Compiler settings
+        -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}
         -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS}
+        -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}
         -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
+        # Options
         -DCMAKE_VERBOSE_MAKEFILE:BOOL=${CMAKE_VERBOSE_MAKEFILE}
         -DUSE_STATIC_RUNTIME:BOOL=${USE_STATIC_RUNTIME}
         -DUSE_TURBOJPEG:BOOL=${USE_TURBOJPEG}
         -DUSE_JASPER:BOOL=${USE_JASPER}
         -DUSE_JPEGLS:BOOL=${USE_JPEGLS}
         -DUSE_JNIFTI:BOOL=${USE_JNIFTI}
+        # ZLIB
         -DZLIB_IMPLEMENTATION:STRING=${ZLIB_IMPLEMENTATION}
         -DZLIB_ROOT:PATH=${ZLIB_ROOT}
          # OpenJPEG
@@ -164,8 +176,10 @@ ExternalProject_Add(console
         -DBUILD_DCM2NIIXFSLIB:BOOL=${BUILD_DCM2NIIXFSLIB}
 )
 
-install(DIRECTORY ${CMAKE_BINARY_DIR}/bin/ DESTINATION bin
-        USE_SOURCE_PERMISSIONS)
+if(SKBUILD)
+    install(DIRECTORY ${CMAKE_BINARY_DIR}/bin/ DESTINATION dcm2niix USE_SOURCE_PERMISSIONS)
+endif()
+install(DIRECTORY ${CMAKE_BINARY_DIR}/bin/ DESTINATION bin USE_SOURCE_PERMISSIONS)
 
 option(BUILD_DOCS "Build documentation (manpages)" OFF)
 if(BUILD_DOCS)
diff --git a/VERSIONS.md b/VERSIONS.md
index 0f61dc0..c1f2a22 100644
--- a/VERSIONS.md
+++ b/VERSIONS.md
@@ -1,4 +1,8 @@
-## Versions
+## Modern Releases
+
+ - [See Github releases for major release notes](https://github.com/rordenlab/dcm2niix/releases).
+
+## Legacy Releases
 
 14-November-2018
  - [GE images provide more BIDS tags](https://github.com/rordenlab/dcm2niix/issues/163).
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 65b1f88..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,62 +0,0 @@
-version: build-{build}
-
-image: Visual Studio 2015
-
-configuration: Release
-
-platform: x64
-
-clone_depth: 1
-
-clone_folder: c:\projects\dcm2niix
-
-init:
-- ps: >-
-    $env:DATE = $(Get-Date -Format d-MMM-yyyy)
-
-    $githash = $env:APPVEYOR_REPO_COMMIT.Substring(0, 7)
-
-    $gittag = if ($env:APPVEYOR_REPO_TAG -eq $True) {"_$($env:APPVEYOR_REPO_TAG_NAME)"} else {""}
-
-    Update-AppveyorBuild -Version "$($env:DATE)_g${githash}${gittag}"
-
-    $env:release_version = $(Get-Date -Format d-MMMM-yyyy)
-
-before_build:
-- cmd: >-
-    echo "Running cmake"
-
-    mkdir c:\projects\dcm2niix\build
-
-    cd c:\projects\dcm2niix\build
-
-    cmake -Wno-dev -G "Visual Studio 14 2015 Win64" -DBATCH_VERSION=ON -DUSE_OPENJPEG=ON -DUSE_JPEGLS=true ..\
-
-build:
-  project: c:\projects\dcm2niix\build\dcm2niix.sln
-
-  verbosity: normal
-
-after_build:
-- ps: >-
-    cd c:\projects\dcm2niix
-
-    7z a dcm2niix_win.zip c:\projects\dcm2niix\build\bin\* >$null
-
-artifacts:
-  - path: dcm2niix*.zip
-    name: dcm2niix
-
-deploy:
-  - provider: GitHub
-    tag: $(appveyor_repo_tag_name)
-    release: version $(release_version) ($(appveyor_repo_tag_name))
-    description: ""
-    auth_token:
-      secure: gCltVLQEWsjSTRlsi8qw7FGP54ujBq60apjXkWTV954b65bOHl95hXMxxkQ734L4
-    artifact: dcm2niix
-    draft: false
-    prerelease: false
-    on:
-      branch: master
-      appveyor_repo_tag: true
diff --git a/cmake/dcm2niixInitializeBuildType.cmake b/cmake/dcm2niixInitializeBuildType.cmake
new file mode 100644
index 0000000..b21ba4a
--- /dev/null
+++ b/cmake/dcm2niixInitializeBuildType.cmake
@@ -0,0 +1,47 @@
+# This module allows to consistently manage the initialization and setting of build type
+# CMake variables.
+#
+# It sets the variable EXTERNAL_PROJECT_BUILD_TYPE_CMAKE_ARGS based on the CMake generator
+# being used:
+# * If a multi-config generator (e.g Visual Studio) is used, it sets the variable with
+#   CMAKE_CONFIGURATION_TYPES.
+# * If a single-config generator (e.g Unix Makefiles) is used, it sets the variable with
+#   CMAKE_BUILD_TYPE.
+#
+# Adapted from https://github.com/Slicer/Slicer/blob/5.2/CMake/SlicerInitializeBuildType.cmake
+
+# Default build type to use if none was specified
+if(NOT DEFINED dcm2niix_DEFAULT_BUILD_TYPE)
+    set(dcm2niix_DEFAULT_BUILD_TYPE "Release")
+endif()
+
+# Set a default build type if none was specified
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+
+    message(STATUS "Setting build type to '${dcm2niix_DEFAULT_BUILD_TYPE}' as none was specified.")
+
+    set(CMAKE_BUILD_TYPE ${dcm2niix_DEFAULT_BUILD_TYPE} CACHE STRING "Choose the type of build." FORCE)
+    mark_as_advanced(CMAKE_BUILD_TYPE)
+
+    # Set the possible values of build type for cmake-gui
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+        "Debug"
+        "Release"
+        "MinSizeRel"
+        "RelWithDebInfo"
+        )
+endif()
+
+# Pass variables to dependent projects
+if(COMMAND ExternalProject_Add)
+    if(NOT CMAKE_CONFIGURATION_TYPES)
+        set(EXTERNAL_PROJECT_BUILD_TYPE_CMAKE_ARGS
+            -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+            )
+    else()
+        set(EXTERNAL_PROJECT_BUILD_TYPE_CMAKE_ARGS
+            -DCMAKE_CONFIGURATION_TYPES:STRING=${CMAKE_CONFIGURATION_TYPES}
+            )
+    endif()
+endif()
+
diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt
index 0dcfe30..aa52bd7 100644
--- a/console/CMakeLists.txt
+++ b/console/CMakeLists.txt
@@ -1,257 +1,262 @@
-cmake_minimum_required(VERSION 2.8.11)
-
-project(console)
-
-# Option Choose whether to use static runtime
-include(ucm.cmake)
-option(USE_STATIC_RUNTIME "Use static runtime" ON)
-if(USE_STATIC_RUNTIME)
-    ucm_set_runtime(STATIC)
-else()
-    ucm_set_runtime(DYNAMIC)
-endif()
-
-# Basic CMake build settings
-if(NOT CMAKE_BUILD_TYPE)
-    set(CMAKE_BUILD_TYPE "Release" CACHE STRING
-        "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
-    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS  "Debug;Release;RelWithDebInfo;MinSizeRel")
-endif()
-
-if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
-    # using Clang
-    add_definitions(-fno-caret-diagnostics)
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip")
-elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
-    # using GCC
-    if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 7.1.0))
-        add_definitions(-Wno-format-overflow)    # available since GCC 7.1.0
-    endif()
-    if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.5.0))
-        add_definitions(-Wno-unused-result)    # available since GCC 4.5.0
-    endif()
-    if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.8.0))
-        add_definitions(-fno-diagnostics-show-caret)    # available since GCC 4.8.0
-    endif()
-elseif(MSVC)
-    # using Visual Studio C++
-    add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4018")   # '<': signed/unsigned mismatch
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4068")   # unknown pragma
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4101")   # unreferenced local variable
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244")   # 'initializing': conversion from 'double' to 'int', possible loss of data
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267")   # 'initializing': conversion from 'size_t' to 'int', possible loss of data
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4305")   # 'argument': truncation from 'double' to 'float'
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4308")   # negative integral constant converted to unsigned type
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4334")   # '<<': result of 32-bit shift implicitly converted to 64 bits
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800")   # 'uint32_t' : forcing value to bool 'true' or 'false'
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819")   # The file contains a character that cannot be represented in the current code page
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4996")   # 'access': The POSIX name for this item is deprecated
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:8388608")  # set "Stack Reserve Size" to 8MB (default value is 1MB)
-endif()
-
-# Compiler dependent flags
-include (CheckCXXCompilerFlag)
-if(UNIX)
-    check_cxx_compiler_flag(-march=armv8-a+crc ARM_CRC)
-    if(ARM_CRC)
-        # wrong answer for Apple Silicon: check_cxx_compiler_flag(-msse2 HAS_SSE2)
-    else()
-	    check_cxx_compiler_flag(-msse2 HAS_SSE2)
-	    if(HAS_SSE2)
-	        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -mfpmath=sse")
-	    endif()
-	endif()
-endif()
-
-set(PROGRAMS dcm2niix)
-
-option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF)
-option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF)
-option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF)
-option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF)
-
-option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF)
-
-option(BUILD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF)
-
-if(USE_OPENJPEG OR USE_TURBOJPEG OR USE_JASPER)
-    message("-- Set BUILD_DCM2NIIXFSLIB to OFF since USE_TURBOJPEG/USE_JASPER/USE_OPENJPEG is ON.")
-    set(BUILD_DCM2NIIXFSLIB OFF CACHE BOOL "Build libdcm2niixfs.a" FORCE)
-endif()
-
-set(DCM2NIIX_SRCS
-    main_console.cpp
-    nii_dicom.cpp
-    jpg_0XC3.cpp
-    ujpeg.cpp
-    nifti1_io_core.cpp
-    nii_foreign.cpp
-    nii_ortho.cpp
-    nii_dicom_batch.cpp)
-
-
-option(USE_JNIfTI "Build with JNIfTI support" ON)
-if(USE_JNIFTI)
-    add_definitions(-DmyEnableJNIfTI)
-    set(DCM2NIIX_SRCS ${DCM2NIIX_SRCS} cJSON.cpp base64.cpp)
-endif()
-
-if(BUILD_DCM2NIIXFSLIB)
-    set(DCM2NIIXFSLIB dcm2niixfs)
-    set(DCM2NIIXFSLIB_SRCS
-        dcm2niix_fswrapper.cpp
-        nii_dicom.cpp
-        jpg_0XC3.cpp
-        ujpeg.cpp
-        nifti1_io_core.cpp
-        nii_foreign.cpp
-        nii_ortho.cpp
-        nii_dicom_batch.cpp)
-endif()
-
-if(USE_JPEGLS)
-    add_definitions(-DmyEnableJPEGLS)
-    if(MSVC)
-        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})
-
-    if(BUILD_DCM2NIIXFSLIB)
-        add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS} ${CHARLS_SRCS})
-    endif()
-else()
-    add_executable(dcm2niix ${DCM2NIIX_SRCS})
-
-    if(BUILD_DCM2NIIXFSLIB)
-        add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS})
-    endif()
-endif()
-
-set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.")
-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()
-
-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()
-
-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()
-
-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()
-
-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_JNIFTI)
-        set(DCM2NIIBATCH_SRCS ${DCM2NIIBATCH_SRCS} cJSON.cpp base64.cpp)
-    endif()
-
-    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(BUILD_DCM2NIIXFSLIB)
-    target_compile_definitions(${DCM2NIIXFSLIB} PRIVATE -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO)
-endif()
-
-if(APPLE)
-    message("--   Adding Apple plist")
-    set_target_properties(dcm2niix PROPERTIES LINK_FLAGS "-Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_SOURCE_DIR}/Info.plist")
-    #Apple notarization requires a Info.plist
-    # For .app bundles, the Info.plist is a separate file, for executables it is appended as a section
-    #you can check that the Info.plist section has been inserted with either of these commands
-    # otool -l ./dcm2niix | grep info_plist -B1 -A10
-    # launchctl plist ./dcm2niix
-endif()
-
-install(TARGETS ${PROGRAMS} DESTINATION bin)
-
-if(BUILD_DCM2NIIXFSLIB)
-    install(TARGETS ${DCM2NIIXFSLIB} DESTINATION lib)
-endif()
+cmake_minimum_required(VERSION 2.8.11)
+
+project(console)
+
+# Option Choose whether to use static runtime
+include(ucm.cmake)
+option(USE_STATIC_RUNTIME "Use static runtime" ON)
+if(USE_STATIC_RUNTIME)
+    ucm_set_runtime(STATIC)
+else()
+    ucm_set_runtime(DYNAMIC)
+endif()
+
+# Basic CMake build settings
+if(NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE "Release" CACHE STRING
+        "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS  "Debug;Release;RelWithDebInfo;MinSizeRel")
+endif()
+
+if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
+    # using Clang
+    add_definitions(-fno-caret-diagnostics)
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip -Wl,-stack_size -Wl,0x1000000")
+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:16388608")  # set "Stack Reserve Size" to 16MB (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)
+
+option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF)
+option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF)
+option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF)
+option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF)
+
+option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF)
+
+option(BUILD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF)
+
+if(USE_OPENJPEG OR USE_TURBOJPEG OR USE_JASPER)
+    message("-- Set BUILD_DCM2NIIXFSLIB to OFF since USE_TURBOJPEG/USE_JASPER/USE_OPENJPEG is ON.")
+    set(BUILD_DCM2NIIXFSLIB OFF CACHE BOOL "Build libdcm2niixfs.a" FORCE)
+endif()
+
+set(DCM2NIIX_SRCS
+    main_console.cpp
+    nii_dicom.cpp
+    jpg_0XC3.cpp
+    ujpeg.cpp
+    nifti1_io_core.cpp
+    nii_foreign.cpp
+    nii_ortho.cpp
+    nii_dicom_batch.cpp)
+
+
+option(USE_JNIfTI "Build with JNIfTI support" ON)
+if(USE_JNIFTI)
+    add_definitions(-DmyEnableJNIfTI)
+    set(DCM2NIIX_SRCS ${DCM2NIIX_SRCS} cJSON.cpp base64.cpp)
+endif()
+
+if(BUILD_DCM2NIIXFSLIB)
+    set(DCM2NIIXFSLIB dcm2niixfs)
+    set(DCM2NIIXFSLIB_SRCS
+        dcm2niix_fswrapper.cpp
+        nii_dicom.cpp
+        jpg_0XC3.cpp
+        ujpeg.cpp
+        nifti1_io_core.cpp
+        nii_foreign.cpp
+        nii_ortho.cpp
+        nii_dicom_batch.cpp)
+endif()
+
+if(USE_JPEGLS)
+    add_definitions(-DmyEnableJPEGLS)
+    if(MSVC)
+        add_definitions(-DCHARLS_STATIC)
+    else()
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
+    endif()
+
+    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})
+
+    if(BUILD_DCM2NIIXFSLIB)
+        add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS} ${CHARLS_SRCS})
+    endif()
+else()
+    add_executable(dcm2niix ${DCM2NIIX_SRCS})
+
+    if(BUILD_DCM2NIIXFSLIB)
+        add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS})
+    endif()
+endif()
+
+set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.")
+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()
+
+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()
+
+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()
+
+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()
+
+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_JNIFTI)
+        set(DCM2NIIBATCH_SRCS ${DCM2NIIBATCH_SRCS} cJSON.cpp base64.cpp)
+    endif()
+
+    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)
+    if(YAML-CPP_FOUND AND NOT YAML_CPP_LIBRARIES)
+        # workaround for yaml-cpp-devel-0.7.0-1.fc38 on Fedora Rawhide
+        set(YAML_CPP_LIBRARIES yaml-cpp)
+    endif()
+    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(BUILD_DCM2NIIXFSLIB)
+    target_compile_definitions(${DCM2NIIXFSLIB} PRIVATE -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO)
+endif()
+
+if(APPLE)
+    message("--   Adding Apple plist")
+    set_target_properties(dcm2niix PROPERTIES LINK_FLAGS "-Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_SOURCE_DIR}/Info.plist")
+    #Apple notarization requires a Info.plist
+    # For .app bundles, the Info.plist is a separate file, for executables it is appended as a section
+    #you can check that the Info.plist section has been inserted with either of these commands
+    # otool -l ./dcm2niix | grep info_plist -B1 -A10
+    # launchctl plist ./dcm2niix
+endif()
+
+install(TARGETS ${PROGRAMS} DESTINATION bin)
+
+if(BUILD_DCM2NIIXFSLIB)
+    install(TARGETS ${DCM2NIIXFSLIB} DESTINATION lib)
+endif()
diff --git a/console/base64.cpp b/console/base64.cpp
index e703327..a645b77 100644
--- a/console/base64.cpp
+++ b/console/base64.cpp
@@ -111,7 +111,8 @@ unsigned char * base64_decode(const unsigned char *src, size_t len,
 	memset(dtable, 0x80, 256); //os_
 	for (i = 0; i < sizeof(base64_table) - 1; i++)
 		dtable[base64_table[i]] = (unsigned char) i;
-	dtable['='] = 0;
+	//next line rewritten to avoid warning -Wchar-subscripts
+	dtable[61] = 0; //dtable['='] = 0;
 
 	count = 0;
 	for (i = 0; i < len; i++) {
diff --git a/console/cJSON.cpp b/console/cJSON.cpp
index bc95cde..d694214 100644
--- a/console/cJSON.cpp
+++ b/console/cJSON.cpp
@@ -95,7 +95,7 @@ CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) {
 CJSON_PUBLIC(const char*) cJSON_Version(void)
 {
     static char version[15];
-    sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);
+    snprintf(version, sizeof(version), "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);
 
     return version;
 }
@@ -505,22 +505,22 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out
     /* This checks for NaN and Infinity */
     if ((d * 0) != 0)
     {
-        length = sprintf((char*)number_buffer, "null");
+        length = snprintf((char*)number_buffer, sizeof(number_buffer), "null");
     }
     else
     {
         /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
-        length = sprintf((char*)number_buffer, "%1.15g", d);
+        length = snprintf((char*)number_buffer, sizeof(number_buffer), "%1.15g", d);
 
         /* Check whether the original double can be recovered */
         if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d))
         {
             /* If not, print with 17 decimal places of precision */
-            length = sprintf((char*)number_buffer, "%1.17g", d);
+            length = snprintf((char*)number_buffer, sizeof(number_buffer), "%1.17g", d);
         }
     }
 
-    /* sprintf failed or buffer overrun occurred */
+    /* snprintf failed or buffer overrun occurred */
     if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))
     {
         return false;
@@ -949,7 +949,7 @@ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffe
                     break;
                 default:
                     /* escape and print as unicode codepoint */
-                    sprintf((char*)output_pointer, "u%04x", *input_pointer);
+                    snprintf((char*)output_pointer, output_buffer->length - (output_pointer - output_buffer->buffer), "u%04x", *input_pointer);
                     output_pointer += 4;
                     break;
             }
diff --git a/console/cJSON.h b/console/cJSON.h
index 2c53562..204b40e 100644
--- a/console/cJSON.h
+++ b/console/cJSON.h
@@ -258,7 +258,7 @@ CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * cons
 
 /* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
  * The input pointer json cannot point to a read-only address area, such as a string constant, 
- * but should point to a readable and writable adress area. */
+ * but should point to a readable and writable address area. */
 CJSON_PUBLIC(void) cJSON_Minify(char *json);
 
 /* Helper functions for creating and adding items to an object at the same time.
diff --git a/console/charls/README.md b/console/charls/README.md
index 82f45ad..4ff0549 100644
--- a/console/charls/README.md
+++ b/console/charls/README.md
@@ -1,6 +1,3 @@
-[![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.
@@ -75,7 +72,7 @@ The code is regularly compiled/tested on Windows and 64 bit Linux. Additionally,
 
 ## 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.
+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 transforms 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
 
diff --git a/console/charls/scan.h b/console/charls/scan.h
index 8c3383e..bcc261d 100644
--- a/console/charls/scan.h
+++ b/console/charls/scan.h
@@ -106,7 +106,7 @@ public:
     using PIXEL = typename Traits::PIXEL;
     using SAMPLE = typename Traits::SAMPLE;
 
-    WARNING_SUPPRESS(26495) // false warning that _contextRunmode is unintialized
+    WARNING_SUPPRESS(26495) // false warning that _contextRunmode is uninitialized
     JlsCodec(const Traits& inTraits, const JlsParameters& params) :
         Strategy(params),
         traits(inTraits),
diff --git a/console/charls/util.h b/console/charls/util.h
index 17914d2..573a07b 100644
--- a/console/charls/util.h
+++ b/console/charls/util.h
@@ -27,7 +27,7 @@ std::unique_ptr<T> make_unique(Args&&... 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.
+// Use the built-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
@@ -136,7 +136,7 @@ struct Quad : Triplet<sample>
         v4(0)
         {}
 
-    WARNING_SUPPRESS(26495) // false warning that v4 is unintialized
+    WARNING_SUPPRESS(26495) // false warning that v4 is uninitialized
     Quad(Triplet<sample> triplet, int32_t alpha) noexcept
             :
         Triplet<sample>(triplet),
diff --git a/console/dcm2niix_fswrapper.cpp b/console/dcm2niix_fswrapper.cpp
index 28db86d..e8eedfc 100644
--- a/console/dcm2niix_fswrapper.cpp
+++ b/console/dcm2niix_fswrapper.cpp
@@ -52,7 +52,7 @@ numSeries = 0
  */
 
 // set TDCMopts defaults, overwrite settings to output in mgz orientation
-void dcm2niix_fswrapper::setOpts(const char* dcmindir, const char* niioutdir)
+void dcm2niix_fswrapper::setOpts(const char* dcmindir, const char* niioutdir, bool createBIDS)
 {
   memset(&tdcmOpts, 0, sizeof(tdcmOpts));
   setDefaultOpts(&tdcmOpts, NULL);
@@ -62,13 +62,15 @@ void dcm2niix_fswrapper::setOpts(const char* dcmindir, const char* niioutdir)
   if (niioutdir != NULL)
     strcpy(tdcmOpts.outdir, niioutdir);
 
+  strcpy(tdcmOpts.filename, "%4s.%p");
+
   // set the options for freesurfer mgz orientation
   tdcmOpts.isRotate3DAcq = false;
   tdcmOpts.isFlipY = false;
   tdcmOpts.isIgnoreSeriesInstanceUID = true;
-  tdcmOpts.isCreateBIDS = false;
+  tdcmOpts.isCreateBIDS = createBIDS;
   tdcmOpts.isGz = false;
-  //tdcmOpts.isForceStackSameSeries = 1; // merge 2D slice '-m y'
+  tdcmOpts.isForceStackSameSeries = 1; // merge 2D slice '-m y'
   tdcmOpts.isForceStackDCE = false;
   //tdcmOpts.isForceOnsetTimes = false;
 }
@@ -119,3 +121,11 @@ const unsigned char* dcm2niix_fswrapper::getMRIimg(void)
   return mrifsStruct->imgM;
 }
 
+void dcm2niix_fswrapper::dicomDump(const char* dicomdir)
+{
+  strcpy(tdcmOpts.indir, dicomdir);
+  tdcmOpts.isDumpNotConvert = true;
+  nii_loadDirCore(tdcmOpts.indir, &tdcmOpts);
+
+  return;
+}
diff --git a/console/dcm2niix_fswrapper.h b/console/dcm2niix_fswrapper.h
index f9e4570..8cb0189 100644
--- a/console/dcm2niix_fswrapper.h
+++ b/console/dcm2niix_fswrapper.h
@@ -17,7 +17,7 @@ class dcm2niix_fswrapper
 {
 public:
   // set TDCMopts defaults, overwrite settings to output in mgz orientation.
-  static void setOpts(const char* dcmindir, const char* niioutdir);
+  static void setOpts(const char* dcmindir, const char* niioutdir=NULL, bool createBIDS=false);
 
   // interface to isDICOMfile() in nii_dicom.cpp
   static bool isDICOM(const char* file);
@@ -35,6 +35,8 @@ public:
   // return image data saved in MRIFSSTRUCT
   static const unsigned char* getMRIimg(void);
 
+  static void dicomDump(const char* dicomdir);
+
 private:
   static struct TDCMopts tdcmOpts;
 };
diff --git a/console/main_console.cpp b/console/main_console.cpp
index 143995c..539fd27 100644
--- a/console/main_console.cpp
+++ b/console/main_console.cpp
@@ -78,7 +78,7 @@ void showHelp(const char *argv[], struct TDCMopts opts) {
 	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("  -c : comment stored in NIfTI aux_file (up to 24 characters e.g. '-c VIP', empty to anonymize e.g. 0020,4000 e.g. '-c \"\"')\n");
 	printf("  -d : directory search depth. Convert DICOMs in sub-folders of in_folder? (0..9, default %d)\n", opts.dirSearchDepth);
 #ifdef myEnableJNIfTI
 	printf("  -e : export as NRRD (y) or MGH (o) or JSON/JNIfTI (j) or BJNIfTI (b) instead of NIfTI (y/n/o/j/b, default n)\n");
@@ -104,6 +104,7 @@ void showHelp(const char *argv[], struct TDCMopts opts) {
 	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("  -q : only search directory for DICOMs (y/l/n, default y) [y=show number of DICOMs found, l=additionally list DICOMs found, n=no]\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
@@ -178,7 +179,7 @@ void showHelp(const char *argv[], struct TDCMopts opts) {
 } //showHelp()
 
 int invalidParam(int i, const char *argv[]) {
-	if (strchr("yYnNoOhHiIjJBb01234",argv[i][0]))
+	if (strchr("yYnNoOhHiIjlLJBb01234",argv[i][0]))
 		return 0;
 
 	//if (argv[i][0] != '-') return 0;
@@ -351,6 +352,8 @@ int main(int argc, const char *argv[]) {
 			} else if ((argv[i][1] == 'c') && ((i + 1) < argc)) {
 				i++;
 				snprintf(opts.imageComments, 24, "%s", argv[i]);
+				if (strlen(opts.imageComments) == 0) //empty string is flag to anonymize DICOM image comments
+					snprintf(opts.imageComments, 24, "%s", "\t");
 			} else if ((argv[i][1] == 'd') && ((i + 1) < argc)) {
 				i++;
 				if ((argv[i][0] >= '0') && (argv[i][0] <= '9'))
@@ -410,6 +413,25 @@ int main(int argc, const char *argv[]) {
 					opts.isTestx0021x105E = true;
 					printf("undocumented '-j y' compares GE slice timing from 0021,105E\n");
 				}
+			} else if ((!strcmp(argv[i], "--diffCyclingModeGE")) && ((i + 1) < argc)) {
+				// see issue 635
+				i++;
+				if (argv[i][0] == '0') {
+					opts.diffCyclingModeGE = 0;
+					printf("undocumented '--diffCyclingModeGE 0' cycling OFF\n");
+				}
+				else if (argv[i][0] == '1') {
+					opts.diffCyclingModeGE = 1;
+					printf("undocumented '--diffCyclingModeGE 1' cycling All-TR\n");
+				}
+				else if (argv[i][0] == '2') {
+					opts.diffCyclingModeGE = 2;
+					printf("undocumented '--diffCyclingModeGE 2' cycling 2-TR\n");
+				}
+				else if (argv[i][0] == '3') {
+					opts.diffCyclingModeGE = 3;
+					printf("undocumented '--diffCyclingModeGE 3' cycling 3-TR\n");
+				}				
 			} else if ((argv[i][1] == 'l') && ((i + 1) < argc)) {
 				i++;
 				if (invalidParam(i, argv))
@@ -452,6 +474,14 @@ int main(int argc, const char *argv[]) {
 					return 0;
 				if ((argv[i][0] == 'y') || (argv[i][0] == 'Y'))
 					opts.isRenameNotConvert = true;
+			} else if ((argv[i][1] == 'q') && ((i + 1) < argc)) {
+				i++;
+				if (invalidParam(i, argv))
+					return 0;
+				if ((argv[i][0] == 'y') || (argv[i][0] == 'Y'))
+					opts.onlySearchDirForDICOM = 1;
+				else if ((argv[i][0] == 'l') || (argv[i][0] == 'L'))
+					opts.onlySearchDirForDICOM = 2;
 			} else if ((argv[i][1] == 's') && ((i + 1) < argc)) {
 				i++;
 				if (invalidParam(i, argv))
@@ -605,11 +635,14 @@ int main(int argc, const char *argv[]) {
 		if (ret != EXIT_SUCCESS)
 			return ret;
 	}
+
+	if (opts.onlySearchDirForDICOM == 0) {
 #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);
+		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);
+		printf("Conversion required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC);
 #endif
+	}
 	//if (isSaveIni) //we now save defaults earlier, in case of early termination.
 	//	saveIniFile(opts);
 	return EXIT_SUCCESS;
diff --git a/console/makefile b/console/makefile
index 3b14bfa..1f35e23 100644
--- a/console/makefile
+++ b/console/makefile
@@ -4,6 +4,12 @@ CFLAGS=-s -O3
 # Debugging
 #CFLAGS=-g
 
+# issue659: increase stack to 16mb
+#For Linux linker stacksize ignored: we must use setrlimit 
+# LFLAGS=-Wl,-z -Wl,stack-size=16777216
+LFLAGS=
+
+
 #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 base64.c cJSON.c -o dcm2niix -DmyDisableOpenJPEG
@@ -34,7 +40,8 @@ ifneq ($(OS),Windows_NT)
 		# 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
+		LFLAGS=-Wl,-stack_size -Wl,0x1000000
 	endif
 endif
 all:
-	g++ $(CFLAGS) -I. $(JSFLAGS) $(JFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG
+	g++ $(CFLAGS) -I. $(JSFLAGS) $(JFLAGS) $(LFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG
diff --git a/console/miniz.c b/console/miniz.c
index 47d1010..133a3d3 100644
--- a/console/miniz.c
+++ b/console/miniz.c
@@ -705,7 +705,7 @@ enum
 //  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.
+//  *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on incompressible 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);
 
@@ -817,7 +817,7 @@ enum
 //  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.
+//  *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on incompressible 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);
 
diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp
index 9c03232..a14ea39 100644
--- a/console/nii_dicom.cpp
+++ b/console/nii_dicom.cpp
@@ -1,5 +1,6 @@
 //#define MY_DEBUG
 #if defined(_WIN64) || defined(_WIN32)
+#define NOMINMAX
 #include <windows.h> //write to registry
 #endif
 #ifdef _MSC_VER
@@ -36,6 +37,7 @@
 #include <string.h>
 #include <sys/stat.h> // discriminate files from folders
 #include <sys/types.h>
+#include <algorithm>
 
 #ifdef USING_R
 #undef isnan
@@ -393,6 +395,39 @@ mat44 noNaN(mat44 Q44, bool isVerbose, bool *isBogus) //simplify any headers tha
 	return ret;
 }
 
+#define kYYYYMMDDlen 8 //how many characters to encode year,month,day in "YYYYDDMM" format
+
+/*double dicomTimeToSecX(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 DateTimeToTime(char *dstr) {
+	if (strlen(dstr) > (kYYYYMMDDlen + 5)) {
+		// 20161117131643.80000 -> date 20161117 time 131643.80000
+		//printMessage("acquisitionDateTime %s\n",acquisitionDateTimeTxt);
+		//char acquisitionDateTxt[kDICOMStr];
+		//memcpy(acquisitionDateTxt, dstr, kYYYYMMDDlen);
+		//acquisitionDateTxt[kYYYYMMDDlen] = '\0'; // IMPORTANT!
+		//d.acquisitionDate = atof(acquisitionDateTxt);
+		char acquisitionTimeTxt[kDICOMStr];
+		int timeLen = (int)strlen(dstr) - kYYYYMMDDlen;
+		strncpy(acquisitionTimeTxt, &dstr[kYYYYMMDDlen], timeLen);
+		acquisitionTimeTxt[timeLen] = '\0'; // IMPORTANT!
+		printf(">>>%s\n", acquisitionTimeTxt);
+		return atof(acquisitionTimeTxt);
+	}
+	return 0.0;
+}*/
+
 #define kSessionOK 0
 #define kSessionBadMatrix 1
 
@@ -627,9 +662,9 @@ int headerDcm2NiiSForm(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1
 		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;
+		d.orient[4] = 0.0f;
+		d.orient[5] = 1.0f;
+		d.orient[6] = 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 {
@@ -648,12 +683,12 @@ int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_hea
 	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);
+		snprintf(txt, 1024, "TE=%.2g;Time=%.3f", d.TE, d.acquisitionTime);
 	else
-		sprintf(txt, "Time=%.3f", d.acquisitionTime);
+		snprintf(txt, 1024, "Time=%.3f", d.acquisitionTime);
 	if (d.CSA.phaseEncodingDirectionPositive >= 0) {
 		char dtxt[1024] = {""};
-		sprintf(dtxt, ";phase=%d", d.CSA.phaseEncodingDirectionPositive);
+		snprintf(dtxt, 1024, ";phase=%d", d.CSA.phaseEncodingDirectionPositive);
 		strcat(txt, dtxt);
 	}
 	//from dicm2nii 20151117 InPlanePhaseEncodingDirection
@@ -663,15 +698,17 @@ int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_hea
 		h->dim_info = (3 << 4) + (2 << 2) + 1;
 	if (d.CSA.multiBandFactor > 1) {
 		char dtxt[1024] = {""};
-		sprintf(dtxt, ";mb=%d", d.CSA.multiBandFactor);
+		snprintf(dtxt, 1024, ";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)
+	if ((strlen(d.imageComments) > 0) && (h->aux_file[0] == 0)) //issue691
 		snprintf(h->aux_file, 24, "%.23s", d.imageComments);
+	if ((h->aux_file[0] == '\t') && (h->aux_file[1] == 0))
+		h->aux_file[0] = 0; //issue691
 	return headerDcm2NiiSForm(d, d2, h, isVerbose);
 } //headerDcm2Nii2()
 
@@ -719,6 +756,7 @@ struct TDICOMdata clear_dicom_data() {
 	strcpy(d.studyDate, "");
 	strcpy(d.studyTime, "");
 	strcpy(d.protocolName, "");
+	strcpy(d.patientOrient, "");
 	strcpy(d.seriesDescription, "");
 	strcpy(d.sequenceName, "");
 	strcpy(d.scanningSequence, "");
@@ -799,11 +837,16 @@ struct TDICOMdata clear_dicom_data() {
 	d.protocolBlockStartGE = 0;
 	d.protocolBlockLengthGE = 0;
 	d.phaseEncodingSteps = 0;
+	d.frequencyEncodingSteps = 0;
+	d.phaseEncodingStepsOutOfPlane = 0;
 	d.coilCrc = 0;
 	d.seriesUidCrc = 0;
 	d.instanceUidCrc = 0;
 	d.accelFactPE = 0.0;
 	d.accelFactOOP = 0.0;
+	d.compressedSensingFactor = 0.0;
+	d.isDeepLearning = false;
+	strcpy(d.deepLearningText, "");
 	//d.patientPositionNumPhilips = 0;
 	d.imageBytes = 0;
 	d.intenScale = 1;
@@ -868,6 +911,8 @@ struct TDICOMdata clear_dicom_data() {
 	d.maxEchoNumGE = -1;
 	d.epiVersionGE = -1;
 	d.internalepiVersionGE = -1;
+	d.diffCyclingModeGE = kGE_DIFF_CYCLING_UNKNOWN;
+	d.tensorFileGE = 0;
 	d.durationLabelPulseGE = -1;
 	d.aslFlags = kASL_FLAG_NONE;
 	d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN;
@@ -883,6 +928,8 @@ struct TDICOMdata clear_dicom_data() {
 	d.isHasOverlay = false;
 	d.isPrivateCreatorRemap = false;
 	d.isRealIsPhaseMapHz = false;
+	d.isVariableFlipAngle = false;
+	d.isQuadruped = false;
 	d.numberOfImagesInGridUIH = 0;
 	d.phaseEncodingRC = '?';
 	d.patientSex = '?';
@@ -903,6 +950,7 @@ struct TDICOMdata clear_dicom_data() {
 	d.CSA.multiBandFactor = 1;
 	d.CSA.SeriesHeader_offset = 0;
 	d.CSA.SeriesHeader_length = 0;
+	d.CSA.coilNumber = -1;
 	return d;
 } //clear_dicom_data()
 
@@ -934,7 +982,7 @@ void dcmStrDigitsOnlyKey(char key, char *lStr) {
 		return;
 	bool isKey = false;
 	for (int i = 0; i < (int)len; i++) {
-		if (!isdigit(lStr[i])) {
+		if (!isdigitdot(lStr[i])) {
 			isKey = (lStr[i] == key);
 			lStr[i] = ' ';
 		} else if (!isKey)
@@ -1208,6 +1256,10 @@ int dcmStrManufacturer(const int lByteLength, unsigned char lBuffer[]) { //read
 		ret = kMANUFACTURER_UIH;
 	if ((toupper(cString[0]) == 'B') && (toupper(cString[1]) == 'R'))
 		ret = kMANUFACTURER_BRUKER;
+	if ((toupper(cString[0]) == 'M') && (toupper(cString[1]) == 'R'))
+		ret = kMANUFACTURER_MRSOLUTIONS;
+	if ((toupper(cString[0]) == 'H') && (toupper(cString[1]) == 'Y'))
+		ret = kMANUFACTURER_HYPERFINE;
 	//if (ret == kMANUFACTURER_UNKNOWN) //reduce verbosity: single warning for series : Unable to determine manufacturer (0008,0070)
 	//	printWarning("Unknown manufacturer %s\n", cString);
 	//#ifdef _MSC_VER
@@ -1244,6 +1296,30 @@ float csaMultiFloat(unsigned char buff[], int nItems, float Floats[], int *Items
 	return Floats[1];
 } //csaMultiFloat()
 
+int csaICEdims(unsigned char buff[]) {
+	//determine coil number from CSA header
+	//Combined images start with a letter: X_1_1_1_1_1_1_1_1_1_1_1_106
+	//Coil data starts with coil number: 29_1_1_1_1_1_7_1_1_1_1_1_3000012
+	TCSAitem itemCSA;
+	int lPos = 0;
+	memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA));
+	int coilNumber = -1;
+	if (itemCSA.xx2_Len > 0) {
+		lPos += sizeof(itemCSA);
+		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;
+		char c = cString[0];
+		if( c >= '0' && c <= '9' ){
+			dcmStrDigitsOnly(cString);
+			char *end;
+			coilNumber = (int)strtol(cString, &end, 10);
+		}
+		free(cString);
+	}
+	return coilNumber;
+} //csaICEdims()
+
 bool csaIsPhaseMap(unsigned char buff[], int nItems) {
 	//returns true if the tag "ImageHistory" has an item named "CC:ComplexAdd"
 	TCSAitem itemCSA;
@@ -1373,11 +1449,17 @@ int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, i
 		// Storage order is always little-endian, so byte-swap required values if necessary
 		if (!littleEndianPlatform())
 			nifti_swap_4bytes(1, &tagCSA.nitems);
+		if (tagCSA.nitems > 128) {
+			printError("%d n_tags CSA Image Header corrupted (0029,1010) see issue 633.\n", tagCSA.nitems);
+			return EXIT_FAILURE;
+		}
 		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, "ICE_Dims") == 0)
+				CSA->coilNumber = csaICEdims(&buff[lPos]);
 			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) {
@@ -1484,6 +1566,15 @@ void dcmMultiFloat(int lByteLength, char lBuffer[], int lnFloats, float *lFloats
 	free(cString);
 } //dcmMultiFloat()
 
+double dcmStrDouble(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
+	double ret = (double)atof(cString);
+	free(cString);
+	return ret;
+} //dcmStrDouble()
+
 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);
@@ -1802,6 +1893,7 @@ struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dt
 	int numSlice2D = 0;
 	int prevDyn = -1;
 	bool dynNotAscending = false;
+	int maxSlice2D = 0;
 	int parVers = 0;
 	int maxSeq = -1; //maximum value of Seq column
 	int seq1 = -1; //value of Seq volume for first slice
@@ -2085,10 +2177,6 @@ struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dt
 			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;
@@ -2193,13 +2281,25 @@ struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dt
 			//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) {
+			int imageType = (int) cols[kImageType];
+			bool isMagnitude = (imageType == 0); 
+			bool isReal = (imageType == 1);
+			bool isImaginary = (imageType == 2);
+			bool isPhase = (imageType == 3);
+			bool isRealIsPhaseMapHz = (imageType == 18);
+			if (isMagnitude)
+				d.isHasMagnitude = true;
+			if (isImaginary)
+				d.isHasImaginary = true;
+			if (isPhase)
+				d.isHasPhase = true;
+			if (isRealIsPhaseMapHz) {
 				isReal = true;
+				d.isHasReal = true;
 				d.isRealIsPhaseMapHz = true;
 			}
+			if (isReal)
+				d.isHasReal = true;
 			if (cols[kImageType] == 4) {
 				if (!isType4Warning) {
 					printWarning("Unknown image type (4). Be aware the 'phase' image is of an unknown type.\n");
@@ -2262,7 +2362,7 @@ struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dt
 			//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);
+			maxSlice2D = std::max(slice, maxSlice2D);
 			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);
@@ -2318,11 +2418,22 @@ struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dt
 			slice = slice + 1;
 		}
 	}
+	if (maxSlice2D >= kMaxSlice2D) {//issue659
+		printError("Use dicm2nii or increase kMaxSlice2D (issue 659) %d\n", maxSlice2D);
+		d.isValid = false;
+	}
+	//number of image types: issue659
+	int nType = 0;
+	if (d.isHasMagnitude) nType ++;
+	if (d.isHasImaginary) nType ++;
+	if (d.isHasPhase) nType ++;
+	if (d.isHasReal) nType ++;
+	nType = std::max(nType, 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",
+		printMessage("  slices*grad*bval*cardiac*echo*dynamic*mix*labels*types = %d*%d*%d*%d*%d*%d*%d*%d*%d\n",
 				d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
-				maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels);
+				maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels, nType);
 		d.isValid = false;
 	}
 	for (int i = 0; i < numSlice2D; i++) { //issue363
@@ -3909,7 +4020,15 @@ void _update_tvd(struct TVolumeDiffusion *ptvd) {
 			}
 		}
 	}
-	if (!isReady) { //bvecs NOT filled: see if symBMatrix filled
+	if ((!isReady) && (ptvd->_dtiV[0] < 100.0) && (!isnan(ptvd->_symBMatrix[0]))) {
+		//issue265: Bruker omits 0018,9089 for low b-values though it includes a bmatrix
+		ptvd->_dtiV[1] = 0.0;
+		ptvd->_dtiV[2] = 0.0;
+		ptvd->_dtiV[3] = 0.0;
+		isReady = true;
+		
+	}
+	/*if (!isReady) { //bvecs NOT filled: see if symBMatrix filled
 		isReady = true;
 		for (int i = 1; i < 6; ++i)
 			if (isnan(ptvd->_symBMatrix[i]))
@@ -3958,11 +4077,7 @@ void _update_tvd(struct TVolumeDiffusion *ptvd) {
 		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.
@@ -4095,6 +4210,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D
 	dti4D->volumeOnsetTime[0] = -1;
 	dti4D->decayFactor[0] = -1;
 	dti4D->frameDuration[0] = -1;
+	dti4D->frameReferenceTime[0] = -1;
 	//dti4D->fragmentOffset[0] = -1;
 	dti4D->intenScale[0] = 0.0;
 	struct TVolumeDiffusion volDiffusion = initTVolumeDiffusion(&d, dti4D);
@@ -4226,7 +4342,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D
 #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 kPhaseEncodingSteps 0x0018 + (0x0089 << 16) //IS
 #define kEchoTrainLength 0x0018 + (0x0091 << 16) //IS
 #define kPercentSampling 0x0018 + (0x0093 << 16) //'DS'
 #define kPhaseFieldofView 0x0018 + (0x0094 << 16) //'DS'
@@ -4247,8 +4363,9 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D
 #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 kFlipAngle 0x0018 + (0x1314 << 16)
+#define kVariableFlipAngleFlag 0x0018 + (0x1315 << 16) //CS
 #define kSAR 0x0018 + (0x1316 << 16) //'DS' 'SAR'
 #define kPatientOrient 0x0018 + (0x5100 << 16) //0018,5100. patient orientation - 'HFS'
 #define kPulseSequenceName 0x0018 + uint32_t(0x9005 << 16) //'SH' 'YES'/'NO'
@@ -4259,30 +4376,34 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D
 #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 kMRAcquisitionFrequencyEncodingSteps 0x0018 + uint32_t(0x9058 << 16) //US
 #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 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);
+const uint32_t kEffectiveTE = 0x0018 + uint32_t(0x9082 << 16); //FD 
 //#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 kImagingFrequencyFD 0x0018 + uint32_t(0x9098 << 16) //FD
+#define kMREchoSequence 0x0018 + uint32_t(0x9114 << 16) //SQ
 #define kParallelReductionFactorOutOfPlane 0x0018 + uint32_t(0x9155 << 16) //FD
+#define kSARFD 0x0018 + uint32_t(0x9181 << 16) //FD
+#define kMRAcquisitionPhaseEncodingStepsInPlane 0x0018 + uint32_t(0x9231 << 16) //US
+#define kMRAcquisitionPhaseEncodingStepsOutOfPlane 0x0018 + uint32_t(0x9232 << 16) //US
+#define kGradientEchoTrainLength 0x0018 + uint32_t(0x9241 << 16) //US
 //#define kFrameAcquisitionDuration 0x0018+uint32_t(0x9220 << 16 ) //FD
 #define kArterialSpinLabelingContrast 0x0018 + uint32_t(0x9250 << 16) //CS
 #define kASLPulseTrainDuration 0x0018 + uint32_t(0x9258 << 16) //UL
 #define kDiffusionBValueXX 0x0018 + uint32_t(0x9602 << 16) //FD
-#define kDiffusionBValueXY 0x0018 + uint32_t(0x9603 << 16) //FD
-#define kDiffusionBValueXZ 0x0018 + uint32_t(0x9604 << 16) //FD
-#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 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 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
@@ -4297,11 +4418,14 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 #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 kUserData11GE 0x0019 + (0x10B2 << 16) //DS Diffusion tensor filename
+#define kUserData12GE 0x0019 + (0x10B3 << 16) //DS phase diffusion direction; diffusion gradient cycling mode
+#define kUserData15GE 0x0019 + (0x10B6 << 16) //DS Diffusion Gradient Derating; cycling special OFF
 #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 kNumberOfDiffusionT2GE 0x0019 + (0x10DF << 16) ///DS NumberOfDiffusionT2:UserData23 (release 10+)
+#define kNumberOfDiffusionDirectionGE 0x0019 + (0x10E0 << 16) ///DS NumberOfDiffusionDirection:UserData24 (release 10+)
 #define kVelocityEncodeScaleGE 0x0019 + (0x10E2 << 16) ///DS Velocity Encode Scale
 #define kStudyID 0x0020 + (0x0010 << 16)
 #define kSeriesNum 0x0020 + (0x0011 << 16)
@@ -4325,7 +4449,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 #define kTemporalPositionIndex 0x0020 + uint32_t(0x9128 << 16) // UL
 #define kDimensionIndexPointer 0x0020 + uint32_t(0x9165 << 16)
 //Private Group 21 as Used by Siemens:
-#define kScanningSequenceSiemens 0x0021 + (0x105A << 16) //CS
+#define kScanningSequenceSiemens 0x0021 + (0x105A << 16) //CS n.b. for GE this is Diffusion direction of SL!
 #define kSequenceVariant21 0x0021 + (0x105B << 16) //CS Siemens ONLY: For GE this is TaggingFlipAngle
 #define kScanOptionsSiemens 0x0021 + (0x105C << 16) //CS Siemens ONLY
 #define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText
@@ -4334,10 +4458,12 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 #define kICE_dims 0x0021 + (0x1106 << 16) //LO [X_4_1_1_1_1_160_1_1_1_1_1_277]
 #define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 16) //IS
 #define kRealDwellTime 0x0021+(0x1142<< 16 )//IS
+//#define kPATModeText2 0x0021 + (0x1156 << 16) //LO, always same as 0021,1009 
 #define kBandwidthPerPixelPhaseEncode21 0x0021 + (0x1153 << 16) //FD
 #define kCoilElements 0x0021 + (0x114F << 16) //LO
 #define kAcquisitionMatrixText21 0x0021 + (0x1158 << 16) //SH
 #define kImageTypeText 0x0021 + (0x1175 << 16) //CS
+#define kDeepLearningText 0x0021 + (0x1176 << 16) //LO
 //Private Group 21 as used by GE:
 #define kLocationsInAcquisitionGE 0x0021 + (0x104F << 16) //SS 'LocationsInAcquisitionGE'
 #define kRTIA_timer 0x0021 + (0x105E << 16) //DS
@@ -4373,6 +4499,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 #define kShimGradientX 0x0043 + (0x1002 << 16) //SS
 #define kShimGradientY 0x0043 + (0x1003 << 16) //SS
 #define kShimGradientZ 0x0043 + (0x1004 << 16) //SS
+#define kVasCollapseFlagGE 0x0043 + (0x1030 << 16) //SS issue690
 #define kPrescanReuseString 0x0043 + (0x1095 << 16) //LO
 #define kUserDefineDataGE 0x0043 + (0x102A << 16) //OB
 #define kEffectiveEchoSpacingGE 0x0043 + (0x102C << 16) //SS
@@ -4384,6 +4511,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 #define kASLLabelingTechniqueGE 0x0043 + (0x10A4 << 16) //LO
 #define kDurationLabelPulseGE 0x0043 + (0x10A5 << 16) //IS
 #define kMultiBandGE 0x0043 + (0x10B6 << 16) //LO
+#define kCompressedSensingParameters 0x0043 + (0x10B7 << 16) //LO
+#define kDeepLearningParameters 0x0043 + (0x10CA << 16) //LO "0.75\High"
 #define kAcquisitionMatrixText 0x0051 + (0x100B << 16) //LO
 #define kImageOrientationText 0x0051 + (0x100E << 16) //
 #define kCoilSiemens 0x0051 + (0x100F << 16)
@@ -4428,6 +4557,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 //#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 kTRPhilips 0x2005 + (0x1030 << 16) //(2005,1030) FL 30\150
 #define kMRfMRIStatusIndicationPhilips 0x2005 + (0x1063 << 16)
 #define kMRAcquisitionTypePhilips 0x2005 + (0x106F << 16) //SS
 #define kAngulationAP 0x2005 + (0x1071 << 16) //'2005' '1071' 'FL' 'MRStackAngulationAP'
@@ -4437,10 +4567,11 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 #define kMRStackOffcentreFH 0x2005 + (0x1079 << 16)
 #define kMRStackOffcentreRL 0x2005 + (0x107A << 16)
 #define kPhilipsSlope 0x2005 + (0x100E << 16)
-#define kMRImageDynamicScanBeginTime 0x2005 + (0x10a0 << 16) //FL
+#define kMRImageDynamicScanBeginTime 0x2005 + (0x10A0 << 16) //FL
 #define kDiffusionDirectionRL 0x2005 + (0x10B0 << 16)
 #define kDiffusionDirectionAP 0x2005 + (0x10B1 << 16)
 #define kDiffusionDirectionFH 0x2005 + (0x10B2 << 16)
+#define kDeepLearningPhilips 0x2005 + (0x1110 << 16)
 #define kPrivatePerFrameSq 0x2005 + (0x140F << 16)
 #define kMRImageDiffBValueNumber 0x2005 + (0x1412 << 16) //IS
 #define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS
@@ -4463,20 +4594,26 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 // 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
+	#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 maxSAR = -INFINITY;
 	double TE = 0.0; //most recent echo time recorded
 	float temporalResolutionMS = 0.0;
 	float MRImageDynamicScanBeginTime = 0.0;
+	bool isHasBMatrix = false;
+	bool isHasBVec = false;
 	bool is2005140FSQ = false;
 	bool is4000561SQ = false; //Original Attributes SQ
 	bool is00089092SQ = false; //Referenced Image Evidence SQ
 	bool overlayOK = true;
+	int userData11GE = 0;
 	int userData12GE = 0;
+	float userData15GE = 0;
+	float accelFactPE = 0.0;
 	int overlayRows = 0;
 	int overlayCols = 0;
 	bool isNeologica = false;
@@ -4491,6 +4628,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 	int philMRImageDiffBValueNumber = 0;
 	int philMRImageDiffVolumeNumber = -1;
 	int sqDepth = 0;
+	int seriesInstanceUIDsqDepth = 65535; //issue655
 	int acquisitionTimesGE_UIH = 0;
 	int sqDepth00189114 = -1;
 	bool hasDwiDirectionality = false;
@@ -4505,6 +4643,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 	size_t dimensionIndexPointerCounter = 0;
 	int maxInStackPositionNumber = 0;
 	int temporalPositionIndex = 0;
+	int minTemporalPositionIndex = 65535;
 	int maxTemporalPositionIndex = 0;
 	//int temporalPositionIdentifier = 0;
 	int locationsInAcquisitionPhilips = 0;
@@ -4549,6 +4688,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 	char scanningSequenceSiemens[kDICOMStr] = "";
 	char imageType1st[kDICOMStr] = "";
 	bool isEncapsulatedData = false;
+	int diffusionDirectionTypeGE = 0; //issue690
 	int multiBandFactor = 0;
 	int frequencyRows = 0;
 	int numberOfImagesInMosaic = 0;
@@ -4572,6 +4712,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 	float vRLPhilips = 0.0;
 	float vAPPhilips = 0.0;
 	float vFHPhilips = 0.0;
+	//float TRPhilips = -1.0;
 	double acquisitionTimePhilips = -1.0;
 	bool isPhase = false;
 	bool isReal = false;
@@ -5033,6 +5174,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
 			// d.isValid = false;
 			//return d;
 		}
+		if (lLength > 0) //issue695: skip empty tags, "gdcmanon --dumb --empty 0018,0089 good.dcm bad.dcm"
 		switch (groupElement) {
 		case kMediaStorageSOPClassUID: {
 			char mediaUID[kDICOMStr];
@@ -5252,7 +5394,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			char uid[kDICOMStrLarge];
 			dcmStr(lLength, &buffer[lPos], uid, true);
 			char *timeStr = strrchr(uid, '.');
-			//nb Manufactuer (0008,0070) comes AFTER (0008,0018) SOPInstanceUID.
+			//nb Manufacturer (0008,0070) comes AFTER (0008,0018) SOPInstanceUID.
 			//format of (0008,0018) UI 
 			//[1.23.4.2019051416101221842
 			//       .YYYYMMDDHHmmssxxxxx
@@ -5304,6 +5446,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			dcmStr(lLength, &buffer[lPos], d.referringPhysicianName);
 			break;
 		case kReferencedImageEvidenceSQ:
+			if (lLength > 8)
+				break; //issue639: we will skip entire icon if there is an explicit length
 			is00089092SQ = true;
 			break;
 		case kComplexImageComponent:
@@ -5375,7 +5519,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			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");
+			d.isQuadruped = true;
+			//printError("Anatomical Orientation Type (0010,2210) is QUADRUPED: rotate coordinates accordingly\n");
 			break;
 		}
 		case kDeidentificationMethod: { //issue 383
@@ -5502,9 +5647,9 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			if (lLength < 2)
 				break;
 			if (toupper(buffer[lPos]) == 'L')
-				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
+				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; //issue674, (0018, 9034) LINEAR-->FLIPPED
 			if (toupper(buffer[lPos]) == 'R')
-				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
+				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; //issue674, R(0018, 9034) REVERSE_LINEAR-->UNFLIPPED
 			break;
 		}
 		case kPartialFourierDirection: { //'CS' PHASE FREQUENCY SLICE_SELECT COMBINATION
@@ -5525,22 +5670,25 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 				isProspectiveSynced = true;
 			break;*/
 		case kParallelReductionFactorInPlane:
+			accelFactPE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
 			if (d.manufacturer == kMANUFACTURER_SIEMENS)
 				break;
-			d.accelFactPE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
+			d.accelFactPE = accelFactPE;
 			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 kFrameAcquisitionDateTime: {
+			//(0018,9074) DT [20190621095516.140000] YYYYMMDDHHMMSS
+			//see https://github.com/rordenlab/dcm2niix/issues/303
+			char dateTime[kDICOMStr];
+			dcmStr(lLength, &buffer[lPos], dateTime);
+			double dTime = DateTimeToTime(dateTime);
+			printf("%s\t FrameAcquisitionDateTime %0.4f \n", dateTime, dTime);
+			//d.triggerDelayTime = dTime;
+		}*/
 		case kDiffusionDirectionality: { // 0018, 9075
 			set_directionality0018_9075(&volDiffusion, (&buffer[lPos]));
 			if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 10))
@@ -5574,7 +5722,16 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			sqDepth00189114 = sqDepth - 1;
 			break;
 		case kMRAcquisitionPhaseEncodingStepsInPlane:
-			d.phaseEncodingLines = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
+			d.phaseEncodingSteps = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
+			break;
+		case kMRAcquisitionFrequencyEncodingSteps:
+			d.frequencyEncodingSteps = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
+			break;
+		case kMRAcquisitionPhaseEncodingStepsOutOfPlane:
+			d.phaseEncodingStepsOutOfPlane = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
+			break;
+		case kGradientEchoTrainLength:
+			d.echoTrainLength = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
 			break;
 		case kNumberOfImagesInMosaic:
 			if (d.manufacturer == kMANUFACTURER_SIEMENS)
@@ -5628,6 +5785,13 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			d.CSA.dtiV[3] = v[2];
 			break;
 		}
+		case kNumberOfDiffusionT2GE: {
+			if (d.manufacturer != kMANUFACTURER_GE)
+				break;
+			float f = dcmStrFloat(lLength, &buffer[lPos]);
+			d.numberOfDiffusionT2GE = round(f);
+			break;
+		}
 		case kNumberOfDiffusionDirectionGE: {
 			if (d.manufacturer != kMANUFACTURER_GE)
 				break;
@@ -5660,12 +5824,22 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 				break;
 			d.maxEchoNumGE = round(dcmStrFloat(lLength, &buffer[lPos]));
 			break;
+		case kUserData11GE: {
+			if (d.manufacturer != kMANUFACTURER_GE)
+				break;
+			userData11GE = 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 kUserData15GE: {
+			if (d.manufacturer != kMANUFACTURER_GE)
+				break;
+			userData15GE = dcmStrFloat(lLength, &buffer[lPos]);
+			break; }
 		case kDiffusionDirectionGEX:
 			if (d.manufacturer == kMANUFACTURER_GE)
 				set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 0);
@@ -5722,8 +5896,9 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			dcmStr(lLength, &buffer[lPos], d.studyInstanceUID);
 			break;
 		case kSeriesInstanceUID: // 0020,000E
+		    if (sqDepth > seriesInstanceUIDsqDepth) break; //issue655
+			seriesInstanceUIDsqDepth = sqDepth;
 			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: {
@@ -5755,8 +5930,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			}			//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 (isVerbose > 1) //verbose > 1 will report full DICOM tag
+				printMessage("   Patient Position 0020,0032 (#,@,X,Y,Z)\t%d\t%zu\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);
@@ -5779,6 +5954,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			break;
 		case kSAR:
 			d.SAR = dcmStrFloat(lLength, &buffer[lPos]);
+			maxSAR = fmax(maxSAR, d.SAR);
 			break;
 		case kStudyID:
 			dcmStr(lLength, &buffer[lPos], d.studyID);
@@ -5809,6 +5985,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			temporalPositionIndex = dcmInt(4, &buffer[lPos], d.isLittleEndian);
 			if (temporalPositionIndex > maxTemporalPositionIndex)
 				maxTemporalPositionIndex = temporalPositionIndex;
+			if (temporalPositionIndex < minTemporalPositionIndex)
+				minTemporalPositionIndex = temporalPositionIndex;
 			break;
 		case kDimensionIndexPointer:
 			dimensionIndexPointer[dimensionIndexPointerCounter++] = dcmAttributeTag(&buffer[lPos], d.isLittleEndian);
@@ -5854,6 +6032,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			d.isPlanarRGB = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
 			break;
 		case kDim3:
+			if (lLength < 1) //issue 695
+				break;
 			d.xyzDim[3] = dcmStrInt(lLength, &buffer[lPos]);
 			numberOfFrames = d.xyzDim[3];
 			break;
@@ -5885,13 +6065,13 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			char accelStr[kDICOMStr];
 			dcmStr(lLength, &buffer[lPos], accelStr);
 			char *ptr;
-			dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return ""
+			dcmStrDigitsDotOnlyKey('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 ""
+			dcmStrDigitsDotOnlyKey('s', accelStr); //e.g. if "p2s4" return "4", if "p2" return ""
 			multiBandFactor = (int)strtol(accelStr, &ptr, 10);
 			if (*ptr != '\0')
 				multiBandFactor = 0.0;
@@ -5916,7 +6096,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 				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]);
+			//printf("\t%d\t%g\tTimeAfterStart(0021,1104)\n", d.imageNum, d.CSA.sliceTiming[acquisitionTimesGE_UIH]);
 			acquisitionTimesGE_UIH++;
 			break;
 		case kICE_dims: { //issue568: LO (0021,1106) [X_4_1_1_1_1_160_1_1_1_1_1_277]
@@ -5925,12 +6105,25 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			char iceStr[kDICOMStr];
 			dcmStr(lLength, &buffer[lPos], iceStr);
 			dcmStrDigitsOnly(iceStr);
-			char *end;
-			int echo = (int)strtol(iceStr, &end, 10);
+			char *end, *echoStr;
+			//read the first item ('X' or numeric if uncombined)
+			char c = iceStr[0];
+			if( c >= '0' && c <= '9' ){
+				int coilNumber = (int)strtol(iceStr, &end, 10);
+				//if ((iceStr != end) && (coilNumber > 0) && (strlen(d.coilName) < 1)) { //nb with uncombined coil will still have a name, e.g. 'HeadNeck_64'
+				if ((iceStr != end) && (coilNumber > 0)) {
+					snprintf(d.coilName, kDICOMStr, "%d", coilNumber);
+					//printf("issue631 coil name '%s'\n", d.coilName);
+					d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName));
+				}
+			}
+			//read the second item: the echo number ('4')
+			echoStr = strchr(iceStr, ' ');
+			int echo = (int)strtol(echoStr, &end, 10);
 			//printMessage("%d:%d:'%s'\n", d.echoNum, echo, iceStr);
 			if (iceStr != end)
 				d.echoNum = echo;
-			//printMessage("%d:'%s'\n", echo, iceStr);
+			//printMessage("%d:'%s'\n", echo, echoStr);
 			break;
 		}
 		case kPhaseEncodingDirectionPositiveSiemens: {
@@ -6083,7 +6276,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			d.numberOfAverages = dcmStrFloat(lLength, &buffer[lPos]);
 			break;
 		case kImagingFrequency:
-			d.imagingFrequency = dcmStrFloat(lLength, &buffer[lPos]);
+			d.imagingFrequency = dcmStrDouble(lLength, &buffer[lPos]);
 			break;
 		case kTriggerTime: {
 			if (prefs->isIgnoreTriggerTimes)
@@ -6154,6 +6347,9 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 		case kFlipAngle:
 			d.flipAngle = dcmStrFloat(lLength, &buffer[lPos]);
 			break;
+		case kVariableFlipAngleFlag:
+			d.isVariableFlipAngle = ('Y' == toupper(buffer[lPos])); //first character is either 'y'es or 'n'o
+			break;
 		case kRadionuclideHalfLife:
 			d.radionuclideHalfLife = dcmStrFloat(lLength, &buffer[lPos]);
 			break;
@@ -6229,6 +6425,12 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			}
 			break;
 		}
+		case kDeepLearningText: {
+			if ((d.manufacturer != kMANUFACTURER_SIEMENS) || (lLength < 2))
+				break;
+			dcmStr(lLength, &buffer[lPos], d.deepLearningText, true);
+			break;
+		}
 		case kAcquisitionMatrixText21:
 			//fall through to kAcquisitionMatrixText
 		case kAcquisitionMatrixText: {
@@ -6305,6 +6507,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]);
 			break;
 		case kIconImageSequence:
+			if (lLength > 8)
+				break; //issue638: we will skip entire icon if there is an explicit length
 			isIconImageSequence = true;
 			if (sqDepthIcon < 0)
 				sqDepthIcon = sqDepth;
@@ -6316,6 +6520,10 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			//~d.numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]);
 			numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]);
 			break;
+		/*case kTRPhilips:
+			if (d.manufacturer != kMANUFACTURER_PHILIPS) break;
+			TRPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
+			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');
@@ -6345,7 +6553,10 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			break; //warp
 		}
 		case kScanningSequenceSiemens:
-			dcmStr(lLength, &buffer[lPos], scanningSequenceSiemens);
+			if (d.manufacturer == kMANUFACTURER_SIEMENS) 
+				dcmStr(lLength, &buffer[lPos], scanningSequenceSiemens);
+			if (d.manufacturer == kMANUFACTURER_GE) //issue690
+				diffusionDirectionTypeGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
 			break;
 		case kSequenceVariant21:
 			if (d.manufacturer != kMANUFACTURER_SIEMENS)
@@ -6486,7 +6697,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			//  ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) &&
 			//  (isAtFirstPatientPosition || isnan(d.patientPosition[1])))
 			//if((d.manufacturer == kMANUFACTURER_SIEMENS) || ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ))
-			if ((d.manufacturer == kMANUFACTURER_MEDISO) || (d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_HITACHI) || (d.manufacturer == kMANUFACTURER_SIEMENS) || (d.manufacturer == kMANUFACTURER_PHILIPS)) {
+			if (true) {
+			//if ((d.manufacturer == kMANUFACTURER_MEDISO) || (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);
@@ -6503,9 +6715,10 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 				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);
+				isHasBVec = true;
 			}
 			break;
-		case kImagingFrequency2:
+		case kImagingFrequencyFD:
 			d.imagingFrequency = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
 			break;
 		case kParallelReductionFactorOutOfPlane:
@@ -6513,6 +6726,12 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 				break;
 			d.accelFactOOP = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
 			break;
+		case kSARFD:
+			//Siemens XA uses kSARFD instead of kSAR
+			// ignore as we also need to know the definition issue 668
+			//d.SAR = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
+			//maxSAR = fmax(maxSAR, d.SAR);
+			break;
 		//case kFrameAcquisitionDuration :
 		//	frameAcquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //issue369
 		//	break;
@@ -6537,42 +6756,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 				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;
+			isHasBMatrix = true;
 			break;
 		}
 		case kSliceNumberMrPhilips: {
@@ -6658,6 +6842,17 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			vFHPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
 			set_diffusion_directionPhilips(&volDiffusion, vFHPhilips, 2);
 			break;
+		case kDeepLearningPhilips: { //CS
+			if (d.manufacturer != kMANUFACTURER_PHILIPS)
+				break;
+			char st[kDICOMStr];
+			//see dcm_qa_cs_dl reports `none` or `CS_SENSE_AI`
+			dcmStr(lLength, &buffer[lPos], st);
+			if (strstr(st, "_AI") != NULL) {
+				d.isDeepLearning = true;
+				dcmStr(lLength, &buffer[lPos], d.deepLearningText, true);
+			}
+		}
 		case kPrivatePerFrameSq:
 			if (d.manufacturer != kMANUFACTURER_PHILIPS)
 				break;
@@ -6689,6 +6884,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			philMRImageDiffVolumeNumber = dcmStrInt(lLength, &buffer[lPos]);
 			break;
 		case kOriginalAttributesSq:
+			if (lLength > 8)
+				break; //issue639: we will skip entire icon if there is an explicit length
 			is4000561SQ = true;
 			break;
 		case kWaveformSq:
@@ -6703,9 +6900,14 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 		case kCSAImageHeaderInfo:
 			if ((lPos + lLength) > fileLen)
 				break;
-			readCSAImageHeader(&buffer[lPos], lLength, &d.CSA, isVerbose, d.is3DAcq); //, dti4D);
+			readCSAImageHeader(&buffer[lPos], lLength, &d.CSA, isVerbose, d.is3DAcq);
 			if (!d.isHasPhase)
 				d.isHasPhase = d.CSA.isPhaseMap;
+			if ((d.CSA.coilNumber > 0) && (strlen(d.coilName) < 1)) {
+				snprintf(d.coilName, kDICOMStr, "%d", d.CSA.coilNumber);
+				//printf("issue631 coil name '%s'\n", d.coilName);
+				d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName));
+			}
 			break;
 		case kCSASeriesHeaderInfo:
 			if ((lPos + lLength) > fileLen)
@@ -6744,6 +6946,11 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 				break;
 			d.shimGradientZ = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian);
 			break;
+		case kVasCollapseFlagGE: //SS issue 690 16=DiffusionDtiDicomValue
+			if (d.manufacturer != kMANUFACTURER_GE)
+				break;
+			diffusionDirectionTypeGE = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian);
+			break;
 		case kPrescanReuseString: //LO
 			if (d.manufacturer != kMANUFACTURER_GE)
 				break;
@@ -6757,7 +6964,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			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);
+				printMessage(" UserDefineDataGE file offset/length %zu %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;
@@ -6765,7 +6972,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			//debug code to export binary data
 			/*
 				char str[kDICOMStr];
-				sprintf(str, "%s_ge.bin",fname);
+				snprintf(str, kDICOMStr, "%s_ge.bin",fname);
 				FILE *pFile = fopen(str, "wb");
 				fwrite(&buffer[lPos], 1, lLength, pFile);
 				fclose (pFile);
@@ -6821,16 +7028,18 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			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)
+			if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNKNOWN) { //issue674 precedence of 0018,9034 over 0043,102A
+				if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_FLIPPED)
 					d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
-				else
+				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;
@@ -6920,6 +7129,24 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 				d.CSA.multiBandFactor = mb;
 			break;
 		}
+		case kCompressedSensingParameters: { //LO issue672
+			if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 2))
+				break;
+			//0043,10b7) LO [1.24\1\10\0]    #  12, 4 Compressed Sensing Parameters
+			float cs = dcmStrFloat(lLength, &buffer[lPos]);
+			if (cs > 1.0)
+				d.compressedSensingFactor = cs;
+			//dcmStr(lLength, &buffer[lPos], d.compressedSensingText);
+			break;
+		}
+		case kDeepLearningParameters: { //LO issue672
+			if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 2))
+				break;
+			//(0043,10ca) LO [0.75\High]
+			d.isDeepLearning = true;
+			dcmStr(lLength, &buffer[lPos], d.deepLearningText, true);
+			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
@@ -7070,30 +7297,30 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			// 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);
+			snprintf(str, kDICOMStr, "%*c%04x,%04x %u@%zu ", 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]);
+				//snprintf(str, kDICOMStr, "%s%c%c ", str, vr[0], vr[1]);
 				//if (snprintf(str2, kDICOMStr-1, "%s%c%c", str, vr[0], vr[1]) < 0) exit(EXIT_FAILURE);
 				strncat(str, &vr[0], 1);
 				str[kDICOMStr-1]  = '\0'; //silence warning -Wstringop-truncation
 				strncat(str, &vr[1], 1);
 				str[kDICOMStr-1]  = '\0'; //silence warning -Wstringop-truncation
 				strcat(str, " ");
-				//sprintf(str, "%s%c%c ", str2, vr[0], vr[1]);
+				//snprintf(str, kDICOMStr, "%s%c%c ", str2, vr[0], vr[1]);
 				char str2[kDICOMStr] = "";
 				if ((vr[0] == 'F') && (vr[1] == 'D'))
-					sprintf(str2, "%g ", dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian));
+					snprintf(str2, kDICOMStr, "%g ", dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian));
 				if ((vr[0] == 'F') && (vr[1] == 'L'))
-					sprintf(str2, "%g ", dcmFloat(lLength, &buffer[lPos], d.isLittleEndian));
+					snprintf(str2, kDICOMStr, "%g ", dcmFloat(lLength, &buffer[lPos], d.isLittleEndian));
 				if ((vr[0] == 'S') && (vr[1] == 'S'))
-					sprintf(str2, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
+					snprintf(str2, kDICOMStr, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
 				if ((vr[0] == 'S') && (vr[1] == 'L'))
-					sprintf(str2, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
+					snprintf(str2, kDICOMStr, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
 				if ((vr[0] == 'U') && (vr[1] == 'S'))
-					sprintf(str2, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
+					snprintf(str2, kDICOMStr, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
 				if ((vr[0] == 'U') && (vr[1] == 'L'))
-					sprintf(str, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
+					snprintf(str2, kDICOMStr, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
 				if ((vr[0] == 'A') && (vr[1] == 'E'))
 					isStr = true;
 				if ((vr[0] == 'A') && (vr[1] == 'S'))
@@ -7171,6 +7398,15 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 		//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;
+	} else if ((!isEncapsulatedData) && (d.imageStart < 128)) {
+		//issue639 d.samplesPerPixel == 3
+		int imageStart = (int) (lPos- lLength);
+		int imgBytes = (int) (lLength);
+		int imgBytesExpected = (d.bitsAllocated >> 3) * d.samplesPerPixel * d.xyzDim[1]  * d.xyzDim[2] ;
+		if ((imgBytes >= imgBytesExpected) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1)) {
+			printf("Assuming final tag is Pixel Data (7fe0,0010) (issue 639)\n");
+			d.imageStart = imageStart;
+		}
 	}
 	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)
@@ -7187,7 +7423,6 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 			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);
@@ -7236,7 +7471,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 		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
+		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))
@@ -7270,6 +7505,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 		d.isDerived = true; //to my knowledge, palette images always derived
 		printWarning("Photometric Interpretation 'PALETTE COLOR' not supported\n");
 	}
+	if ((isHasBMatrix) && (!isHasBVec))
+		printWarning("Underspecified BMatrix without BVector (issue 265)\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);
@@ -7307,6 +7544,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 		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 ((numberOfDynamicScans < 1) && (maxTemporalPositionIndex > minTemporalPositionIndex))
+		numberOfDynamicScans = maxTemporalPositionIndex - minTemporalPositionIndex + 1;
 	if ((locationsInAcquisitionPhilips > 0) && ((d.xyzDim[3] % locationsInAcquisitionPhilips) == 0)) {
 		d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips;
 		d.xyzDim[3] = locationsInAcquisitionPhilips;
@@ -7333,7 +7572,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 	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
+	} //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];
@@ -7562,7 +7801,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 		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
+		//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
@@ -7578,10 +7817,14 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 	//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));
+		if (d.isXA10A) //issue689
+			d.imageNum += (d.acquNum * 1000);
+		else {
+			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
@@ -7600,7 +7843,7 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 	}
 	if (((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON)) && (B0Philips > 0.0)) { //issue 388
 		char txt[1024] = {""};
-		sprintf(txt, "b=%d(", (int)round(B0Philips));
+		snprintf(txt, 1024, "b=%d(", (int)round(B0Philips));
 		if (strstr(d.imageComments, txt) != NULL) {
 			//printf("%s>>>%s %g\n", txt, d.imageComments, B0Philips);
 			int len = strlen(txt);
@@ -7644,6 +7887,41 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 	if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fl2d1") != NULL)) {
 		d.isLocalizer = true;
 	}
+	// detect GE diffusion gradient cycling mode (see issue 635)
+	// GE diffusion epi
+	if ((d.epiVersionGE == kGE_EPI_EPI2) || (d.internalepiVersionGE == 2)) {
+		// Diffusion tensor file number
+		d.tensorFileGE = userData11GE;
+		// cycling systems: Premier, UHP, 7.0T
+		if ((strstr(d.manufacturersModelName, "Premier") != NULL) || (strstr(d.manufacturersModelName, "UHP") != NULL) || (strstr(d.manufacturersModelName, "7.0T") != NULL)) {
+			// cycling special OFF mode
+			if (isSameFloatGE(userData15GE, 0.72))
+				d.diffCyclingModeGE = kGE_DIFF_CYCLING_SPOFF;
+			// 2TR cycling mode
+			else if (userData12GE == 2) {
+				d.diffCyclingModeGE = kGE_DIFF_CYCLING_2TR;
+				if (userData11GE == 0)
+					d.tensorFileGE = 2;
+			}
+			// 3TR cycling mode
+			else if (userData12GE == 3) {
+				d.diffCyclingModeGE = kGE_DIFF_CYCLING_3TR;
+				if (userData11GE == 0)
+					d.tensorFileGE = 3;
+			}
+			// (Default) ALLTR cycling mode
+			else 
+				d.diffCyclingModeGE = kGE_DIFF_CYCLING_ALLTR;
+		}
+		// Non-cylcing systems: all other systems including MR750, Architect, etc
+		else {
+			d.diffCyclingModeGE = kGE_DIFF_CYCLING_OFF;
+		}
+	}
+	if ((d.accelFactPE < accelFactPE) && (accelFactPE > 1.0)) {
+		d.accelFactPE = accelFactPE;
+		//printf("Determining accelFactPE from 0018,9069 not 0021,1009 or 0051,1011\n");
+	}
 	//detect pepolar https://github.com/nipy/heudiconv/issues/479
 	if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 1))
 		d.epiVersionGE = kGE_EPI_PEPOLAR_REV;
@@ -7700,6 +7978,9 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 		//in practice 0020,0110 not used
 		//https://github.com/bids-standard/bep001/blob/repetitiontime/Proposal_RepetitionTime.md
 	}
+	//issue690
+	if ((d.manufacturer == kMANUFACTURER_GE) && (diffusionDirectionTypeGE > 0) && (diffusionDirectionTypeGE != 16))
+		d.numberOfDiffusionDirectionGE = 0;
 	//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");
@@ -7718,6 +7999,8 @@ https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6
 		d.rawDataRunNumber = philMRImageDiffVolumeNumber;
 		d.phaseNumber  = 0; 
 	}
+	//issue 668: several SAR levels for different regions (IEC_HEAD, IEC_LOCAL, etc)
+	d.SAR = fmax(maxSAR, d.SAR);
 	// d.rawDataRunNumber =  (d.rawDataRunNumber > d.phaseNumber) ? d.rawDataRunNumber : d.phaseNumber; //will not work: conflict for MultiPhase ASL with multiple averages
 	//end: issue529
 	if (hasDwiDirectionality)
diff --git a/console/nii_dicom.h b/console/nii_dicom.h
index 2458f75..aa86ef4 100644
--- a/console/nii_dicom.h
+++ b/console/nii_dicom.h
@@ -50,11 +50,16 @@ extern "C" {
     #define kCPUsuf " " //unknown CPU
 #endif
 
-#define kDCMdate "v1.0.20220720"
+#define kDCMdate "v1.0.20230411"
 #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
+
+#if defined(__linux__) //Unix users must use setrlimit
+  static const int kMaxSlice2D = 65535; //issue460 maximum number of 2D slices in 4D (Philips) images
+#else
+  static const int kMaxSlice2D = 131070;// 65535; //issue460 maximum number of 2D slices in 4D (Philips) images
+#endif
 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
@@ -70,6 +75,8 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire
 #define kMANUFACTURER_HITACHI  7
 #define kMANUFACTURER_CANON  8
 #define kMANUFACTURER_MEDISO  9
+#define kMANUFACTURER_MRSOLUTIONS  10
+#define kMANUFACTURER_HYPERFINE  11
 
 //note: note a complete modality list, e.g. XA,PX, etc
 #define kMODALITY_UNKNOWN  0
@@ -98,6 +105,13 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire
 #define kGE_EPI_PEPOLAR_REV_FWD_FLIP  7
 #define kGE_EPI_PEPOLAR_FWD_REV_FLIP  8
 
+//GE Diff Gradient Cycling Mode
+#define kGE_DIFF_CYCLING_UNKNOWN -1
+#define kGE_DIFF_CYCLING_OFF  0
+#define kGE_DIFF_CYCLING_ALLTR  1
+#define kGE_DIFF_CYCLING_2TR 2
+#define kGE_DIFF_CYCLING_3TR 3
+#define kGE_DIFF_CYCLING_SPOFF  100
 
 //GE phase encoding
 #define kGE_PHASE_ENCODING_POLARITY_UNKNOWN  -1
@@ -213,29 +227,28 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8;
     } 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;
-
+        float sliceTiming[kMaxEPI3D], dtiV[4], sliceNormV[4], bandwidthPerPixelPhaseEncode, sliceMeasurementDuration;
+        int coilNumber, 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 postLabelDelay, shimGradientX, shimGradientY, shimGradientZ, phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme;
-        float  xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4], velocityEncodeScaleGE;
+        int postLabelDelay, shimGradientX, shimGradientY, shimGradientZ, phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionT2GE, numberOfDiffusionDirectionGE, tensorFileGE, diffCyclingModeGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, frequencyEncodingSteps, phaseEncodingStepsOutOfPlane, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme;
+        float compressedSensingFactor, xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4], velocityEncodeScaleGE;
         float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4];
         float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57)
         float frameReferenceTime, frameDuration, ecat_isotope_halflife, ecat_dosage;
         float pixelPaddingValue;  // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present.
-        double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode;
+        double imagingFrequency, acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode;
         char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr];
         char prescanReuseString[kDICOMStr], imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageTypeText[kDICOMStr], imageType[kDICOMStr], institutionalDepartmentName[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr], accessionNumber[kDICOMStr], seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr],sequenceVariant[kDICOMStr],scanningSequence[kDICOMStr], patientBirthDate[kDICOMStr], patientAge[kDICOMStr],  studyDate[kDICOMStr],studyTime[kDICOMStr];
-        char scanOptions[kDICOMStrLarge], institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge];
+        char deepLearningText[kDICOMStrLarge], scanOptions[kDICOMStrLarge], institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge];
         uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS];
         struct TCSAdata CSA;
-        bool isRealIsPhaseMapHz, isPrivateCreatorRemap, isHasOverlay, isEPI, isIR, isPartialFourier, isDiffusion, isVectorFromBMatrix, isRawDataStorage, isGrayscaleSoftcopyPresentationState, isStackableSeries, isCoilVaries, isNonParallelSlices, isBVecWorldCoordinates, isSegamiOasis, isXA10A, isScaleOrTEVaries, isScaleVariesEnh, isDerived, isXRay, isMultiEcho, isValid, is3DAcq, is2DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase, isHasImaginary, isHasReal, isHasMagnitude,isHasMixed, isFloat, isResampled, isLocalizer;
+        bool isDeepLearning, isVariableFlipAngle, isQuadruped, 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 {
diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp
index 5cfb3ad..f913f2f 100644
--- a/console/nii_dicom_batch.cpp
+++ b/console/nii_dicom_batch.cpp
@@ -59,6 +59,7 @@
 #ifndef M_PI
 #define M_PI 3.14159265358979323846
 #endif
+#define kSliceTolerance 0.2
 
 #if defined(_WIN64) || defined(_WIN32)
 const char kPathSeparator = '\\';
@@ -390,7 +391,7 @@ void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI
 	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
+			if ((vx[i].V[0] > 50.0) && (!d->isDerived))	//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
@@ -629,6 +630,10 @@ int phoenixOffsetCSASeriesHeader(unsigned char *buff, int lLength) {
 		memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag
 		if (!littleEndianPlatform())
 			nifti_swap_4bytes(1, &tagCSA.nitems);
+		if (tagCSA.nitems > 128) {
+			printError("%d n_tags CSA Series Header corrupted (0029,1020 ) see issue 633.\n", tagCSA.nitems);
+			return EXIT_FAILURE;
+		}
 		//printf("%d CSA of %s %d\n",lPos, tagCSA.name, tagCSA.nitems);
 		lPos += sizeof(tagCSA);
 		if (strcmp(tagCSA.name, "MrPhoenixProtocol") == 0)
@@ -646,7 +651,7 @@ int phoenixOffsetCSASeriesHeader(unsigned char *buff, int lLength) {
 
 #define kMaxWipFree 64
 typedef struct {
-	float TE0, TE1, delayTimeInTR, phaseOversampling, phaseResolution, txRefAmp;
+	float TE0, TE1, delayTimeInTR, phaseOversampling, phaseResolution, txRefAmp, accelFactTotal;
 	int phaseEncodingLines, existUcImageNumb, ucMode, baseResolution, interp, partialFourier, echoSpacing,
 		difBipolar, parallelReductionFactorInPlane, refLinesPE, combineMode, patMode, ucMTC, accelFact3D;
 	float alFree[kMaxWipFree];
@@ -676,6 +681,7 @@ void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, i
 	csaAscii->difBipolar = 0; //0=not assigned,1=bipolar,2=monopolar
 	csaAscii->parallelReductionFactorInPlane = 0;
 	csaAscii->accelFact3D = 0;//lAccelFact3D
+	csaAscii->accelFactTotal = 0.0;
 	csaAscii->refLinesPE = 0;
 	csaAscii->combineMode = 0;
 	csaAscii->patMode = 0;
@@ -707,8 +713,11 @@ void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, i
 	size_t result = fread(buffer, 1, csaLength, pFile);
 	if ((int)result != csaLength)
 		return;
+	fclose(pFile);
 	//next bit complicated: restrict to ASCII portion to avoid buffer overflow errors in BINARY portion
 	int startAscii = phoenixOffsetCSASeriesHeader((unsigned char *)buffer, csaLength);
+	//n.b. previous function parses binary V* "SV10" portion of header
+	// it will return "EXIT_FAILURE for text based X* "<XProtocol>"
 	int csaLengthTrim = csaLength;
 	char *bufferTrim = buffer;
 	if ((startAscii > 0) && (startAscii < csaLengthTrim)) { //ignore binary data at start
@@ -757,6 +766,12 @@ void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, i
 		csaAscii->parallelReductionFactorInPlane = readKey(keyStrAF, keyPos, csaLengthTrim);
 		char keyStrAF3D[] = "sPat.lAccelFact3D";
 		csaAscii->accelFact3D = readKey(keyStrAF3D, keyPos, csaLengthTrim);
+		char keyStrAFTotal[] = "sPat.dTotalAccelFact";
+		csaAscii->accelFactTotal = readKeyFloat(keyStrAFTotal, keyPos, csaLengthTrim);
+		//issue 672: the tag "sSliceAcceleration.lMultiBandFactor" is not reliable:
+		//  series 7 dcm_qa_xa30 has x3 multiband, but this tag reports "1" (perhaps cmrr sequences)
+		//char keyStrMB[] = "sSliceAcceleration.lMultiBandFactor";
+		//csaAscii->multiBandFactor = readKey(keyStrMB, keyPos, csaLengthTrim);
 		char keyStrRef[] = "sPat.lRefLinesPE";
 		csaAscii->refLinesPE = readKey(keyStrRef, keyPos, csaLengthTrim);
 		char keyStrCombineMode[] = "ucCoilCombineMode";
@@ -801,7 +816,7 @@ void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, i
 		if (keyPosTi) {
 			for (int k = 0; k < kMaxWipFree; k++) {
 				char txt[1024] = {""};
-				sprintf(txt, "%s%d]", keyStrTiFree, k);
+				snprintf(txt, 1024, "%s%d]", keyStrTiFree, k);
 				csaAscii->alTI[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim);
 			}
 		}
@@ -814,7 +829,7 @@ void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, i
 		if (keyPosFree) {
 			for (int k = 0; k < kMaxWipFree; k++) {
 				char txt[1024] = {""};
-				sprintf(txt, "%s%d]", keyStrAlFree, k);
+				snprintf(txt, 1024, "%s%d]", keyStrAlFree, k);
 				csaAscii->alFree[k] = readKeyFloat(txt, keyPos, csaLengthTrim);
 			}
 		}
@@ -833,7 +848,7 @@ void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, i
 		if (keyPosFree) {
 			for (int k = 0; k < kMaxWipFree; k++) {
 				char txt[1024] = {""};
-				sprintf(txt, "%s%d]", keyStrAdFree, k);
+				snprintf(txt, 1024, "%s%d]", keyStrAdFree, k);
 				csaAscii->adFree[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim);
 			}
 		}
@@ -889,7 +904,6 @@ void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, i
 		char keyStrSh7[] = "sGRADSPEC.alShimCurrent[4]";
 		shimSetting[7] = readKeyFloat(keyStrSh7, keyPos, csaLengthTrim);
 	}
-	fclose(pFile);
 	free(buffer);
 	return;
 } // siemensCsaAscii()
@@ -1028,6 +1042,7 @@ void json_Str(FILE *fp, const char *sLabel, char *sVal) { // issue131,425
 	int o = 0;
 	for (int i = 0; i < (int)strlen(sVal); i++) {
 		//escape double quote (") and Backslash
+		//if ((sVal[i] == '"') || (sVal[i] == '\\') || (sVal[i] == '/')) { //issue640: escape double quotes, back slash, or slash
 		if ((sVal[i] == '"') || (sVal[i] == '\\')) { //escape double quotes and back slash
 			sValEsc[o] = '\\';
 			o++;
@@ -1205,7 +1220,7 @@ tse3d: T2*/
 	//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);
+		fprintf(fp, "\t\"ImagingFrequency\": %.10g,\n", d.imagingFrequency);
 	switch (d.manufacturer) {
 	case kMANUFACTURER_BRUKER:
 		fprintf(fp, "\t\"Manufacturer\": \"Bruker\",\n");
@@ -1234,6 +1249,12 @@ tse3d: T2*/
 	case kMANUFACTURER_UIH:
 		fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n");
 		break;
+	case kMANUFACTURER_MRSOLUTIONS:
+		fprintf(fp, "\t\"Manufacturer\": \"MRSolutions\",\n");
+		break;
+	case kMANUFACTURER_HYPERFINE:
+		fprintf(fp, "\t\"Manufacturer\": \"Hyperfine\",\n");
+		break;
 	};
 	//if (d.epiVersionGE == 0)
 	//	fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n");
@@ -1274,6 +1295,8 @@ tse3d: T2*/
 		//d.patientBirthDate //convert from DICOM YYYYMMDD to JSON
 		//d.patientAge //4-digit Age String: nnnD, nnnW, nnnM, nnnY;
 	}
+	if (d.isQuadruped)
+		json_Bool(fp, "\t\"Quadruped\": %s,\n", true); // BIDS suggests 0018,9020 but Siemens V-series do not populate this, alternatives are CSA or (0018,0021) CS [SK\MTC\SP]
 	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);
@@ -1332,7 +1355,6 @@ tse3d: T2*/
 	if ((strstr(d.imageTypeText, "_ND") != NULL) || (strstr(d.imageType, "_ND") != NULL) ||
 		(strstr(d.imageTypeText, "_ND") != NULL) || (strstr(d.imageType, "_ND") != NULL))
 			fprintf(fp, "\t\"NonlinearGradientCorrection\": false,\n");
-
 	if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc)
 		fprintf(fp, "\t\"RawImage\": false,\n");
 	if (d.seriesNum > 0)
@@ -1366,8 +1388,11 @@ tse3d: T2*/
 	// 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);
+	bool maskComments =  (strlen(opts.imageComments) == 1) && (opts.imageComments[0] == '\t');
+	if (!maskComments) {
+		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) {
@@ -1461,6 +1486,7 @@ tse3d: T2*/
 		json_Float(fp, "\t\"SliceThickness\": %g,\n", d.zThick);
 		json_Float(fp, "\t\"SpacingBetweenSlices\": %g,\n", d.zSpacing);
 	}
+	//if (!opts.isAnonymizeBIDS) //issue668 is SAR identifiable??
 	json_Float(fp, "\t\"SAR\": %g,\n", d.SAR);
 	if (d.numberOfAverages > 1.0)
 		json_Float(fp, "\t\"NumberOfAverages\": %g,\n", d.numberOfAverages);
@@ -1495,6 +1521,8 @@ tse3d: T2*/
 		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);
+	if (d.isVariableFlipAngle)
+		json_Bool(fp, "\t\"VariableFlipAngleFlag\": %s,\n", true); // BIDS suggests 0018,9020 but Siemens V-series do not populate this, alternatives are CSA or (0018,0021) CS [SK\MTC\SP]
 	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
@@ -1511,6 +1539,31 @@ tse3d: T2*/
 	json_Str(fp, "\t\"PrescanReuseString\": \"%s\",\n", d.prescanReuseString);
 	float delayTimeInTR = -0.01;
 	float repetitionTimePreparation = 0.0;
+	// GE Diffusion specific fields
+	if ((d.epiVersionGE == kGE_EPI_EPI2) || (d.internalepiVersionGE == 2)) {
+		if (d.numberOfDiffusionDirectionGE > 0)
+			fprintf(fp, "\t\"NumberOfDiffusionDirectionGE\": %d,\n", d.numberOfDiffusionDirectionGE);
+		if (d.numberOfDiffusionT2GE > 0)
+			fprintf(fp, "\t\"NumberOfDiffusionT2GE\": %d,\n", d.numberOfDiffusionT2GE);
+		if (d.tensorFileGE > 0)
+			fprintf(fp, "\t\"TensorFileNumberGE\": %d,\n", d.tensorFileGE);
+		if (opts.diffCyclingModeGE >= 0) {
+			fprintf(fp, "\t\"DiffGradientCyclingGE\": \"OVERRIDE\",\n"); // see issue 635
+			d.diffCyclingModeGE = opts.diffCyclingModeGE;
+		}
+		if (d.diffCyclingModeGE > 0) {
+			if (d.diffCyclingModeGE == kGE_DIFF_CYCLING_OFF)
+				fprintf(fp, "\t\"DiffGradientCyclingGE\": \"OFF\",\n");
+			if (d.diffCyclingModeGE == kGE_DIFF_CYCLING_ALLTR)
+				fprintf(fp, "\t\"DiffGradientCyclingGE\": \"ALLTR\",\n");
+			if (d.diffCyclingModeGE == kGE_DIFF_CYCLING_2TR)
+				fprintf(fp, "\t\"DiffGradientCyclingGE\": \"2TR\",\n");
+			if (d.diffCyclingModeGE == kGE_DIFF_CYCLING_3TR)
+				fprintf(fp, "\t\"DiffGradientCyclingGE\": \"3TR\",\n");
+			if (d.diffCyclingModeGE == kGE_DIFF_CYCLING_SPOFF)
+				fprintf(fp, "\t\"DiffGradientCyclingGE\": \"SPOFF\",\n");
+		}
+	}
 #ifdef myReadAsciiCsa
 	if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.SeriesHeader_offset > 0) && (d.CSA.SeriesHeader_length > 0)) {
 		float pf = 1.0f; //partial fourier
@@ -1564,7 +1617,7 @@ tse3d: T2*/
 			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\"MeanGzx10\": %g,\n", csaAscii.adFree[10]);
 			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
@@ -1631,14 +1684,14 @@ tse3d: T2*/
 			for (int k = 11; k < 31; k++) {
 				if (isValid) {
 					char newstr[256];
-					sprintf(newstr, "\t\"PLD%d\": %%g,\n", k-11);
+					snprintf(newstr, 256, "\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
+				snprintf(newstr, 256, "\t\"sWipMemBlockAdFree%d\": %%g,\n", k); //issue483: sWipMemBlock.AdFree -> sWipMemBlockAdFree
 				json_FloatNotNan(fp, newstr, csaAscii.adFree[k]);
 			}
 		}
@@ -1762,25 +1815,31 @@ tse3d: T2*/
 		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.accelFact3D > 0)
+			d.accelFactOOP = csaAscii.accelFact3D;
+		//see issue 672 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
+			//1=SENSE, 2=GRAPPA, 32=SMS??, 256=CompressedSense?
 			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)
-			}
+			d.accelFactPE = csaAscii.parallelReductionFactorInPlane; //issue672: csa precedence over value found 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);
 		}
+		if ((csaAscii.patMode == 256) && (!isnan(csaAscii.accelFactTotal)) && (csaAscii.accelFactTotal > (d.accelFactPE * d.accelFactOOP) ))
+			d.compressedSensingFactor = csaAscii.accelFactTotal; //see dcm_qa_cs_dl
 	} 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)) {
+		int phaseEncodingLines = d.phaseEncodingLines;
+		if (phaseEncodingLines < 1) //support enhanced DICOM terminology
+			phaseEncodingLines = d.phaseEncodingSteps;
+		if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (!d.is3DAcq) && (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;
+			float pf = (float)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;
@@ -1838,7 +1897,7 @@ tse3d: T2*/
 		fprintf(fp, "\t\"MultibandAccelerationFactor\": %d,\n", d.CSA.multiBandFactor);
 	json_Float(fp, "\t\"PercentPhaseFOV\": %g,\n", d.phaseFieldofView);
 	json_Float(fp, "\t\"PercentSampling\": %g,\n", d.percentSampling);
-	if (d.echoTrainLength > 1)											//>1 as for Siemens EPI this is 1, Siemens uses EPI factor http://mriquestions.com/echo-planar-imaging.html
+	if (d.echoTrainLength > 1) //>1 as for Siemens EPI this is 1, Siemens uses EPI factor http://mriquestions.com/echo-planar-imaging.html
 		fprintf(fp, "\t\"EchoTrainLength\": %d,\n", d.echoTrainLength); //0018,0091 Combination of partial fourier and in-plane parallel imaging
 	if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_PHASE)
 		fprintf(fp, "\t\"PartialFourierDirection\": \"PHASE\",\n");
@@ -1854,6 +1913,10 @@ tse3d: T2*/
 		fprintf(fp, "\t\"PhaseEncodingStepsNoPartialFourier\": %d,\n", d.phaseEncodingSteps);
 	} else if (d.phaseEncodingSteps > 0)
 		fprintf(fp, "\t\"PhaseEncodingSteps\": %d,\n", d.phaseEncodingSteps);
+	if (d.frequencyEncodingSteps > 0)
+		fprintf(fp, "\t\"FrequencyEncodingSteps\": %d,\n", d.frequencyEncodingSteps);
+	if (d.phaseEncodingStepsOutOfPlane > 0)
+		fprintf(fp, "\t\"PhaseEncodingStepsOutOfPlane\": %d,\n", d.phaseEncodingStepsOutOfPlane);
 	if ((d.phaseEncodingLines > 0) && (d.modality == kMODALITY_MR))
 		fprintf(fp, "\t\"AcquisitionMatrixPE\": %d,\n", d.phaseEncodingLines);
 	//Compute ReconMatrixPE
@@ -1872,6 +1935,12 @@ tse3d: T2*/
 	}
 	if ((d.modality == kMODALITY_MR) && (reconMatrixPE > 0))
 		fprintf(fp, "\t\"ReconMatrixPE\": %d,\n", reconMatrixPE);
+	if ((d.accelFactPE > 1.0) && (d.manufacturer == kMANUFACTURER_PHILIPS) && strstr(d.parallelAcquisitionTechnique, "CSENSE") ) {
+		//see dcm_qa_cs_dl: while GE allows you to set ASSET and compressed sense, Philips reports only CSENSE
+		d.compressedSensingFactor = d.accelFactPE;
+		d.accelFactPE = 1.0;
+		d.parallelAcquisitionTechnique[0] = '\0';
+	}
 	double bandwidthPerPixelPhaseEncode = d.bandwidthPerPixelPhaseEncode;
 	if (bandwidthPerPixelPhaseEncode == 0.0)
 		bandwidthPerPixelPhaseEncode = d.CSA.bandwidthPerPixelPhaseEncode;
@@ -1882,7 +1951,19 @@ tse3d: T2*/
 	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);
+		json_Float(fp, "\t\"ParallelReductionFactorOutOfPlane\": %g,\n", d.accelFactOOP); //issue672
+	if (d.compressedSensingFactor > 1.0)
+		json_Float(fp, "\t\"CompressedSensingFactor\": %g,\n", d.compressedSensingFactor);
+	//detect if Siemens data is DeepLearning: see dcm_qa_cs_dl
+	if (d.manufacturer == kMANUFACTURER_SIEMENS) {
+		//DRB,DRG,DRS DeepReveal Boost,Gain,Sharp
+		d.isDeepLearning = (strstr(d.imageType, "_DRB_")|| strstr(d.imageType, "_DRG_") || strstr(d.imageType, "_DRS_") ||
+			strstr(d.imageTypeText, "_DRB_")|| strstr(d.imageTypeText, "_DRG_") || strstr(d.imageTypeText, "_DRS_"));
+	}
+	if (d.isDeepLearning) {
+		json_Bool(fp, "\t\"DeepLearning\": %s,\n", 1);
+		json_Str(fp, "\t\"DeepLearningDetails\": \"%s\",\n", d.deepLearningText);
+	}
 	//EffectiveEchoSpacing
 	// Siemens bandwidthPerPixelPhaseEncode already accounts for the effects of parallel imaging,
 	// interpolation, phaseOversampling, and phaseResolution, in the context of the size of the
@@ -2001,7 +2082,7 @@ tse3d: T2*/
 	} //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)) {
+	if ((d.modality != kMODALITY_CT) && (d.modality != kMODALITY_PT) && (!d.is3DAcq) && (h->dim[3] > 1) && (d.CSA.sliceTiming[1] >= 0.0) && (d.CSA.sliceTiming[0] >= 0.0)) {
 		fprintf(fp, "\t\"SliceTiming\": [\n");
 		for (int i = 0; i < h->dim[3]; i++) {
 			if (i != 0)
@@ -2131,7 +2212,6 @@ unsigned char *reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg,
 	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)
@@ -2140,7 +2220,6 @@ unsigned char *reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg,
 		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);
@@ -2181,6 +2260,7 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st
 #endif
 	//https://github.com/rordenlab/dcm2niix/issues/352
 	bool allB0 = dcmList[indx0].isDiffusion;
+	bool isDerived = dcmList[indx0].isDerived;
 	if (dcmList[indx0].isDerived)
 		allB0 = false; //e.g. FA map
 	if ((numDti == numVol) && (numDti > 1))
@@ -2225,10 +2305,10 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st
 	}
 	if (numDti < 1)
 		return NULL;
-	if ((numDti < 3) && (nConvert < 3))
+	if ((numDti < 2) && (nConvert < 2))
 		return NULL;
 	TDTI *vx = NULL;
-	if (numDti > 2) {
+	if (numDti > 1) {
 		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
@@ -2310,11 +2390,13 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st
 	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);
+	if (!isDerived) { //no warnings for derived data
+		//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));
@@ -2457,6 +2539,82 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st
 				vx[i].V[1] = -vx[i].V[1];
 		} //for each direction
 	} //if not a mosaic
+
+#ifdef USING_DCM2NIIXFSWRAPPER
+        // make adjustments for MGH bvecs output
+        for (int i = 0; i < (numDti); i++) {
+	  if (sliceDir < 0)
+	  {
+            // at this point, bvecs output is calculated as not isFlipY, assuming isFlipZ, determinant is positive.
+            // So, bvecs first column is reversed for FSL.
+            // MGH conversion: not isFlipY, slice direction not flipped, determinant is negative, 
+            // 1. we need to reverse bvecs column 1 back, 
+            // 2. also need to reverse bvecs column 3
+            
+            float tmp = vx[i].V[1];
+            vx[i].V[1] = -vx[i].V[1];
+            if (getenv("DCM2NIIXFSWRAPPER_DEBUG") != NULL && strcmp(getenv("DCM2NIIXFSWRAPPER_DEBUG"), "yes") == 0)
+	    {
+              if (i < 6)
+	        printf("nii_saveDTI() (BVECS_DEBUG) (mgh adj. sliceDir < 0) flip bvecs sign column 1: %f => %f\n", tmp, vx[i].V[1]);
+            }
+
+            if (fabs(vx[i].V[3]) > FLT_EPSILON)
+	    {
+              tmp = vx[i].V[3];
+	      vx[i].V[3] = -vx[i].V[3];
+              if (getenv("DCM2NIIXFSWRAPPER_DEBUG") != NULL && strcmp(getenv("DCM2NIIXFSWRAPPER_DEBUG"), "yes") == 0)
+	      {
+                if (i < 6)
+		  printf("nii_saveDTI() (BVECS_DEBUG) (mgh adj. sliceDir < 0) flip bvecs sign column 3: %f => %f\n", tmp, vx[i].V[3]);
+              }
+	    }
+          }
+	  else if (abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant)
+	  {
+            // swap signs for every column
+            for (int j = 1; j < 4; j++)
+	    {
+              if (fabs(vx[i].V[j]) > FLT_EPSILON)
+	      {
+                float tmp = vx[i].V[j];
+	        vx[i].V[j] = -vx[i].V[j];
+                if (getenv("DCM2NIIXFSWRAPPER_DEBUG") != NULL && strcmp(getenv("DCM2NIIXFSWRAPPER_DEBUG"), "yes") == 0)
+	        {
+                  if (i < 6)
+                    printf("nii_saveDTI() (BVECS_DEBUG) (mgh adj. abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant) flip bvecs sign column j: %f => %f\n", tmp, vx[i].V[j]);
+                }
+	      }
+            }
+	  }
+          else  // sliceDir >= 0 && abs(sliceDir) != kSliceOrientMosaicNegativeDeterminant
+	  {	
+            // MGH conversion: not flip Y, image determinant is positive, bvecs first column is reversed for FSL.
+            // So, we need to flip bvecs first column.
+            if (fabs(vx[i].V[1]) > FLT_EPSILON)
+	    {
+              float tmp = vx[i].V[1];
+	      vx[i].V[1] = -vx[i].V[1];
+              if (getenv("DCM2NIIXFSWRAPPER_DEBUG") != NULL && strcmp(getenv("DCM2NIIXFSWRAPPER_DEBUG"), "yes") == 0)
+	      {
+                if (i < 6)
+		  printf("nii_saveDTI() (BVECS_DEBUG) (mgh adj. abs(sliceDir) != kSliceOrientMosaicNegativeDeterminant) flip bvecs sign column 1: %f => %f\n", tmp, vx[i].V[1]);
+              }
+	    }
+          }
+        } //for each direction
+
+        mrifsStruct.numDti = numDti;
+        mrifsStruct.tdti = (TDTI *)malloc(numDti * sizeof(TDTI));
+        for (int i = 0; i < numDti; i++)
+	{
+            mrifsStruct.tdti[i].V[0] = vx[i].V[0];
+            mrifsStruct.tdti[i].V[1] = vx[i].V[1];
+            mrifsStruct.tdti[i].V[2] = vx[i].V[2];
+            mrifsStruct.tdti[i].V[3] = vx[i].V[3];
+        }
+#endif
+
 	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]);
@@ -2482,12 +2640,7 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st
 				for (int v = 0; v < 4; v++) //for each vector+B-value
 					dti4D->S[i].V[v] = vx[i].V[v];
 		}
-#ifdef USING_DCM2NIIXFSWRAPPER
-		mrifsStruct.tdti = vx;
-		mrifsStruct.numDti = numDti;
-#else
 		free(vx);
-#endif
 		return volOrderIndex;
 	}
 
@@ -2512,12 +2665,7 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st
 	fclose(fp);
 #endif
 	if (isIsotropic) { //issue 405: ISOTROPIC images have bval but not bvec
-#ifdef USING_DCM2NIIXFSWRAPPER
-		mrifsStruct.tdti = vx;
-		mrifsStruct.numDti = numDti;
-#else
 		free(vx);
-#endif
 		return volOrderIndex;
 	}
 
@@ -2547,12 +2695,7 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st
 #endif
 #endif
 
-#ifdef USING_DCM2NIIXFSWRAPPER
-	mrifsStruct.tdti = vx;
-	mrifsStruct.numDti = numDti;
-#else
 	free(vx);
-#endif
 	return volOrderIndex;
 } // nii_saveDTI()
 
@@ -2729,12 +2872,35 @@ int compareTFloatSort(const void *a, const void *b) {
 	return 0;
 } // compareTFloatSort()
 
+
+int isSameFloatT(float a, float b, float tolerance) {
+	return (fabs(a - b) <= tolerance);
+}
+
 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
+	//first pass: check order: issue 622
+	int i = 0;
+	bool isSequential = true;
+	for (int t = 0; t < d4; t++) { //for each volume
+		float dx = intersliceDistanceSigned(dcmList[dcmSort[i].indx], dcmList[dcmSort[i + 1].indx]);
+		for (int z = 0; z < d3; z++) { //for slice
+			if (z > 0) {
+				float dx2 = intersliceDistanceSigned(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]);
+				if (!isSameFloatT(dx, dx2, kSliceTolerance))
+					isSequential = false;
+			} //if not 1st slice (which does not have prior slice)
+			i++;
+		} //for each slice
+	} //for each volume
+	if (isSequential)
+		return true;
+	//second pass: fix if required
+	printWarning("Instance Number (0020,0013) order is not spatial.\n");
 	TFloatSort *floatSort = (TFloatSort *)malloc(nConvert * sizeof(TFloatSort));
 	int minVol = dcmList[dcmSort[0].indx].rawDataRunNumber;
 	int maxVol = minVol;
@@ -3039,7 +3205,7 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts
 		inname[strlen(inname) - 4] = '\0';
 	}
 	char outname[PATH_MAX] = {""};
-	char newstr[256];
+	char newstr[PATH_MAX];
 	if (strlen(inname) < 1) {
 		strcpy(inname, "T%t_N%n_S%s");
 	}
@@ -3079,7 +3245,7 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts
 				strcat(outname, dcm.seriesDescription);
 			if (f == 'E') {
 				isEchoReported = true;
-				sprintf(newstr, "%d", dcm.echoNum);
+				snprintf(newstr, PATH_MAX, "%d", dcm.echoNum);
 				strcat(outname, newstr);
 			}
 			if (f == 'F')
@@ -3109,6 +3275,12 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts
 					strcat(outname, "Ph");
 				else if (dcm.manufacturer == kMANUFACTURER_SIEMENS)
 					strcat(outname, "Si");
+				else if (dcm.manufacturer == kMANUFACTURER_MEDISO)
+					strcat(outname, "Me");
+				else if (dcm.manufacturer == kMANUFACTURER_MRSOLUTIONS)
+					strcat(outname, "MR");
+				else if (dcm.manufacturer == kMANUFACTURER_HYPERFINE)
+					strcat(outname, "Hy");
 				else
 					strcat(outname, "NA"); //manufacturer name not available
 			}
@@ -3125,28 +3297,28 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts
 					printWarning("Unable to append protocol name (0018,1030) to filename (it is empty).\n");
 			}
 			if (f == 'R') {
-				sprintf(newstr, "%d", dcm.imageNum);
+				snprintf(newstr, PATH_MAX, "%d", dcm.imageNum);
 				strcat(outname, newstr);
 				isImageNumReported = true;
 			}
 			if (f == 'Q')
 				strcat(outname, dcm.scanningSequence);
 			if (f == 'S') {
-				sprintf(newstr, "%ld", dcm.seriesNum);
+				snprintf(newstr, PATH_MAX, "%ld", dcm.seriesNum);
 				strcat(outname, newstr);
 				isSeriesReported = true;
 			}
 			if (f == 'T') {
-				sprintf(newstr, "%0.0f", dcm.dateTime);
+				snprintf(newstr, PATH_MAX, "%0.0f", dcm.dateTime);
 				strcat(outname, newstr);
 			}
 			if (f == 'U') {
 				if (opts.isRenameNotConvert) {
-					sprintf(newstr, "%d", dcm.acquNum);
+					snprintf(newstr, PATH_MAX, "%d", dcm.acquNum);
 					strcat(outname, newstr);
 					//isAcquisitionReported = true;
 				} else {
-					sprintf(newstr, "%d", dcm.acquNum);
+					snprintf(newstr, PATH_MAX, "%d", dcm.acquNum);
 					strcat(outname, newstr);
 #ifdef mySegmentByAcq
 	//isAcquisitionReported = true;
@@ -3176,31 +3348,31 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts
 			if (f == 'X')
 				strcat(outname, dcm.studyID);
 			if ((f == 'Y') && (dcm.rawDataRunNumber >= 0)) {
-				sprintf(newstr, "%d", dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100)
+				snprintf(newstr, PATH_MAX, "%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);
+					char zeroPad[128] = {""};
+					snprintf(zeroPad, 128, "%%0%dd", f - '0');
+					snprintf(newstr, PATH_MAX, 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);
+					char zeroPad[128] = {""};
+					snprintf(zeroPad, 128, "%%0%dd", f - '0');
+					snprintf(newstr, PATH_MAX, 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)
+					char zeroPad[128] = {""};
+					snprintf(zeroPad, 128, "%%0%dd", f - '0');
+					snprintf(newstr, PATH_MAX, 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
 				}
@@ -3215,7 +3387,7 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts
 		strcat(outname, newstr);
 	}
 	if ((isAddNamePostFixes) && (!isCoilReported) && (dcm.isCoilVaries)) {
-		//sprintf(newstr, "_c%d", dcm.coilNum);
+		//snprintf(newstr, PATH_MAX, "_c%d", dcm.coilNum);
 		//strcat (outname,newstr);
 		strcat(outname, "_c");
 		strcat(outname, dcm.coilName);
@@ -3227,23 +3399,23 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts
 	if ((isAddNamePostFixes) && (!isEchoReported) && ((dcm.isMultiEcho) || (dcm.echoNum > 1))) { //multiple echoes saved as same series
 #endif
 		if ((dcm.echoNum < 1) && (dcm.TE > 0))
-			sprintf(newstr, "_e%g", dcm.TE); //issue568: Siemens XA20 might omit echo number
+			snprintf(newstr, PATH_MAX, "_e%g", dcm.TE); //issue568: Siemens XA20 might omit echo number
 		else
-			sprintf(newstr, "_e%d", dcm.echoNum);
+			snprintf(newstr, PATH_MAX, "_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);
+		snprintf(newstr, PATH_MAX, "_e%d", dcm.echoNum);
 		strcat(outname, newstr);
 		isEchoReported = true;
 	}
 	if ((dcm.isNonParallelSlices) && (!isImageNumReported)) {
-		sprintf(newstr, "_i%05d", dcm.imageNum);
+		snprintf(newstr, PATH_MAX, "_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
+	snprintf(newstr, PATH_MAX, "_v%04d", dcm.gradDynVol+1); //+1 as indexed from zero
 	strcat (outname,newstr);
 	}*/
 	if ((isAddNamePostFixes) && (dcm.isHasImaginary)) {
@@ -3261,7 +3433,7 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts
 			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));
+		snprintf(newstr, PATH_MAX, "_t%d", (int)roundf(dcm.triggerDelayTime));
 		strcat(outname, newstr);
 	}
 	//could add (isAddNamePostFixes) to these next two, but consequences could be catastrophic
@@ -3339,8 +3511,8 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts
 					mkdir(newdir, 0700);
 #endif
 			}
-			char ch[12] = {""};
-			sprintf(ch, "%c", outname[pos]);
+			char ch[128] = {""};
+			snprintf(ch, 128, "%c", outname[pos]);
 			strcat(newdir, ch);
 		}
 	}
@@ -3605,6 +3777,12 @@ void nii_saveAttributes(struct TDICOMdata &data, struct nifti_1_header &header,
 	case kMANUFACTURER_CANON:
 		images->addAttribute("manufacturer", "Canon");
 		break;
+	case kMANUFACTURER_MRSOLUTIONS:
+		images->addAttribute("manufacturer", "MRSolutions");
+		break;
+	case kMANUFACTURER_HYPERFINE:
+		images->addAttribute("manufacturer", "Hyperfine");
+		break;
 	}
 	images->addAttribute("scannerModelName", data.manufacturersModelName);
 	images->addAttribute("imageType", data.imageType);
@@ -3714,11 +3892,11 @@ int pigz_File(char *fname, struct TDCMopts opts, size_t imgsz) {
 	strcat(command, opts.pigzname);
 	if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) {
 		char newstr[256];
-		sprintf(newstr, "\"%s -n -f -%d \"", blockSize, opts.gzLevel);
+		snprintf(newstr, 256, "\"%s -n -f -%d \"", blockSize, opts.gzLevel);
 		strcat(command, newstr);
 	} else {
 		char newstr[256];
-		sprintf(newstr, "\"%s -n \"", blockSize);
+		snprintf(newstr, 256, "\"%s -n \"", blockSize);
 		strcat(command, newstr);
 	}
 	strcat(command, fname);
@@ -4231,7 +4409,7 @@ int zmat_run(const size_t inputsize, unsigned char *inputstr, size_t *outputsize
 		/** perform compression or encoding */
 		if(zipid==zmBase64){
 			/** base64 encoding  */
-                        *outputbuf=base64_encode((const unsigned char*)inputstr, inputsize, outputsize);
+			*outputbuf=base64_encode((const unsigned char*)inputstr, inputsize, outputsize);
 		}else if(zipid==zmZlib || zipid==zmGzip){
 			/** zlib (.zip) or gzip (.gz) compression  */
 			if(zipid==zmZlib){
@@ -4551,7 +4729,7 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im
 	size_t compressedbytes, totalbytes;
 	unsigned char *compressed=NULL, *buf=NULL;
 
-	/*jnifti convers code-based header fields to human-readable/standardized strings*/
+	/*jnifti converts code-based header fields to human-readable/standardized strings*/
 	int datatypeidx;
 	const char *datatypestr[]={"uint8","int16","int32","single","complex64","double",
 				"rgb24" ,"int8","uint16","uint32","int64","uint64",
@@ -4909,7 +5087,7 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im,
 		strcat(command, opts.pigzname);
 		if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) {
 			char newstr[256];
-			sprintf(newstr, "\" -n -f -%d > \"", opts.gzLevel);
+			snprintf(newstr, 256, "\" -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 ':\'
@@ -4986,9 +5164,9 @@ int nii_saveNII3D(char *niiFilename, struct nifti_1_header hdr, unsigned char *i
 	char zeroPad[PATH_MAX] = {""};
 	double fnVol = nVol;
 	int zeroPadLen = (1 + log10(fnVol));
-	sprintf(zeroPad, "%%s_%%0%dd", zeroPadLen);
+	snprintf(zeroPad, PATH_MAX, "%%s_%%0%dd", zeroPadLen);
 	for (int i = 1; i <= nVol; i++) {
-		sprintf(fname, zeroPad, niiFilename, i);
+		snprintf(fname, 2048, zeroPad, niiFilename, i);
 		if (nii_saveNII(fname, hdr1, (unsigned char *)&im[pos], opts, d) == EXIT_FAILURE)
 			return EXIT_FAILURE;
 		pos += imgsz;
@@ -4999,12 +5177,16 @@ int nii_saveNII3D(char *niiFilename, struct nifti_1_header hdr, unsigned char *i
 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);
+	snprintf(newstr, 256, " 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) {
+int int12toint16(int U12) {
+	return (short)(U12 & 0xFFF) - ((U12 & 0x800) << 1);
+}
+
+void nii_mask12bit(unsigned char *img, struct nifti_1_header *hdr, bool isSigned) {
 	//https://github.com/rordenlab/dcm2niix/issues/251
 	if (hdr->datatype != DT_INT16)
 		return;
@@ -5016,8 +5198,15 @@ void nii_mask12bit(unsigned char *img, struct nifti_1_header *hdr) {
 	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
+	//issue 688
+	if (isSigned) {
+		for (int i = 0; i < nVox; i++)
+			img16[i] = int12toint16(img16[i]); //signed 12 bit data ranges from 0..4095, any other values are overflow
+	
+	} else {
+		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) {
@@ -5175,10 +5364,6 @@ int siemensCtKludge(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 	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
@@ -5754,7 +5939,7 @@ void checkSliceTiming(struct TDICOMdata *d, struct TDICOMdata *d1, int verbose,
 	int nSlices = 0;
 	while ((nSlices < kMaxEPI3D) && (d->CSA.sliceTiming[nSlices] >= 0.0))
 		nSlices++;
-	if (nSlices < 1)
+	if (nSlices < 2)
 		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");
@@ -5838,6 +6023,10 @@ void checkSliceTiming(struct TDICOMdata *d, struct TDICOMdata *d1, int verbose,
 	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->isDerived) { //slice timing not relevant for derived data, values mangled with Siemens XA30
+			d->CSA.sliceTiming[0] = -1.0;
+			return;
+		}
 		if (d->modality == kMODALITY_MR)
 			printWarning("Siemens MoCo? Bogus slice timing (range %g..%g, TR=%g seconds)\n", minT1, maxT1, TRms);
 		return;
@@ -5856,36 +6045,40 @@ void checkSliceTiming(struct TDICOMdata *d, struct TDICOMdata *d1, int verbose,
 	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 setMultiBandFactor(int dim3, uint64_t indx0, struct TDICOMdata *dcmList) {
+		float mn = dcmList[indx0].CSA.sliceTiming[0];
+		//first pass: find minimum
+		for (int v = 0; v < dim3; v++)
+			mn = fminf(dcmList[indx0].CSA.sliceTiming[v],mn);
+		//second pass: all times relative to min (i.e. make min = 0)
+		int mb = 0;
+		for (int v = 0; v < dim3; 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) && (mb < dim3))
+			dcmList[indx0].CSA.multiBandFactor = mb;
+} // setMultiBandFactor()
+
 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))
+	if ((!dcmList[indx0].isXA10A) || (hdr->dim[3] < 1) || (hdr->dim[4] < 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
+	if ((nConvert == (hdr->dim[3] * hdr->dim[4])) && (hdr->dim[3] < (kMaxEPI3D - 1)) && (hdr->dim[3] > 1)) {
+		//XA11 2D classic: nb XA30 in `MFSPLIT` will save each 3D volume from 4D timeseries as a unique series number!
 		for (int v = 0; v < hdr->dim[3]; v++)
 			dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].CSA.sliceTiming[0];
+		setMultiBandFactor(hdr->dim[3], indx0, dcmList);
 	} 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++) {
+		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;
+		setMultiBandFactor(hdr->dim[3], indx0, dcmList);
 		return; //we have subtracted min
 	}
 	//issue429: subtract min
@@ -5960,6 +6153,7 @@ void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose, char geVersi
 	// softwareVersionsGE
 	//	"27\LX\MR Software release:RX27.0_R02_1831.a" -> 27
 	//	"28\LX\MR29.1_EA_2039.g" -> 29
+	//  "30\LX\SIGNA_LX1.MR30.0_R01_2236.d" -> 30; see issue 634
 	// geVersionPrefix
 	//	RX27.0_R02_1831.a -> RX
 	//	MR29.1_EA_2039.g -> MR
@@ -5977,16 +6171,36 @@ void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose, char geVersi
 	//	RX27.0_R02_1831.a -> 2
 	//	MR29.1_EA_2039.g -> 0
 	int len = 0;
+	bool ismatched = false;
+	int substrlen = 0;
+
+	// If softwareVersionsGE is 30\LX\SIGNA_LX1.MR30.0_R01_2236.d; see issue 634
+	char *sepStart = strstr(softwareVersionsGE, "SIGNA_LX1");
+	if (ismatched == false) {
+		if (sepStart != NULL) {
+			ismatched = true;
+			substrlen = strlen("SIGNA_LX1");
+			sepStart += substrlen+1;
+		}
+	}
 	// 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
+	if (ismatched == false) {
+		sepStart = strstr(softwareVersionsGE, "MR Software release");
+		if (sepStart != NULL) {
+			ismatched = true;
+			substrlen = strlen("MR Software release");
+			sepStart += substrlen+1;
+		}
+	}
+	// If softwareVersionsGE is 28\LX\MR29.1_EA_2039.g
+	if (ismatched == false) {
 		sepStart = strrchr(softwareVersionsGE, '\\');
-		if (sepStart == NULL)
-			return;
+		if (sepStart != NULL) {
+			ismatched = true;
+			sepStart += 1;
+		}
 	}
-	sepStart += 1;
-	len = 11;
+	len = 11; // RX27.0_R02_
 	char *versionString = (char *)malloc(sizeof(char) * len);
 	versionString[len - 1] = 0;
 	memcpy(versionString, sepStart, len);
@@ -6002,6 +6216,7 @@ void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose, char geVersi
 	*geMajorVersion = (float)*geMajorVersionInt + (float)0.1 * (float)*geMinorVersionInt;
 	*is27r3 = ((*geMajorVersion >= 27.1) || ((*geMajorVersionInt == 27) && (*geReleaseVersionInt >= 3)));
 	if (verbose > 1) {
+		printMessage("GE Software VersionSting: %s\n", softwareVersionsGE);
 		printMessage("GE Software VersionPrefix: %s\n", geVersionPrefix);
 		printMessage("GE Software MajorVersion: %d\n", *geMajorVersionInt);
 		printMessage("GE Software MinorVersion: %d\n", *geMinorVersionInt);
@@ -6070,7 +6285,7 @@ void sliceTimingGE(struct TDICOMdata *d, const char *filename, struct TDCMopts o
 	//start version check:
 	float geMajorVersion = 0;
 	int geMajorVersionInt = 0, geMinorVersionInt = 0, geReleaseVersionInt = 0;
-	char geVersionPrefix[2] = " ";
+	char geVersionPrefix[3] = "";
 	bool is27r3 = false;
 	readSoftwareVersionsGE(d->softwareVersions, opts.isVerbose, geVersionPrefix, &geMajorVersion, &geMajorVersionInt, &geMinorVersionInt, &geReleaseVersionInt, &is27r3);
 	//readSoftwareVersionsGE(&geMajorVersion);
@@ -6110,7 +6325,7 @@ void sliceTimingGE(struct TDICOMdata *d, const char *filename, struct TDCMopts o
 	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);
+	bool isInterleaved = true;
 	groupDelay *= 1000.0; //sec -> ms
 	//
 	// Estimate GE Slice Time only for EPI Multi-Phase (epi) or EPI fMRI/BrainWave (epiRT)
@@ -6119,6 +6334,7 @@ void sliceTimingGE(struct TDICOMdata *d, const char *filename, struct TDCMopts o
 	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
+		isInterleaved = (sliceOrderGE != 0);
 		d->epiVersionGE = 1;
 		d->internalepiVersionGE = 1; // 'EPI'(gradient echo)/'EPI2'(spin echo)
 		if (!isSameFloatGE(groupDelay, d->groupDelay))
@@ -6139,11 +6355,34 @@ void sliceTimingGE(struct TDICOMdata *d, const char *filename, struct TDCMopts o
 			return;
 		}
 	}
-	// Diffusion (Unsupported)
+	// Diffusion (see issue 635)
 	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;
+		// diffusion gradient cycling OFF
+		if (opts.diffCyclingModeGE >= 0)
+			d->diffCyclingModeGE = opts.diffCyclingModeGE;
+		if ((d->diffCyclingModeGE == kGE_DIFF_CYCLING_SPOFF) || (d->diffCyclingModeGE == kGE_DIFF_CYCLING_OFF))
+			is27r3 = false;
+		else if (d->diffCyclingModeGE == kGE_DIFF_CYCLING_ALLTR) {
+			printWarning("Unable to compute slice times for GE Diffusion:Cycling\n");
+			d->CSA.sliceTiming[0] = -1.0;
+			return;
+		}
+		// TO DO: support 2TR/3TR cycling mode
+		else if (d->diffCyclingModeGE == kGE_DIFF_CYCLING_2TR) {
+			printWarning("Unable to compute slice times for GE Diffusion:2TR-Cycling\n");
+			d->CSA.sliceTiming[0] = -1.0;
+			return;
+		}
+		else if (d->diffCyclingModeGE == kGE_DIFF_CYCLING_3TR) {
+			printWarning("Unable to compute slice times for GE Diffusion:3TR-Cyclin\n");
+			d->CSA.sliceTiming[0] = -1.0;
+			return;
+		}
+		else {
+			printWarning("Unable to compute slice times for GE Diffusion\n");
+			d->CSA.sliceTiming[0] = -1.0;
+			return;
+		}
 	}
 	// Others (Unsupported)
 	else {
@@ -6473,6 +6712,10 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 		dti4D->frameDuration[0] = -1;
 		dti4D->frameReferenceTime[0] = -1;
 	}
+	if (strlen(dcmList[indx0].patientOrient) < 3)
+		printWarning("PatientOrient (0018,5100) not specified (issue 642).\n");
+	if (dcmList[indx0].isQuadruped)
+		printWarning("Anatomical Orientation Type (0010,2210) is QUADRUPED: rotate coordinates accordingly (issue 642)\n");
 #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);
@@ -6520,7 +6763,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 	mrifsStruct.tdicomData = dcmList[indx];  // first in sorted list dcmSort
 #endif 
 
-	struct nifti_1_header hdr0;
+	struct nifti_1_header hdr0 =  {0};
 	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++)
@@ -6542,6 +6785,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 
 	//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;
+	bool isDerived = dcmList[indx0].isDerived;
 	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;
@@ -6595,7 +6839,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 					}
 				}
 			}
-			if (nAcq != nSamePos) 
+			if ((nAcq != nSamePos) && (!isDerived)) //Siemens Derived FA-RGB images have bogus spatial data
 				printWarning("Expected %d volumes but found spatial position repeats %d times.\n", nAcq, nSamePos);
 			//end validate number of spatial volumes
 			if ((nAcq > 1) && ((nConvert / nAcq) > 1) && ((nConvert % nAcq) == 0)) {
@@ -6746,17 +6990,15 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 			} //if PET
 			//next: detect variable inter-slice distance
 			float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]);
-#ifdef myInstanceNumberOrderIsNotSpatial
+//#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
+//#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))
+				if (!isSameFloatT(dx, intersliceDistance(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]), kSliceTolerance))
 					dxVaries = true;
 			if (hdr0.dim[4] < 2) {
 				if (dxVaries) {
@@ -6804,7 +7046,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 						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))
+							if (!isSameFloatT(dx, intersliceDistance(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]), kSliceTolerance))
 								dxVaries = true;
 						for (int i = 1; i < nConvert; i++)
 							sliceMMarray[i] = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]);
@@ -7035,7 +7277,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 	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);
+		nii_mask12bit(imgM, &hdr0, dcmList[dcmSort[0].indx].isSigned);
 	if ((opts.saveFormat == kSaveFormatMGH) && (hdr0.datatype == DT_UINT16))
 		imgM = nii_uint16toFloat32(imgM, &hdr0, opts.isVerbose);
 	if ((opts.isMaximize16BitRange == kMaximize16BitRange_True) && (hdr0.datatype == DT_INT16)) {
@@ -7049,7 +7291,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 #ifndef USING_DCM2NIIXFSWRAPPER
 	printMessage("Convert %d DICOM as %s (%dx%dx%dx%d)\n", nConvert, pathoutname, hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4]);
 #else
-        printMessage( "Convert %d DICOM (%dx%dx%dx%d)\n",  nConvert, hdr0.dim[1],hdr0.dim[2],hdr0.dim[3],hdr0.dim[4]);
+	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
@@ -7101,6 +7343,20 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 		float c = cos(thetaRad);
 		if (!isSameFloatGE(c, 0.0)) {
 			mat33 shearMat;
+			//gantry tilt formula changed with issue697
+			/*printf("a=[%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1];\n",
+				hdr0.srow_x[0], hdr0.srow_x[1], hdr0.srow_x[2], hdr0.srow_x[3],
+				hdr0.srow_y[0], hdr0.srow_y[1], hdr0.srow_y[2], hdr0.srow_y[3],
+				hdr0.srow_z[0], hdr0.srow_z[1], hdr0.srow_z[2], hdr0.srow_z[3]
+			);*/
+			hdr0.srow_y[2] = 0.0; //remove gantry tilt
+			hdr0.srow_z[2] = hdr0.pixdim[3]; //retain distance between slices
+			/*printf("b=[%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1];\n",
+				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]
+			);*/
+			/*
 			LOAD_MAT33(shearMat, 1.0, 0.0, 0.0,
 					0.0, 1.0, sin(thetaRad) / c,
 					0.0, 0.0, 1.0);
@@ -7114,6 +7370,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 					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
@@ -7153,7 +7410,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 				char pathoutnameROI[2048] = {""};
 				strcat(pathoutnameROI, pathoutname);
 				char append[128] = {""};
-				sprintf(append, "_ROI%d", j + 1);
+				snprintf(append, 127, "_ROI%d", j + 1);
 				strcat(pathoutnameROI, append);
 				struct nifti_1_header hdrr = hdrrx;
 				hdrr.dim[0] = 3;
@@ -7252,6 +7509,27 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d
 } // saveDcm2NiiCore()
 
 int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D) {
+#ifdef USING_DCM2NIIXFSWRAPPER
+  if (opts.isDumpNotConvert) {
+    int indx0 = dcmSort[0].indx;
+    if (opts.isIgnoreSeriesInstanceUID)
+      printMessage("%d %s %s (total %d)\n", dcmList[indx0].seriesUidCrc, dcmList[indx0].protocolName, nameList->str[indx0], nConvert);
+    else
+      printMessage("%d %ld %s %s (total %d)\n", dcmList[indx0].seriesUidCrc, dcmList[indx0].seriesNum, dcmList[indx0].protocolName, nameList->str[indx0], nConvert);
+
+#if 1
+    for (int i = 0; i < nConvert; i++) {
+      int indx = dcmSort[i].indx;
+      if (opts.isIgnoreSeriesInstanceUID)
+	printMessage("\t#\%d: %d %s\n", i+1, dcmList[indx].seriesUidCrc, nameList->str[indx]);
+      else
+	printMessage("\t#\%d: %d %ld %s\n", i+1, dcmList[indx].seriesUidCrc, dcmList[indx].seriesNum, nameList->str[indx]);
+    }
+#endif
+
+    return 0;
+  }
+#endif
 	//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;
@@ -7515,7 +7793,7 @@ bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts
 	}
 	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
+			//Siemens B12/B13 users with a "DWI" but not "DTI" license would often create multi-series acquisitions
 			if (!warnings->forceStackSeries)
 				printMessage("Diffusion images stacked despite varying series number (early Siemens DTI).\n");
 			warnings->forceStackSeries = true;
@@ -7576,7 +7854,18 @@ bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts
 		//	*isMultiEcho = true;
 		//}
 #ifdef USING_DCM2NIIXFSWRAPPER
-		printf("isForceStackSameSeries = true, seriesNum %ld, %ld, seriesInstanceUidCrc %d, %d\n", d1.seriesNum, d2.seriesNum, d1.seriesUidCrc, d2.seriesUidCrc);
+               /* for mgh conversion set opts->isForceStackSameSeries = 1 by default, *isMultiEcho, *isNonParallelSlices, and *isCoilVaries remain unchanged.
+                *
+                * local variable isForceStackSeries is set to true if following condition met:
+	        *  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;
+                *  }
+                */
+
+	        //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
 	}
@@ -7586,6 +7875,20 @@ bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts
 		warnings->phaseVaries = true;
 		return false;
 	}
+	if (!(isSameFloat(d1.TR, d2.TR))) {
+		if (!warnings->echoVaries)
+		printMessage("Slices not stacked: TR varies (%g, %g, issue 641). Use 'merge 2D slices' option to force stacking\n", d1.TR, d2.TR);
+		*isMultiEcho = true;
+		warnings->echoVaries = true;
+		return false;
+	}
+	if (!(isSameFloat(d1.flipAngle, d2.flipAngle))) {
+		if (!warnings->echoVaries)
+		printMessage("Slices not stacked: flip angle varies (%g, %g, issue 646).\n", d1.TR, d2.TR);
+		*isMultiEcho = true;
+		warnings->echoVaries = 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)
@@ -8126,14 +8429,19 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) {
 	}
 	size_t nDcm = nameList.numItems;
 	printMessage("Found %lu DICOM file(s)\n", nameList.numItems); //includes images and other non-image DICOMs
+	if (opts->onlySearchDirForDICOM == 2) {
+		printMessage("List of DICOM file(s):\n");
+		for (int i = 0; i < nameList.numItems; i++)
+			printMessage("%s\n", nameList.str[i]);
+		printMessage("End of list (%lu in total)\n", nameList.numItems);
+	}
 #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
+		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;
@@ -8183,7 +8491,7 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) {
 		printMessage("Stage 2 (Read DICOM headers, Convert 4D) required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC);
 	start = clock();
 #endif
-	if (opts->isRenameNotConvert) {
+	if ((opts->isRenameNotConvert) || (opts->onlySearchDirForDICOM != 0)) {
 		free(dcmList);
 		free(dti4D);
 		return EXIT_SUCCESS;
@@ -8251,7 +8559,7 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) {
 						fillTDCMsort(dcmSort[nConvert], j, dcmList[j]);
 						nConvert++;
 					} else {
-						if (isNonParallelSlices) {
+					       if (isNonParallelSlices) {
 							dcmList[i].isNonParallelSlices = true;
 							dcmList[j].isNonParallelSlices = true;
 						}
@@ -8280,7 +8588,7 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) {
 				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
+#else //avoid bubble sort - do not 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
@@ -8320,6 +8628,8 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) {
 				nConvert++;
 			}
 		} //for all images with same seriesUID as first one
+
+                // MGH set Opts.isForceStackSameSeries = 1 by default, isMultiEcho, isNonParallelSlices, isCoilVaries remain false for MGH default run after isSameSet
 		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++) {
@@ -8596,27 +8906,44 @@ int findpathof(char *pth, const char *exe) {
 void readFindPigz(struct TDCMopts *opts, const char *argv[]) {
 #if defined(_WIN64) || defined(_WIN32)
 	strcpy(opts->pigzname, "pigz.exe");
+	if (is_exe(opts->pigzname)) 
+		return;
 	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
+		char exepth[PATH_MAX];
+		strcpy(exepth, argv[0]);
+		dropFilenameFromPath(exepth); //, opts.pigzname);
+		char appendChar[2] = {"a"};
+		appendChar[0] = kPathSeparator;
+		strcat(exepth, appendChar);
+		strcat(exepth, opts->pigzname);
+		strcpy(opts->pigzname, exepth);
+	}
+	if (is_exe(opts->pigzname)) 
+		return;
+	HMODULE hModule = GetModuleHandle(NULL);
+	if (hModule != NULL) {
+		// https://stackoverflow.com/questions/1528298/get-path-of-executable
+		char exepth[PATH_MAX];
+		GetModuleFileName(hModule, exepth, (sizeof(exepth))); 
+		dropFilenameFromPath(exepth); //, opts.pigzname);
+		char appendChar[2] = {"a"};
+		appendChar[0] = kPathSeparator;
+		strcat(exepth, appendChar);
+		strcpy(opts->pigzname, "pigz.exe");
+		strcat(exepth, opts->pigzname);
+		strcpy(opts->pigzname, exepth);
+	}
+	if (is_exe(opts->pigzname)) 
+		return;
 #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
+	strcpy(opts->pigzname, "");
+	return;
+#else //if windows else linux
 	char str[PATH_MAX];
 	//possible pigz names
 	const char *names[] = {
@@ -8718,11 +9045,13 @@ void setDefaultOpts(struct TDCMopts *opts, const char *argv[]) { //either "setDe
 	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->diffCyclingModeGE = -1;
 	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->onlySearchDirForDICOM = 0;
 	opts->isProgress = 0;
 	opts->nameConflictBehavior = kNAME_CONFLICT_ADD_SUFFIX;
 #ifdef myDisableZLib
@@ -8752,6 +9081,8 @@ void setDefaultOpts(struct TDCMopts *opts, const char *argv[]) { //either "setDe
 	opts->numSeries = 0;
 	memset(opts->seriesNumber, 0, sizeof(opts->seriesNumber));
 	strcpy(opts->filename, "%f_%p_%t_%s");
+
+        opts->isDumpNotConvert = false;
 } // setDefaultOpts()
 
 #if defined(_WIN64) || defined(_WIN32)
@@ -8802,11 +9133,11 @@ void readIniFile(struct TDCMopts *opts, const char *argv[]) {
 
 void readIniFile(struct TDCMopts *opts, const char *argv[]) {
 	setDefaultOpts(opts, argv);
-	sprintf(opts->optsname, "%s%s", getenv("HOME"), STATUSFILENAME);
+	snprintf(opts->optsname, kOptsStr, "%s%s", getenv("HOME"), STATUSFILENAME);
 	FILE *fp = fopen(opts->optsname, "r");
 	if (fp == NULL)
 		return;
-	char Setting[20], Value[255];
+	char Setting[255], 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) {
diff --git a/console/nii_dicom_batch.h b/console/nii_dicom_batch.h
index 80902c0..00ab73e 100644
--- a/console/nii_dicom_batch.h
+++ b/console/nii_dicom_batch.h
@@ -25,15 +25,15 @@ extern "C" {
 
 typedef struct 
 {
-    struct nifti_1_header hdr0;
+  struct nifti_1_header hdr0;
 
-    size_t         imgsz;
-    unsigned char *imgM;
+  size_t         imgsz;
+  unsigned char *imgM;
 
-    struct TDICOMdata tdicomData;
+  struct TDICOMdata tdicomData;
 
-    struct TDTI *tdti;
-    int numDti;
+  struct TDTI *tdti;
+  int numDti;
 } MRIFSSTRUCT;
 
 MRIFSSTRUCT* nii_getMrifsStruct();
@@ -54,11 +54,13 @@ void nii_clrMrifsStruct();
 #define kSaveFormatBNII 4
 
 #define MAX_NUM_SERIES 16
+#define kOptsStr 512
 
     struct TDCMopts {
+        bool isDumpNotConvert;
         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];
+        int saveFormat, isMaximize16BitRange, isForceStackSameSeries, nameConflictBehavior, isVerbose, isProgress, compressFlag, dirSearchDepth, onlySearchDirForDICOM, gzLevel, diffCyclingModeGE; //support for compressed data 0=none,
+        char filename[kOptsStr], outdir[kOptsStr], indir[kOptsStr], pigzname[kOptsStr], optsname[kOptsStr], indirParent[kOptsStr], 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
@@ -77,7 +79,7 @@ void nii_clrMrifsStruct();
     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);
+  int nii_loadDirCore(char *indir, struct TDCMopts* opts);
     void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename);
     int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopts opts);
     void  nii_createDummyFilename(char * niiFilename, struct TDCMopts opts);
diff --git a/console/nii_foreign.cpp b/console/nii_foreign.cpp
index 5547a58..8377160 100644
--- a/console/nii_foreign.cpp
+++ b/console/nii_foreign.cpp
@@ -399,7 +399,7 @@ unsigned char *readEcat7(const char *fname, struct TDICOMdata *dcm, struct nifti
 	}
 	dcm->manufacturer = kMANUFACTURER_SIEMENS;
 	//dcm->manufacturersModelName = itoa(mhdr.system_type);
-	sprintf(dcm->manufacturersModelName, "%d", mhdr.system_type);
+	snprintf(dcm->manufacturersModelName, kDICOMStr, "%d", mhdr.system_type);
 	dcm->bitsAllocated = bytesPerVoxel * 8;
 	if (isScaleFactorVaries)
 		dcm->isFloat = true;
diff --git a/console/ucm.cmake b/console/ucm.cmake
index e6e7fc8..37f025f 100644
--- a/console/ucm.cmake
+++ b/console/ucm.cmake
@@ -611,7 +611,7 @@ macro(ucm_add_target)
         # 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
+            # reset the folder property so all unity targets do not 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")
diff --git a/console/windows.bat b/console/windows.bat
new file mode 100644
index 0000000..acc1846
--- /dev/null
+++ b/console/windows.bat
@@ -0,0 +1,4 @@
+cl /wd4018 /wd4068 /wd4101 /wd4244 /wd4267 /wd4305 /wd4308 /wd4334 /wd4800 /wd4819 /wd4996  base64.cpp cJSON.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 /Fe:dcm2niix.exe -DmyDisableOpenJPEG /link /STACK:16388608
+rm *.exp
+rm *.lib
+rm *.obj
\ No newline at end of file
diff --git a/dcm2niix/__init__.py b/dcm2niix/__init__.py
new file mode 100644
index 0000000..8a4127a
--- /dev/null
+++ b/dcm2niix/__init__.py
@@ -0,0 +1,27 @@
+"""Thin wrapper around dcm2niix binary"""
+__author__ = "Casper da Costa-Luis <https://github.com/casperdcl>"
+__date__ = "2022"
+# version detector. Precedence: installed dist, git, 'UNKNOWN'
+try:
+    from ._dist_ver import __version__
+except ImportError: # pragma: nocover
+    try:
+        from setuptools_scm import get_version
+
+        __version__ = get_version(root="../..", relative_to=__file__)
+    except (ImportError, LookupError):
+        __version__ = "UNKNOWN"
+__all__ = ['bin', 'bin_path', 'main']
+
+from pathlib import Path
+
+bin_path = Path(__file__).resolve().parent / "dcm2niix"
+bin = str(bin_path)
+
+
+def main(args=None):
+    if args is None:
+        import sys
+        args = sys.argv[1:]
+    from subprocess import run
+    run([bin] + args)
diff --git a/dcm2niix/__main__.py b/dcm2niix/__main__.py
new file mode 100644
index 0000000..8273c4f
--- /dev/null
+++ b/dcm2niix/__main__.py
@@ -0,0 +1,3 @@
+from . import main
+
+main()
diff --git a/debian-tests-data/0001.dcm b/debian-tests-data/0001.dcm
deleted file mode 100644
index a8c31fe..0000000
Binary files a/debian-tests-data/0001.dcm and /dev/null differ
diff --git a/debian-tests-data/0002.dcm b/debian-tests-data/0002.dcm
deleted file mode 100644
index bbdeacc..0000000
Binary files a/debian-tests-data/0002.dcm and /dev/null differ
diff --git a/debian-tests-data/0003.dcm b/debian-tests-data/0003.dcm
deleted file mode 100644
index 6115c04..0000000
Binary files a/debian-tests-data/0003.dcm and /dev/null differ
diff --git a/debian-tests-data/0004.dcm b/debian-tests-data/0004.dcm
deleted file mode 100644
index 5e04263..0000000
Binary files a/debian-tests-data/0004.dcm and /dev/null differ
diff --git a/debian-tests-data/0005.dcm b/debian-tests-data/0005.dcm
deleted file mode 100644
index 4fc1b85..0000000
Binary files a/debian-tests-data/0005.dcm and /dev/null differ
diff --git a/debian-tests-data/0006.dcm b/debian-tests-data/0006.dcm
deleted file mode 100644
index ee793b1..0000000
Binary files a/debian-tests-data/0006.dcm and /dev/null differ
diff --git a/debian-tests-data/0007.dcm b/debian-tests-data/0007.dcm
deleted file mode 100644
index 65136ec..0000000
Binary files a/debian-tests-data/0007.dcm and /dev/null differ
diff --git a/debian-tests-data/0008.dcm b/debian-tests-data/0008.dcm
deleted file mode 100644
index a9592bf..0000000
Binary files a/debian-tests-data/0008.dcm and /dev/null differ
diff --git a/debian-tests-data/0009.dcm b/debian-tests-data/0009.dcm
deleted file mode 100644
index 5ddf7c8..0000000
Binary files a/debian-tests-data/0009.dcm and /dev/null differ
diff --git a/debian-tests-data/0010.dcm b/debian-tests-data/0010.dcm
deleted file mode 100644
index 362c8a4..0000000
Binary files a/debian-tests-data/0010.dcm and /dev/null differ
diff --git a/debian-tests-data/0011.dcm b/debian-tests-data/0011.dcm
deleted file mode 100644
index 0175959..0000000
Binary files a/debian-tests-data/0011.dcm and /dev/null differ
diff --git a/debian-tests-data/0012.dcm b/debian-tests-data/0012.dcm
deleted file mode 100644
index 3933658..0000000
Binary files a/debian-tests-data/0012.dcm and /dev/null differ
diff --git a/debian-tests-data/0013.dcm b/debian-tests-data/0013.dcm
deleted file mode 100644
index f809b0c..0000000
Binary files a/debian-tests-data/0013.dcm and /dev/null differ
diff --git a/debian-tests-data/0014.dcm b/debian-tests-data/0014.dcm
deleted file mode 100644
index dd25267..0000000
Binary files a/debian-tests-data/0014.dcm and /dev/null differ
diff --git a/debian-tests-data/0015.dcm b/debian-tests-data/0015.dcm
deleted file mode 100644
index 77119eb..0000000
Binary files a/debian-tests-data/0015.dcm and /dev/null differ
diff --git a/debian-tests-data/0016.dcm b/debian-tests-data/0016.dcm
deleted file mode 100644
index 0f33f43..0000000
Binary files a/debian-tests-data/0016.dcm and /dev/null differ
diff --git a/debian-tests-data/0017.dcm b/debian-tests-data/0017.dcm
deleted file mode 100644
index 9199b60..0000000
Binary files a/debian-tests-data/0017.dcm and /dev/null differ
diff --git a/debian-tests-data/0018.dcm b/debian-tests-data/0018.dcm
deleted file mode 100644
index 5f71383..0000000
Binary files a/debian-tests-data/0018.dcm and /dev/null differ
diff --git a/debian-tests-data/0019.dcm b/debian-tests-data/0019.dcm
deleted file mode 100644
index 30fb7bd..0000000
Binary files a/debian-tests-data/0019.dcm and /dev/null differ
diff --git a/debian-tests-data/0020.dcm b/debian-tests-data/0020.dcm
deleted file mode 100644
index 88346f6..0000000
Binary files a/debian-tests-data/0020.dcm and /dev/null differ
diff --git a/debian-tests-data/0021.dcm b/debian-tests-data/0021.dcm
deleted file mode 100644
index 9169e4f..0000000
Binary files a/debian-tests-data/0021.dcm and /dev/null differ
diff --git a/debian-tests-data/0022.dcm b/debian-tests-data/0022.dcm
deleted file mode 100644
index aa55cdb..0000000
Binary files a/debian-tests-data/0022.dcm and /dev/null differ
diff --git a/debian-tests-data/0023.dcm b/debian-tests-data/0023.dcm
deleted file mode 100644
index c0e8591..0000000
Binary files a/debian-tests-data/0023.dcm and /dev/null differ
diff --git a/debian-tests-data/0024.dcm b/debian-tests-data/0024.dcm
deleted file mode 100644
index 554fc28..0000000
Binary files a/debian-tests-data/0024.dcm and /dev/null differ
diff --git a/debian-tests-data/0025.dcm b/debian-tests-data/0025.dcm
deleted file mode 100644
index fb793af..0000000
Binary files a/debian-tests-data/0025.dcm and /dev/null differ
diff --git a/debian-tests-data/0026.dcm b/debian-tests-data/0026.dcm
deleted file mode 100644
index 4438ef6..0000000
Binary files a/debian-tests-data/0026.dcm and /dev/null differ
diff --git a/debian-tests-data/0027.dcm b/debian-tests-data/0027.dcm
deleted file mode 100644
index 47040b8..0000000
Binary files a/debian-tests-data/0027.dcm and /dev/null differ
diff --git a/debian-tests-data/0028.dcm b/debian-tests-data/0028.dcm
deleted file mode 100644
index 43785e4..0000000
Binary files a/debian-tests-data/0028.dcm and /dev/null differ
diff --git a/debian-tests-data/0029.dcm b/debian-tests-data/0029.dcm
deleted file mode 100644
index 6dd949d..0000000
Binary files a/debian-tests-data/0029.dcm and /dev/null differ
diff --git a/debian-tests-data/0030.dcm b/debian-tests-data/0030.dcm
deleted file mode 100644
index d96309a..0000000
Binary files a/debian-tests-data/0030.dcm and /dev/null differ
diff --git a/debian-tests-data/0031.dcm b/debian-tests-data/0031.dcm
deleted file mode 100644
index 926dd03..0000000
Binary files a/debian-tests-data/0031.dcm and /dev/null differ
diff --git a/debian-tests-data/0032.dcm b/debian-tests-data/0032.dcm
deleted file mode 100644
index 56ecd61..0000000
Binary files a/debian-tests-data/0032.dcm and /dev/null differ
diff --git a/debian-tests-data/0033.dcm b/debian-tests-data/0033.dcm
deleted file mode 100644
index 56880a4..0000000
Binary files a/debian-tests-data/0033.dcm and /dev/null differ
diff --git a/debian-tests-data/0034.dcm b/debian-tests-data/0034.dcm
deleted file mode 100644
index afaaf04..0000000
Binary files a/debian-tests-data/0034.dcm and /dev/null differ
diff --git a/debian-tests-data/0035.dcm b/debian-tests-data/0035.dcm
deleted file mode 100644
index e442729..0000000
Binary files a/debian-tests-data/0035.dcm and /dev/null differ
diff --git a/debian-tests-data/0036.dcm b/debian-tests-data/0036.dcm
deleted file mode 100644
index d465b70..0000000
Binary files a/debian-tests-data/0036.dcm and /dev/null differ
diff --git a/debian-tests-data/0037.dcm b/debian-tests-data/0037.dcm
deleted file mode 100644
index 15efdfb..0000000
Binary files a/debian-tests-data/0037.dcm and /dev/null differ
diff --git a/debian-tests-data/0038.dcm b/debian-tests-data/0038.dcm
deleted file mode 100644
index d0beadc..0000000
Binary files a/debian-tests-data/0038.dcm and /dev/null differ
diff --git a/debian-tests-data/0039.dcm b/debian-tests-data/0039.dcm
deleted file mode 100644
index 63e3ade..0000000
Binary files a/debian-tests-data/0039.dcm and /dev/null differ
diff --git a/debian-tests-data/0040.dcm b/debian-tests-data/0040.dcm
deleted file mode 100644
index 319a6d6..0000000
Binary files a/debian-tests-data/0040.dcm and /dev/null differ
diff --git a/debian-tests-data/0041.dcm b/debian-tests-data/0041.dcm
deleted file mode 100644
index 33f8c2f..0000000
Binary files a/debian-tests-data/0041.dcm and /dev/null differ
diff --git a/debian-tests-data/0042.dcm b/debian-tests-data/0042.dcm
deleted file mode 100644
index 84058b1..0000000
Binary files a/debian-tests-data/0042.dcm and /dev/null differ
diff --git a/debian-tests-data/0043.dcm b/debian-tests-data/0043.dcm
deleted file mode 100644
index 8e8fdbd..0000000
Binary files a/debian-tests-data/0043.dcm and /dev/null differ
diff --git a/debian-tests-data/0044.dcm b/debian-tests-data/0044.dcm
deleted file mode 100644
index 58a060c..0000000
Binary files a/debian-tests-data/0044.dcm and /dev/null differ
diff --git a/debian-tests-data/0045.dcm b/debian-tests-data/0045.dcm
deleted file mode 100644
index cfdca94..0000000
Binary files a/debian-tests-data/0045.dcm and /dev/null differ
diff --git a/debian-tests-data/0046.dcm b/debian-tests-data/0046.dcm
deleted file mode 100644
index c61e06a..0000000
Binary files a/debian-tests-data/0046.dcm and /dev/null differ
diff --git a/debian-tests-data/0047.dcm b/debian-tests-data/0047.dcm
deleted file mode 100644
index 23a9569..0000000
Binary files a/debian-tests-data/0047.dcm and /dev/null differ
diff --git a/debian-tests-data/0048.dcm b/debian-tests-data/0048.dcm
deleted file mode 100644
index 360743f..0000000
Binary files a/debian-tests-data/0048.dcm and /dev/null differ
diff --git a/debian-tests-data/0049.dcm b/debian-tests-data/0049.dcm
deleted file mode 100644
index 19e739f..0000000
Binary files a/debian-tests-data/0049.dcm and /dev/null differ
diff --git a/debian-tests-data/0050.dcm b/debian-tests-data/0050.dcm
deleted file mode 100644
index ff86b69..0000000
Binary files a/debian-tests-data/0050.dcm and /dev/null differ
diff --git a/debian-tests-data/0051.dcm b/debian-tests-data/0051.dcm
deleted file mode 100644
index 9e302ba..0000000
Binary files a/debian-tests-data/0051.dcm and /dev/null differ
diff --git a/debian-tests-data/0052.dcm b/debian-tests-data/0052.dcm
deleted file mode 100644
index d56c5c0..0000000
Binary files a/debian-tests-data/0052.dcm and /dev/null differ
diff --git a/debian-tests-data/0053.dcm b/debian-tests-data/0053.dcm
deleted file mode 100644
index cb4ce4f..0000000
Binary files a/debian-tests-data/0053.dcm and /dev/null differ
diff --git a/debian-tests-data/0054.dcm b/debian-tests-data/0054.dcm
deleted file mode 100644
index 435fb3e..0000000
Binary files a/debian-tests-data/0054.dcm and /dev/null differ
diff --git a/debian-tests-data/0055.dcm b/debian-tests-data/0055.dcm
deleted file mode 100644
index b32fc38..0000000
Binary files a/debian-tests-data/0055.dcm and /dev/null differ
diff --git a/debian-tests-data/0056.dcm b/debian-tests-data/0056.dcm
deleted file mode 100644
index 259dde6..0000000
Binary files a/debian-tests-data/0056.dcm and /dev/null differ
diff --git a/debian-tests-data/0057.dcm b/debian-tests-data/0057.dcm
deleted file mode 100644
index 0c28693..0000000
Binary files a/debian-tests-data/0057.dcm and /dev/null differ
diff --git a/debian-tests-data/0058.dcm b/debian-tests-data/0058.dcm
deleted file mode 100644
index 1cfa45c..0000000
Binary files a/debian-tests-data/0058.dcm and /dev/null differ
diff --git a/debian-tests-data/0059.dcm b/debian-tests-data/0059.dcm
deleted file mode 100644
index e002118..0000000
Binary files a/debian-tests-data/0059.dcm and /dev/null differ
diff --git a/debian-tests-data/0060.dcm b/debian-tests-data/0060.dcm
deleted file mode 100644
index 8d8aa3a..0000000
Binary files a/debian-tests-data/0060.dcm and /dev/null differ
diff --git a/debian-tests-data/0061.dcm b/debian-tests-data/0061.dcm
deleted file mode 100644
index 5a58697..0000000
Binary files a/debian-tests-data/0061.dcm and /dev/null differ
diff --git a/debian-tests-data/0062.dcm b/debian-tests-data/0062.dcm
deleted file mode 100644
index b6a622f..0000000
Binary files a/debian-tests-data/0062.dcm and /dev/null differ
diff --git a/debian-tests-data/0063.dcm b/debian-tests-data/0063.dcm
deleted file mode 100644
index 0ad43f3..0000000
Binary files a/debian-tests-data/0063.dcm and /dev/null differ
diff --git a/debian-tests-data/0064.dcm b/debian-tests-data/0064.dcm
deleted file mode 100644
index ffd1428..0000000
Binary files a/debian-tests-data/0064.dcm and /dev/null differ
diff --git a/debian-tests-data/0065.dcm b/debian-tests-data/0065.dcm
deleted file mode 100644
index 163cf64..0000000
Binary files a/debian-tests-data/0065.dcm and /dev/null differ
diff --git a/debian-tests-data/0066.dcm b/debian-tests-data/0066.dcm
deleted file mode 100644
index d181ad3..0000000
Binary files a/debian-tests-data/0066.dcm and /dev/null differ
diff --git a/debian-tests-data/0067.dcm b/debian-tests-data/0067.dcm
deleted file mode 100644
index 41db493..0000000
Binary files a/debian-tests-data/0067.dcm and /dev/null differ
diff --git a/debian-tests-data/0068.dcm b/debian-tests-data/0068.dcm
deleted file mode 100644
index 30f9f3e..0000000
Binary files a/debian-tests-data/0068.dcm and /dev/null differ
diff --git a/debian-tests-data/0069.dcm b/debian-tests-data/0069.dcm
deleted file mode 100644
index 83c187a..0000000
Binary files a/debian-tests-data/0069.dcm and /dev/null differ
diff --git a/debian-tests-data/0070.dcm b/debian-tests-data/0070.dcm
deleted file mode 100644
index a661f57..0000000
Binary files a/debian-tests-data/0070.dcm and /dev/null differ
diff --git a/debian-tests-data/0071.dcm b/debian-tests-data/0071.dcm
deleted file mode 100644
index f9ee7d6..0000000
Binary files a/debian-tests-data/0071.dcm and /dev/null differ
diff --git a/debian-tests-data/0072.dcm b/debian-tests-data/0072.dcm
deleted file mode 100644
index c9e3001..0000000
Binary files a/debian-tests-data/0072.dcm and /dev/null differ
diff --git a/debian-tests-data/0073.dcm b/debian-tests-data/0073.dcm
deleted file mode 100644
index 01b76d0..0000000
Binary files a/debian-tests-data/0073.dcm and /dev/null differ
diff --git a/debian-tests-data/0074.dcm b/debian-tests-data/0074.dcm
deleted file mode 100644
index 74c51e3..0000000
Binary files a/debian-tests-data/0074.dcm and /dev/null differ
diff --git a/debian-tests-data/0075.dcm b/debian-tests-data/0075.dcm
deleted file mode 100644
index 7ff4771..0000000
Binary files a/debian-tests-data/0075.dcm and /dev/null differ
diff --git a/debian-tests-data/0076.dcm b/debian-tests-data/0076.dcm
deleted file mode 100644
index 89045d3..0000000
Binary files a/debian-tests-data/0076.dcm and /dev/null differ
diff --git a/debian-tests-data/0077.dcm b/debian-tests-data/0077.dcm
deleted file mode 100644
index f71d6a5..0000000
Binary files a/debian-tests-data/0077.dcm and /dev/null differ
diff --git a/debian-tests-data/0078.dcm b/debian-tests-data/0078.dcm
deleted file mode 100644
index c4f4ff8..0000000
Binary files a/debian-tests-data/0078.dcm and /dev/null differ
diff --git a/debian-tests-data/0079.dcm b/debian-tests-data/0079.dcm
deleted file mode 100644
index bfd8586..0000000
Binary files a/debian-tests-data/0079.dcm and /dev/null differ
diff --git a/debian-tests-data/0080.dcm b/debian-tests-data/0080.dcm
deleted file mode 100644
index 13e9a3b..0000000
Binary files a/debian-tests-data/0080.dcm and /dev/null differ
diff --git a/debian-tests-data/0081.dcm b/debian-tests-data/0081.dcm
deleted file mode 100644
index 41a0f69..0000000
Binary files a/debian-tests-data/0081.dcm and /dev/null differ
diff --git a/debian-tests-data/0082.dcm b/debian-tests-data/0082.dcm
deleted file mode 100644
index 131c800..0000000
Binary files a/debian-tests-data/0082.dcm and /dev/null differ
diff --git a/debian-tests-data/0083.dcm b/debian-tests-data/0083.dcm
deleted file mode 100644
index e4ddb2f..0000000
Binary files a/debian-tests-data/0083.dcm and /dev/null differ
diff --git a/debian-tests-data/0084.dcm b/debian-tests-data/0084.dcm
deleted file mode 100644
index f545bf2..0000000
Binary files a/debian-tests-data/0084.dcm and /dev/null differ
diff --git a/debian-tests-data/0085.dcm b/debian-tests-data/0085.dcm
deleted file mode 100644
index f4ce477..0000000
Binary files a/debian-tests-data/0085.dcm and /dev/null differ
diff --git a/debian/changelog b/debian/changelog
index 7cfd81d..6c4f7e3 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+dcm2niix (1.0.20230411-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 15 May 2023 02:14:49 -0000
+
 dcm2niix (1.0.20220720-1) unstable; urgency=medium
 
   [ Andreas Tille ]
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..5c9a063
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,7 @@
+[build-system]
+requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4",
+            "scikit-build>=0.11.0", "cmake>=3.18", "ninja"]
+
+[tool.setuptools_scm]
+write_to = "dcm2niix/_dist_ver.py"
+write_to_template = "__version__ = '{version}'\n"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..785be9d
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,41 @@
+[metadata]
+name=dcm2niix
+description=DCM2NIIX Python package
+long_description=file: README.md
+long_description_content_type=text/markdown
+license_file=license.txt
+url=https://github.com/rordenlab/dcm2niix
+project_urls=
+    Changelog=https://github.com/rordenlab/dcm2niix/releases
+    Documentation=https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage
+author=Li X, Morgan PS, Ashburner J, Smith J, Rorden C
+maintainer=Casper da Costa-Luis
+maintainer_email=imaging@cdcl.ml
+keywords=research, jpeg, dicom, neuroscience, mri, neuroimaging, nifti, dcm, nii, nitrc, bids, dcm2niix, mricrogl
+classifiers=
+    Development Status :: 5 - Production/Stable
+    Intended Audience :: Education
+    Intended Audience :: Healthcare Industry
+    Intended Audience :: Science/Research
+    Operating System :: Microsoft :: Windows
+    Operating System :: POSIX :: Linux
+    Programming Language :: C++
+    Programming Language :: Python :: 3
+    Programming Language :: Python :: 3.6
+    Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3 :: Only
+    Topic :: Scientific/Engineering :: Medical Science Apps.
+[options]
+setup_requires=
+    setuptools>=42
+    wheel
+    setuptools_scm[toml]
+    scikit-build>=0.11.0
+    cmake>=3.18
+    ninja
+python_requires=>=3.6
+[options.entry_points]
+console_scripts=
+    dcm2niix=dcm2niix:main
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..146f7a0
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,13 @@
+"""Compile source code and setup Python 3 package"""
+import re
+from pathlib import Path
+
+from setuptools_scm import get_version
+from skbuild import setup
+
+__version__ = get_version(root=".", relative_to=__file__)
+build_ver = ".".join(__version__.split(".")[:3]).split(".dev")[0]
+for i in (Path(__file__).resolve().parent / "_skbuild").rglob("CMakeCache.txt"):
+    i.write_text(re.sub("^//.*$\n^[^#].*pip-build-env.*$", "", i.read_text(), flags=re.M))
+setup(use_scm_version=True, packages=["dcm2niix"],
+      cmake_languages=("CXX",), cmake_minimum_required_version="3.18")

More details

Full run details

Historical runs