// #define MY_DEBUG
#if defined(_WIN64) || defined(_WIN32)
#define NOMINMAX
#include <windows.h> //write to registry
#endif
#ifdef _MSC_VER
#include <direct.h>
#define getcwd _getcwd
#define chdir _chrdir
#include "io.h"
#include <math.h>
// #define snprintf _snprintf
// #define vsnprintf _vsnprintf
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
#ifdef _WIN32
#pragma comment(lib, "advapi32")
#endif
#else
#include <unistd.h>
#endif
// #include <time.h> //clock()
#ifndef USING_R
#include "nifti1.h"
#endif
#include "jpg_0XC3.h"
#include "nifti1_io_core.h"
#include "nii_dicom.h"
#include "print.h"
#include <ctype.h> //toupper
#include <float.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // discriminate files from folders
#include <sys/types.h>
#ifndef USING_DCM2NIIXFSWRAPPER
#include <algorithm>
#endif

#ifdef USING_R
#undef isnan
#define isnan ISNAN
#endif

#ifndef myDisableClassicJPEG
#ifdef myTurboJPEG
#include <turbojpeg.h>
#else
#include "ujpeg.h"
#endif
#endif
#ifdef myEnableJasper
#include <jasper/jasper.h>
#endif
#ifndef myDisableOpenJPEG
#include "openjpeg.h"

#ifdef myEnableJasper
ERROR : YOU CAN NOT COMPILE WITH myEnableJasper AND NOT myDisableOpenJPEG OPTIONS SET SIMULTANEOUSLY
#endif

		unsigned char *
		imagetoimg(opj_image_t *image) {
	int numcmpts = image->numcomps;
	int sgnd = image->comps[0].sgnd;
	int width = image->comps[0].w;
	int height = image->comps[0].h;
	int bpp = (image->comps[0].prec + 7) >> 3; // e.g. 12 bits requires 2 bytes
	int imgbytes = bpp * width * height * numcmpts;
	bool isOK = true;
	if (numcmpts > 1) {
		for (int comp = 1; comp < numcmpts; comp++) { // check RGB data
			if (image->comps[0].w != image->comps[comp].w)
				isOK = false;
			if (image->comps[0].h != image->comps[comp].h)
				isOK = false;
			if (image->comps[0].dx != image->comps[comp].dx)
				isOK = false;
			if (image->comps[0].dy != image->comps[comp].dy)
				isOK = false;
			if (image->comps[0].prec != image->comps[comp].prec)
				isOK = false;
			if (image->comps[0].sgnd != image->comps[comp].sgnd)
				isOK = false;
		}
		if (numcmpts != 3)
			isOK = false; // we only handle Gray and RedGreenBlue, not GrayAlpha or RedGreenBlueAlpha
		if (image->comps[0].prec != 8)
			isOK = false; // only 8-bit for RGB data
	}
	if ((image->comps[0].prec < 1) || (image->comps[0].prec > 16))
		isOK = false; // currently we only handle 1 and 2 byte data
	if (!isOK) {
		printMessage("jpeg decode failure w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version());
		return NULL;
	}
#ifdef MY_DEBUG
	printMessage("w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version());
#endif
	// extract the data
	if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) {
		printError("Catastrophic decompression error\n");
		return NULL;
	}
	unsigned char *img = (unsigned char *)malloc(imgbytes);
	uint16_t *img16ui = (uint16_t *)img; // unsigned 16-bit
	int16_t *img16i = (int16_t *)img;	 // signed 16-bit
	if (sgnd)
		bpp = -bpp;
	if (bpp == -1) {
		free(img);
		printError("Signed 8-bit DICOM?\n");
		return NULL;
	}
	// n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB
	int pix = 0; // output pixel
	for (int cmptno = 0; cmptno < numcmpts; ++cmptno) {
		int cpix = 0; // component pixel
		int *v = image->comps[cmptno].data;
		for (int y = 0; y < height; ++y) {
			for (int x = 0; x < width; ++x) {
				switch (bpp) {
				case 1:
					img[pix] = (unsigned char)v[cpix];
					break;
				case 2:
					img16ui[pix] = (uint16_t)v[cpix];
					break;
				case -2:
					img16i[pix] = (int16_t)v[cpix];
					break;
				}
				pix++;
				cpix++;
			} // for x
		} // for y
	} // for each component
	return img;
} // imagetoimg()

typedef struct bufinfo {
	unsigned char *buf;
	unsigned char *cur;
	size_t len;
} BufInfo;

static void my_stream_free(void *p_user_data) { // do nothing
												// BufInfo d = (BufInfo) p_user_data;
												// free(d.buf);
} // my_stream_free()

static OPJ_UINT32 opj_read_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) {
	OPJ_UINT32 l_nb_read;
	if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) {
		l_nb_read = p_nb_bytes;
	} else {
		l_nb_read = (OPJ_UINT32)(p_file->buf + p_file->len - p_file->cur);
	}
	memcpy(p_buffer, p_file->cur, l_nb_read);
	p_file->cur += l_nb_read;

	return l_nb_read ? l_nb_read : ((OPJ_UINT32)-1);
} // opj_read_from_buffer()

static OPJ_UINT32 opj_write_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) {
	memcpy(p_file->cur, p_buffer, p_nb_bytes);
	p_file->cur += p_nb_bytes;
	p_file->len += p_nb_bytes;
	return p_nb_bytes;
} // opj_write_from_buffer()

static OPJ_SIZE_T opj_skip_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) {
	if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) {
		p_file->cur += p_nb_bytes;
		return p_nb_bytes;
	}
	p_file->cur = p_file->buf + p_file->len;
	return (OPJ_SIZE_T)-1;
} // opj_skip_from_buffer()

// fix for https://github.com/neurolabusc/dcm_qa/issues/5
static OPJ_BOOL opj_seek_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) {
	// printf("opj_seek_from_buffer %d + %d -> %d + %d\n", p_file->cur , p_nb_bytes, p_file->buf, p_file->len);
	if (p_nb_bytes < p_file->len) {
		p_file->cur = p_file->buf + p_nb_bytes;
		return OPJ_TRUE;
	}
	p_file->cur = p_file->buf + p_file->len;
	return OPJ_FALSE;
} // opj_seek_from_buffer()

opj_stream_t *opj_stream_create_buffer_stream(BufInfo *p_file, OPJ_UINT32 p_size, OPJ_BOOL p_is_read_stream) {
	opj_stream_t *l_stream;
	if (!p_file)
		return NULL;
	l_stream = opj_stream_create(p_size, p_is_read_stream);
	if (!l_stream)
		return NULL;
	opj_stream_set_user_data(l_stream, p_file, my_stream_free);
	opj_stream_set_user_data_length(l_stream, p_file->len);
	opj_stream_set_read_function(l_stream, (opj_stream_read_fn)opj_read_from_buffer);
	opj_stream_set_write_function(l_stream, (opj_stream_write_fn)opj_write_from_buffer);
	opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn)opj_skip_from_buffer);
	opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn)opj_seek_from_buffer);
	return l_stream;
} // opj_stream_create_buffer_stream()

unsigned char *nii_loadImgCoreOpenJPEG(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) {
	// OpenJPEG library is not well documented and has changed between versions
	// Since the JPEG is embedded in a DICOM we need to skip bytes at the start of the file
	//  In theory we might also want to strip data that exists AFTER the image, see gdcmJPEG2000Codec.c
	unsigned char *ret = NULL;
	opj_dparameters_t params;
	opj_codec_t *codec;
	opj_image_t *jpx;
	opj_stream_t *stream;
	FILE *reader = fopen(imgname, "rb");
	fseek(reader, 0, SEEK_END);
	long size = ftell(reader) - dcm.imageStart;
	if (size <= 8)
		return NULL;
	fseek(reader, dcm.imageStart, SEEK_SET);
	unsigned char *data = (unsigned char *)malloc(size);
	size_t sz = fread(data, 1, size, reader);
	fclose(reader);
	if (sz < size)
		return NULL;
	OPJ_CODEC_FORMAT format = OPJ_CODEC_JP2;
	// DICOM JPEG2k is SUPPOSED to start with codestream, but some vendors include a header
	if (data[0] == 0xFF && data[1] == 0x4F && data[2] == 0xFF && data[3] == 0x51)
		format = OPJ_CODEC_J2K;
	opj_set_default_decoder_parameters(&params);
	BufInfo dx;
	dx.buf = data;
	dx.cur = data;
	dx.len = size;
	stream = opj_stream_create_buffer_stream(&dx, (OPJ_UINT32)size, true);
	if (stream == NULL)
		return NULL;
	codec = opj_create_decompress(format);
	// setup the decoder decoding parameters using user parameters
	if (!opj_setup_decoder(codec, &params))
		goto cleanup2;
	// Read the main header of the codestream and if necessary the JP2 boxes
	if (!opj_read_header(stream, codec, &jpx)) {
		printError("OpenJPEG failed to read the header %s (offset %d)\n", imgname, dcm.imageStart);
// comment these next lines to abort: include these to create zero-padded slice
#ifdef MY_ZEROFILLBROKENJPGS
		// fix broken slices https://github.com/scitran-apps/dcm2niix/issues/4
		printError("Zero-filled slice created\n");
		int imgbytes = (hdr.bitpix / 8) * hdr.dim[1] * hdr.dim[2];
		ret = (unsigned char *)calloc(imgbytes, 1);
#endif
		goto cleanup2;
	}
	// Get the decoded image
	if (!(opj_decode(codec, stream, jpx) && opj_end_decompress(codec, stream))) {
		printError("OpenJPEG j2k_to_image failed to decode %s\n", imgname);
		goto cleanup1;
	}
	ret = imagetoimg(jpx);
cleanup1:
	opj_image_destroy(jpx);
cleanup2:
	free(dx.buf);
	opj_stream_destroy(stream);
	opj_destroy_codec(codec);
	return ret;
}
#endif // myDisableOpenJPEG

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

float deFuzz(float v) {
	if (fabs(v) < 0.00001)
		return 0;
	else
		return v;
}

#ifdef MY_DEBUG
void reportMat33(char *str, mat33 A) {
	printMessage("%s = [%g %g %g ; %g %g %g; %g %g %g ]\n", str,
				 deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]),
				 deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]),
				 deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2]));
}

void reportMat44(char *str, mat44 A) {
	// example: reportMat44((char*)"out",*R);
	printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n", str,
				 deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]), deFuzz(A.m[0][3]),
				 deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]), deFuzz(A.m[1][3]),
				 deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2]), deFuzz(A.m[2][3]));
}
#endif

int verify_slice_dir(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, mat44 *R, int isVerbose) {
	// returns slice direction: 1=sag,2=coronal,3=axial, -= flipped
	if (h->dim[3] < 2)
		return 0; // don't care direction for single slice
	int iSL = 1;  // find Z-slice direction: row with highest magnitude of 3rd column
	if ((fabs(R->m[1][2]) >= fabs(R->m[0][2])) && (fabs(R->m[1][2]) >= fabs(R->m[2][2])))
		iSL = 2; //
	if ((fabs(R->m[2][2]) >= fabs(R->m[0][2])) && (fabs(R->m[2][2]) >= fabs(R->m[1][2])))
		iSL = 3; // axial acquisition
	float pos = NAN;
	if (!isnan(d2.patientPosition[iSL])) { // patient position fields exist
		pos = d2.patientPosition[iSL];
		if (isSameFloat(pos, d.patientPosition[iSL]))
			pos = NAN;
#ifdef MY_DEBUG
		if (!isnan(pos))
			printMessage("position determined using lastFile %f\n", pos);
#endif
	}
	if (isnan(pos) && (!isnan(d.patientPositionLast[iSL]))) { // patient position fields exist
		pos = d.patientPositionLast[iSL];
		if (isSameFloat(pos, d.patientPosition[iSL]))
			pos = NAN;
#ifdef MY_DEBUG
		if (!isnan(pos))
			printMessage("position determined using last (4d) %f\n", pos);
#endif
	}
	if (isnan(pos) && (!isnan(d.stackOffcentre[iSL])))
		pos = d.stackOffcentre[iSL];
	if (isnan(pos) && (!isnan(d.lastScanLoc)))
		pos = d.lastScanLoc;
	vec4 x;
	x.v[0] = 0.0;
	x.v[1] = 0.0;
	x.v[2] = (float)(h->dim[3] - 1.0);
	x.v[3] = 1.0;
	vec4 pos1v = nifti_vect44mat44_mul(x, *R);
	float pos1 = pos1v.v[iSL - 1]; //-1 as C indexed from 0
	bool flip = false;
	if (!isnan(pos))												  // we have real SliceLocation for last slice or volume center
		flip = (pos > R->m[iSL - 1][3]) != (pos1 > R->m[iSL - 1][3]); // same direction?, note C indices from 0
	else {															  // we do some guess work and warn user
		vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]);
		vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]);
		// printMessage("rd %g %g %g\n",readV.v[0],readV.v[1],readV.v[2]);
		// printMessage("ph %g %g %g\n",phaseV.v[0],phaseV.v[1],phaseV.v[2]);
		vec3 sliceV = crossProduct(readV, phaseV); // order important: this is our hail mary
		flip = ((sliceV.v[0] + sliceV.v[1] + sliceV.v[2]) < 0);
		// printMessage("verify slice dir %g %g %g\n",sliceV.v[0],sliceV.v[1],sliceV.v[2]);
		if (isVerbose) {		// 1st pass only
			if (!d.isDerived) { // do not warn user if image is derived
				printWarning("Unable to determine slice direction: please check whether slices are flipped\n");
			} else if (!d.isMicroscopy) {
				printWarning("Unable to determine slice direction: please check whether slices are flipped (derived image)\n");
			}
		}
	}
	if (flip) {
		for (int i = 0; i < 4; i++)
			R->m[i][2] = -R->m[i][2];
	}
	if (flip)
		iSL = -iSL;
#ifdef MY_DEBUG
	printMessage("verify slice dir %d %d %d\n", h->dim[1], h->dim[2], h->dim[3]);
	// reportMat44((char*)"Rout",*R);
	printMessage("flip = %d\n", flip);
	printMessage("sliceDir = %d\n", iSL);
	printMessage(" pos1 = %f\n", pos1);
#endif
	return iSL;
} // verify_slice_dir()

mat44 noNaN(mat44 Q44, bool isVerbose, bool *isBogus) // simplify any headers that have NaN values
{
	mat44 ret = Q44;
	bool isNaN44 = false;
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
			if (isnan(ret.m[i][j]))
				isNaN44 = true;
	if (isNaN44) {
		*isBogus = true;
		if (isVerbose)
			printWarning("Bogus spatial matrix (perhaps non-spatial image): inspect spatial orientation.\n");
		for (int i = 0; i < 4; i++)
			for (int j = 0; j < 4; j++)
				if (i == j)
					ret.m[i][j] = 1;
				else
					ret.m[i][j] = 0;
		ret.m[1][1] = -1;
	} // if isNaN detected
	return ret;
}

#define 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

void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose) {
	bool isBogus = false;
	mat44 Q44 = noNaN(Q44i, isVerbose, &isBogus);
	if ((h->session_error == kSessionBadMatrix) || (isBogus)) {
		h->session_error = kSessionBadMatrix;
		h->sform_code = NIFTI_XFORM_UNKNOWN;
	} else
		h->sform_code = NIFTI_XFORM_SCANNER_ANAT;
	h->srow_x[0] = Q44.m[0][0];
	h->srow_x[1] = Q44.m[0][1];
	h->srow_x[2] = Q44.m[0][2];
	h->srow_x[3] = Q44.m[0][3];
	h->srow_y[0] = Q44.m[1][0];
	h->srow_y[1] = Q44.m[1][1];
	h->srow_y[2] = Q44.m[1][2];
	h->srow_y[3] = Q44.m[1][3];
	h->srow_z[0] = Q44.m[2][0];
	h->srow_z[1] = Q44.m[2][1];
	h->srow_z[2] = Q44.m[2][2];
	h->srow_z[3] = Q44.m[2][3];
	float dumdx, dumdy, dumdz;
	nifti_mat44_to_quatern(Q44, &h->quatern_b, &h->quatern_c, &h->quatern_d, &h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz, &h->pixdim[0]);
	h->qform_code = h->sform_code;
} // setQSForm()

#ifdef my_unused

ivec3 maxCol(mat33 R) {
	// return index of maximum column in 3x3 matrix, e.g. [1 0 0; 0 1 0; 0 0 1] -> 1,2,3
	ivec3 ixyz;
	mat33 foo;
	for (int i = 0; i < 3; i++)
		for (int j = 0; j < 3; j++)
			foo.m[i][j] = fabs(R.m[i][j]);
	// ixyz.v[0] : row with largest value in column 1
	ixyz.v[0] = 1;
	if ((foo.m[1][0] > foo.m[0][0]) && (foo.m[1][0] >= foo.m[2][0]))
		ixyz.v[0] = 2; // 2nd column largest column
	else if ((foo.m[2][0] > foo.m[0][0]) && (foo.m[2][0] > foo.m[1][0]))
		ixyz.v[0] = 3; // 3rd column largest column
	// ixyz.v[1] : row with largest value in column 2, but not the same row as ixyz.v[1]
	if (ixyz.v[0] == 1) {
		ixyz.v[1] = 2;
		if (foo.m[2][1] > foo.m[1][1])
			ixyz.v[1] = 3;
	} else if (ixyz.v[0] == 2) {
		ixyz.v[1] = 1;
		if (foo.m[2][1] > foo.m[0][1])
			ixyz.v[1] = 3;
	} else { // ixyz.v[0] == 3
		ixyz.v[1] = 1;
		if (foo.m[1][1] > foo.m[0][1])
			ixyz.v[1] = 2;
	}
	// ixyz.v[2] : 3rd row, constrained by previous rows
	ixyz.v[2] = 6 - ixyz.v[1] - ixyz.v[0]; // sum of 1+2+3
	return ixyz;
}

int sign(float x) {
	// returns -1,0,1 depending on if X is less than, equal to or greater than zero
	if (x < 0)
		return -1;
	else if (x > 0)
		return 1;
	return 0;
}

// Subfunction: get dicom xform matrix and related info
// This is a direct port of Xiangrui Li's dicm2nii function
mat44 xform_mat(struct TDICOMdata d) {
	vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]);
	vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]);
	vec3 sliceV = crossProduct(readV, phaseV);
	mat33 R;
	LOAD_MAT33(R, readV.v[0], readV.v[1], readV.v[2],
			   phaseV.v[0], phaseV.v[1], phaseV.v[2],
			   sliceV.v[0], sliceV.v[1], sliceV.v[2]);
	R = nifti_mat33_transpose(R);
	// reportMat33((char*)"R",R);
	ivec3 ixyz = maxCol(R);
	// printMessage("%d %d %d\n", ixyz.v[0], ixyz.v[1], ixyz.v[2]);
	int iSL = ixyz.v[2]; // 1/2/3 for Sag/Cor/Tra slice
	float cosSL = R.m[iSL - 1][2];
	// printMessage("cosSL\t%g\n", cosSL);
	// vec3 pixdim = setVec3(d.xyzMM[1], d.xyzMM[2], d.xyzMM[3]);
	// printMessage("%g %g %g\n", pixdim.v[0], pixdim.v[1], pixdim.v[2]);
	mat33 pixdim;
	LOAD_MAT33(pixdim, d.xyzMM[1], 0.0, 0.0,
			   0.0, d.xyzMM[2], 0.0,
			   0.0, 0.0, d.xyzMM[3]);
	R = nifti_mat33_mul(R, pixdim);
	// reportMat33((char*)"R",R);
	mat44 R44;
	LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.patientPosition[1],
			   R.m[1][0], R.m[1][1], R.m[1][2], d.patientPosition[2],
			   R.m[2][0], R.m[2][1], R.m[2][2], d.patientPosition[3]);
	// reportMat44((char*)"R",R44);
	// rest are former: R = verify_slice_dir(R, s, dim, iSL)
	if ((d.xyzDim[3] < 2) && (d.CSA.mosaicSlices < 2))
		return R44; // don't care direction for single slice
	vec3 dim = setVec3(d.xyzDim[1], d.xyzDim[2], d.xyzDim[3]);
	if (d.CSA.mosaicSlices > 1) { // Siemens mosaic: use dim(1) since no transpose to img
		float nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices));
		dim.v[0] = dim.v[0] / nRowCol;
		dim.v[1] = dim.v[1] / nRowCol;
		dim.v[2] = d.CSA.mosaicSlices;
		vec4 dim4 = setVec4((nRowCol - 1) * dim.v[0] / 2.0f, (nRowCol - 1) * dim.v[1] / 2.0f, 0);
		vec4 offset = nifti_vect44mat44_mul(dim4, R44);
		// printMessage("%g %g %g\n", dim.v[0], dim.v[1], dim.v[2]);
		// printMessage("%g %g %g\n", dim4.v[0], dim4.v[1], dim4.v[2]);
		// printMessage("%g %g %g %g\n", offset.v[0], offset.v[1], offset.v[2], offset.v[3]);
		// printMessage("nRowCol\t%g\n", nRowCol);
		R44.m[0][3] = offset.v[0];
		R44.m[1][3] = offset.v[1];
		R44.m[2][3] = offset.v[2];
		// R44.m[3][3] = offset.v[3];
		if (sign(d.CSA.sliceNormV[iSL]) != sign(cosSL)) {
			R44.m[0][2] = -R44.m[0][2];
			R44.m[1][2] = -R44.m[1][2];
			R44.m[2][2] = -R44.m[2][2];
			R44.m[3][2] = -R44.m[3][2];
		}
		// reportMat44((char*)"iR44",R44);
		return R44;
	} else if (true) {
		// SliceNormalVector TO DO
		printMessage("Not completed");
#ifndef USING_R
		exit(2);
#endif
		return R44;
	}
	printMessage("Unable to determine spatial transform\n");
#ifndef USING_R
	exit(1);
#else
	return R44;
#endif
}

mat44 set_nii_header(struct TDICOMdata d) {
	mat44 R = xform_mat(d);
	// R(1:2,:) = -R(1:2,:); % dicom LPS to nifti RAS, xform matrix before reorient
	for (int i = 0; i < 2; i++)
		for (int j = 0; j < 4; j++)
			R.m[i][j] = -R.m[i][j];
#ifdef MY_DEBUG
	reportMat44((char *)"R44", R);
#endif
}
#endif

// This code predates Xiangrui Li's set_nii_header function
mat44 set_nii_header_x(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int *sliceDir, int isVerbose) {
	*sliceDir = 0;
	mat44 Q44 = nifti_dicom2mat(d.orient, d.patientPosition, d.xyzMM);
	if ((d.isMicroscopy) && (isnan(Q44.m[0][3])) ) {
		for (int c = 0; c < 4; c++)
			for (int r = 0; r < 4; r++)
				Q44.m[r][c] = 0.0;
		Q44.m[0][0] = h->pixdim[1];
		Q44.m[1][1] = h->pixdim[2];
		Q44.m[2][2] = h->pixdim[3];
		Q44.m[3][3] = 1.0;
		return Q44;
	}
	// Q44 = doQuadruped(Q44);
	if (d.isSegamiOasis == true) {
		// Segami reconstructions appear to disregard DICOM spatial parameters: assume center of volume is isocenter and no table tilt
		//  Consider sample image with d.orient (0020,0037) = -1 0 0; 0 1 0: this suggests image RAI (L->R, P->A, S->I) but the vendors viewing software suggests LPS
		// Perhaps we should ignore 0020,0037 and 0020,0032 as they are hidden in sequence 0054,0022, but in this case no positioning is provided
		//  http://www.cs.ucl.ac.uk/fileadmin/cmic/Documents/DavidAtkinson/DICOM.pdf
		//  https://www.slicer.org/wiki/Coordinate_systems
		LOAD_MAT44(Q44, -h->pixdim[1], 0, 0, 0, 0, -h->pixdim[2], 0, 0, 0, 0, h->pixdim[3], 0); // X and Y dimensions flipped in NIfTI (RAS) vs DICOM (LPS)
		vec4 originVx = setVec4((h->dim[1] + 1.0f) / 2.0f, (h->dim[2] + 1.0f) / 2.0f, (h->dim[3] + 1.0f) / 2.0f);
		vec4 originMm = nifti_vect44mat44_mul(originVx, Q44);
		for (int i = 0; i < 3; i++)
			Q44.m[i][3] = -originMm.v[i]; // set origin to center voxel
		if (isVerbose) {
			// printMessage("origin (vx) %g %g %g\n",originVx.v[0],originVx.v[1],originVx.v[2]);
			// printMessage("origin (mm) %g %g %g\n",originMm.v[0],originMm.v[1],originMm.v[2]);
			printWarning("Segami coordinates defy DICOM convention, please check orientation\n");
		}
		return Q44;
	}
	// next line only for Siemens mosaic: ignore for UIH grid
	//	https://github.com/xiangruili/dicm2nii/commit/47ad9e6d9bc8a999344cbd487d602d420fb1509f
	if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.mosaicSlices > 1)) {
		double nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices));
		double lFactorX = (d.xyzDim[1] - (d.xyzDim[1] / nRowCol)) / 2.0;
		double lFactorY = (d.xyzDim[2] - (d.xyzDim[2] / nRowCol)) / 2.0;
		Q44.m[0][3] = (float)((Q44.m[0][0] * lFactorX) + (Q44.m[0][1] * lFactorY) + Q44.m[0][3]);
		Q44.m[1][3] = (float)((Q44.m[1][0] * lFactorX) + (Q44.m[1][1] * lFactorY) + Q44.m[1][3]);
		Q44.m[2][3] = (float)((Q44.m[2][0] * lFactorX) + (Q44.m[2][1] * lFactorY) + Q44.m[2][3]);
		for (int c = 0; c < 2; c++)
			for (int r = 0; r < 4; r++)
				Q44.m[c][r] = -Q44.m[c][r];
		mat33 Q;
		LOAD_MAT33(Q, d.orient[1], d.orient[4], d.CSA.sliceNormV[1],
				   d.orient[2], d.orient[5], d.CSA.sliceNormV[2],
				   d.orient[3], d.orient[6], d.CSA.sliceNormV[3]);
		if (nifti_mat33_determ(Q) < 0) { // Siemens sagittal are R>>L, whereas NIfTI is L>>R, we retain Siemens order on disk so ascending is still ascending, but we need to have the spatial transform reflect this.
			mat44 det;
			*sliceDir = kSliceOrientMosaicNegativeDeterminant; // we need to handle DTI vectors accordingly
			LOAD_MAT44(det, 1.0l, 0.0l, 0.0l, 0.0l, 0.0l, 1.0l, 0.0l, 0.0l, 0.0l, 0.0l, -1.0l, 0.0l);
			// patient_to_tal.m[2][3] = 1-d.CSA.MosaicSlices;
			Q44 = nifti_mat44_mul(Q44, det);
		}
	} else { // not a mosaic
		*sliceDir = verify_slice_dir(d, d2, h, &Q44, isVerbose);
		for (int c = 0; c < 4; c++)		// LPS to nifti RAS, xform matrix before reorient
			for (int r = 0; r < 2; r++) // swap rows 1 & 2
				Q44.m[r][c] = -Q44.m[r][c];
	}
#ifdef MY_DEBUG
	reportMat44((char *)"Q44", Q44);
#endif
	return Q44;
}

int headerDcm2NiiSForm(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { // fill header s and q form
	// see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c
	// returns sliceDir: 0=unknown,1=sag,2=coro,3=axial,-=reversed slices
	int sliceDir = 0;
	if (h->dim[3] < 2) {
		mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose);
		setQSForm(h, Q44, isVerbose);
		return sliceDir; // don't care direction for single slice
	}
	h->sform_code = NIFTI_XFORM_UNKNOWN;
	h->qform_code = NIFTI_XFORM_UNKNOWN;
	bool isOK = false;
	for (int i = 1; i <= 6; i++)
		if (d.orient[i] != 0.0)
			isOK = true;
	if (!isOK) {
		// we will have to guess, assume axial acquisition saved in standard Siemens style?
		d.orient[1] = 1.0f;
		d.orient[2] = 0.0f;
		d.orient[3] = 0.0f;
		d.orient[4] = 0.0f;
		d.orient[5] = 1.0f;
		d.orient[6] = 0.0f;
		if (d.isMicroscopy) {
			// WSI
		} else if ((d.isDerived) || ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3) && (d.manufacturer == kMANUFACTURER_SIEMENS))) {
			printMessage("Unable to determine spatial orientation: 0020,0037 missing (probably not a problem: derived image)\n");
		} else {
			printMessage("Unable to determine spatial orientation: 0020,0037 missing (Type 1 attribute: not a valid DICOM) Series %ld\n", d.seriesNum);
		}
	}
	mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose);
	setQSForm(h, Q44, isVerbose);
	return sliceDir;
} // headerDcm2NiiSForm()

int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { // final pass after de-mosaic
	char txt[1024] = {""};
	if (h->slice_code == NIFTI_SLICE_UNKNOWN)
		h->slice_code = d.CSA.sliceOrder;
	if (h->slice_code == NIFTI_SLICE_UNKNOWN)
		h->slice_code = d2.CSA.sliceOrder; // sometimes the first slice order is screwed up https://github.com/eauerbach/CMRR-MB/issues/29
	if (d.modality == kMODALITY_MR)
		snprintf(txt, 1024, "TE=%.2g;Time=%.3f", d.TE, d.acquisitionTime);
	else
		snprintf(txt, 1024, "Time=%.3f", d.acquisitionTime);
	if (d.CSA.phaseEncodingDirectionPositive >= 0) {
		char dtxt[1024] = {""};
		snprintf(dtxt, 1024, ";phase=%d", d.CSA.phaseEncodingDirectionPositive);
		strcat(txt, dtxt);
	}
	// from dicm2nii 20151117 InPlanePhaseEncodingDirection
	if (d.phaseEncodingRC == 'R')
		h->dim_info = (3 << 4) + (1 << 2) + 2;
	if (d.phaseEncodingRC == 'C')
		h->dim_info = (3 << 4) + (2 << 2) + 1;
	if (d.CSA.multiBandFactor > 1) {
		char dtxt[1024] = {""};
		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) && (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()

int dcmStrLen(int len, int kMaxLen) {
	if (len < kMaxLen)
		return len + 1;
	else
		return kMaxLen;
} // dcmStrLen()

struct TDICOMdata clear_dicom_data() {
	struct TDICOMdata d;
	// d.dti4D = NULL;
	d.locationsInAcquisition = 0;
	d.locationsInAcquisitionConflict = 0; // for GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081
	d.modality = kMODALITY_UNKNOWN;
	d.effectiveEchoSpacingGE = 0;
	for (int i = 0; i < 4; i++) {
		d.CSA.dtiV[i] = 0;
		d.patientPosition[i] = NAN;
		// d.patientPosition2nd[i] = NAN; //used to distinguish XYZT vs XYTZ for Philips 4D
		d.patientPositionLast[i] = NAN; // used to compute slice direction for Philips 4D
		d.stackOffcentre[i] = NAN;
		d.angulation[i] = 0.0f;
		d.xyzMM[i] = 1;
	}
	for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i)
		d.dimensionIndexValues[i] = 0;
	// d.CSA.sliceTiming[0] = -1.0f; //impossible value denotes not known
	for (int z = 0; z < kMaxEPI3D; z++)
		d.CSA.sliceTiming[z] = -1.0;
	d.CSA.numDti = 0;
	for (int i = 0; i < 5; i++)
		d.xyzDim[i] = 1;
	for (int i = 0; i < 7; i++)
		d.orient[i] = 0.0f;
	strcpy(d.patientName, "");
	strcpy(d.deidentificationMethod, "");
	strcpy(d.patientID, "");
	strcpy(d.accessionNumber, "");
	strcpy(d.imageType, "");
	strcpy(d.imageTypeText, "");
	strcpy(d.imageComments, "");
	strcpy(d.imageBaseName, "");
	strcpy(d.phaseEncodingDirectionDisplayedUIH, "");
	strcpy(d.studyDate, "");
	strcpy(d.studyTime, "");
	strcpy(d.protocolName, "");
	strcpy(d.patientOrient, "");
	strcpy(d.seriesDescription, "");
	strcpy(d.sequenceName, "");
	strcpy(d.scanningSequence, "");
	strcpy(d.sequenceVariant, "");
	strcpy(d.manufacturersModelName, "");
	strcpy(d.institutionalDepartmentName, "");
	strcpy(d.procedureStepDescription, "");
	strcpy(d.institutionName, "");
	strcpy(d.referringPhysicianName, "");
	strcpy(d.institutionAddress, "");
	strcpy(d.deviceSerialNumber, "");
	strcpy(d.softwareVersions, "");
	strcpy(d.stationName, "");
	strcpy(d.studyDescription, "");
	strcpy(d.scanOptions, "");
	// strcpy(d.mrAcquisitionType, "");
	strcpy(d.seriesInstanceUID, "");
	strcpy(d.instanceUID, "");
	strcpy(d.studyID, "");
	strcpy(d.studyInstanceUID, "");
	strcpy(d.bodyPartExamined, "");
	strcpy(d.coilName, "");
	strcpy(d.coilElements, "");
	strcpy(d.pulseSequenceName, "");
	strcpy(d.radiopharmaceutical, "");
	strcpy(d.convolutionKernel, "");
	strcpy(d.parallelAcquisitionTechnique, "");
	strcpy(d.imageOrientationText, "");
	strcpy(d.tracerRadionuclide, "");
	strcpy(d.unitsPT, "");
	strcpy(d.decayCorrection, "");
	strcpy(d.attenuationCorrectionMethod, "");
	strcpy(d.reconstructionMethod, "");
	d.phaseEncodingLines = 0;
	//~ d.patientPositionSequentialRepeats = 0;
	//~ d.patientPositionRepeats = 0;
	d.isHasPhase = false;
	d.isHasReal = false;
	d.isHasImaginary = false;
	d.isHasMagnitude = false;
	// d.maxGradDynVol = -1; //PAR/REC only
	d.sliceOrient = kSliceOrientUnknown;
	d.dateTime = (double)19770703150928.0;
	d.acquisitionTime = 0.0f;
	d.acquisitionDate = 0.0f;
	d.manufacturer = kMANUFACTURER_UNKNOWN;
	d.isPlanarRGB = false;
	d.lastScanLoc = NAN;
	d.TR = 0.0;
	d.TE = 0.0;
#ifdef USING_DCM2NIIXFSWRAPPER
	d.TI = -1.0; // default when there is no TI in dicom file
#else
	d.TI = 0.0;
#endif
	d.flipAngle = 0.0;
	d.bandwidthPerPixelPhaseEncode = 0.0;
	d.acquisitionDuration = 0.0;
	d.imagingFrequency = 0.0;
	d.numberOfAverages = 0.0;
	d.fieldStrength = 0.0;
	d.SAR = 0.0;
	d.pixelBandwidth = 0.0;
	d.zSpacing = 0.0;
	d.zThick = 0.0;
	//~ d.numberOfDynamicScans = 0;
	d.echoNum = 0;
	d.echoTrainLength = 0;
	d.waterFatShift = 0.0;
	d.groupDelay = 0.0;
	d.postLabelDelay = 0;
	d.shimGradientX = -33333; // impossible value for UINT16
	d.shimGradientY = -33333; // impossible value for UINT16
	d.shimGradientZ = -33333; // impossible value for UINT16
	strcpy(d.prescanReuseString, "");
	d.decayFactor = 0.0;
	d.scatterFraction = 0.0;
	d.percentSampling = 0.0;
	d.phaseFieldofView = 0.0;
	d.dwellTime = 0;
	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;
	d.isYBRfull = false;
	strcpy(d.deepLearningText, "");
	// d.patientPositionNumPhilips = 0;
	d.imageBytes = 0;
	d.intenScale = 1;
	d.intenScalePhilips = 0;
	d.intenIntercept = 0;
	d.gantryTilt = 0.0;
	d.exposureTimeMs = 0.0;
	d.xRayTubeCurrent = 0.0;
	d.radionuclidePositronFraction = 0.0;
	d.radionuclideHalfLife = 0.0;
	d.doseCalibrationFactor = 0.0;
	d.injectedVolume = 0.0;
	d.reconFilterSize = NAN;
	d.ecat_isotope_halflife = 0.0;
	d.frameDuration = -1.0;
	d.frameReferenceTime = -1.0;
	d.ecat_dosage = 0.0;
	d.radionuclideTotalDose = 0.0;
	d.seriesNum = 1;
	d.acquNum = 0;
	d.frameNum = 0; //first shall be one
	d.imageNum = 1;
	d.imageStart = 0;
	d.offsetTableItems = 0;
	d.is3DAcq = false;				  // e.g. MP-RAGE, SPACE, TFE
	d.is2DAcq = false;				  //
	d.isDerived = false;			  // 0008,0008 = DERIVED,CSAPARALLEL,POSDISP
	d.isSegamiOasis = false;		  // these images do not store spatial coordinates
	d.isBVecWorldCoordinates = false; // bvecs can be in image space (GE) or world coordinates (Siemens)
	d.isGrayscaleSoftcopyPresentationState = false;
	d.isRawDataStorage = false;
	d.isMicroscopy = false;
	d.isPartialFourier = false;
	d.isIR = false;
	d.isEPI = false;
	d.isDiffusion = false;
	d.isVectorFromBMatrix = false;
	d.isStackableSeries = false; // combine DCE series https://github.com/rordenlab/dcm2niix/issues/252
	d.isXA10A = false;			 // https://github.com/rordenlab/dcm2niix/issues/236
	d.isXA = false;
	d.triggerDelayTime = 0.0;
	d.RWVScale = 0.0;
	d.RWVIntercept = 0.0;
	d.isScaleOrTEVaries = false;
	d.isScaleVariesEnh = false; // issue363
	d.bitsAllocated = 16;		// bits
	d.bitsStored = 0;
	d.samplesPerPixel = 1;
	d.pixelPaddingValue = NAN;
	d.isValid = false;
	d.isXRay = false;
	d.isMultiEcho = false;
	d.numberOfTR = 1;
	d.isSigned = false;	   // default is unsigned!
	d.isFloat = false;	   // default is for integers, not single or double precision
	d.isResampled = false; // assume data not resliced to remove gantry tilt problems
	d.isLocalizer = false;
	d.isNonParallelSlices = false;
	d.isCoilVaries = false;
	d.compressionScheme = 0; // none
	d.isExplicitVR = true;
	d.isLittleEndian = true; // DICOM initially always little endian
	d.converted2NII = 0;
	d.numberOfDiffusionDirectionGE = -1;
	d.velocityEncodeScaleGE = 1.0;
	d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNKNOWN;
	d.rtia_timerGE = -1.0;
	d.rawDataRunNumber = -1;
	d.maxEchoNumGE = -1;
	d.epiVersionGE = -1;
	d.internalepiVersionGE = -1;
	d.diffCyclingModeGE = kGE_DIFF_CYCLING_UNKNOWN;
	d.tensorFileGE = 0;
	d.durationLabelPulseGE = -1;
	d.aslFlags = kASL_FLAG_NONE;
	d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN;
	d.mtState = -1;
	d.numberOfExcitations = -1;
	d.numberOfArms = -1;
	d.numberOfPointsPerArm = -1;
	d.phaseNumber = -1; // Philips Multi-Phase ASL
	d.spoiling = kSPOILING_UNKOWN;
	d.interp3D = -1;
	for (int i = 0; i < kMaxOverlay; i++)
		d.overlayStart[i] = 0;
	d.isHasOverlay = false;
	d.isPrivateCreatorRemap = false;
	d.isRealIsPhaseMapHz = false;
	d.isVariableFlipAngle = false;
	d.isQuadruped = false;
	d.numberOfImagesInGridUIH = 0;
	d.phaseEncodingRC = '?';
	d.patientSex = '?';
	d.patientSize = 0.0;
	d.patientWeight = 0.0;
	strcpy(d.patientBirthDate, "");
	strcpy(d.patientAge, "");
	d.CSA.bandwidthPerPixelPhaseEncode = 0.0;
	d.CSA.mosaicSlices = 0;
	d.CSA.tablePos[0] = -1.0; // unset
	d.CSA.tablePos[1] = 0.0;  // x
	d.CSA.tablePos[2] = 0.0;  // y
	d.CSA.tablePos[3] = 0.0;  // z
	d.CSA.sliceNormV[1] = 0.0;
	d.CSA.sliceNormV[2] = 0.0;
	d.CSA.sliceNormV[3] = 1.0; // default Siemens Image Numbering is F>>H https://www.mccauslandcenter.sc.edu/crnl/tools/stc
	d.CSA.sliceOrder = NIFTI_SLICE_UNKNOWN;
	d.CSA.slice_start = 0;
	d.CSA.slice_end = 0;
	d.CSA.protocolSliceNumber1 = 0;
	d.CSA.phaseEncodingDirectionPositive = -1; // unknown
	d.CSA.isPhaseMap = false;
	d.CSA.multiBandFactor = 1;
	d.CSA.SeriesHeader_offset = 0;
	d.CSA.SeriesHeader_length = 0;
	d.CSA.coilNumber = -1;
	strcpy(d.CSA.bidsDataType, "");
	strcpy(d.CSA.bidsEntitySuffix, "");
	strcpy(d.CSA.bidsTask, "");
	return d;
} // clear_dicom_data()

int isdigitdot(int c) { // returns true if digit or '.'
	if (c == '.')
		return 1;
	return isdigit(c);
}

void dcmStrDigitsDotOnlyKey(char key, char *lStr) {
	// e.g. string "F:2.50" returns 2.50 if key==":"
	size_t len = strlen(lStr);
	if (len < 1)
		return;
	bool isKey = false;
	for (int i = 0; i < (int)len; i++) {
		if (!isdigitdot(lStr[i])) {
			isKey = (lStr[i] == key);
			lStr[i] = ' ';
		} else if (!isKey)
			lStr[i] = ' ';
	}
} // dcmStrDigitsDotOnlyKey()

void dcmStrDigitsOnlyKey(char key, char *lStr) {
	// e.g. string "p2s3" returns 2 if key=="p" and 3 if key=="s"
	size_t len = strlen(lStr);
	if (len < 1)
		return;
	bool isKey = false;
	for (int i = 0; i < (int)len; i++) {
		if (!isdigitdot(lStr[i])) {
			isKey = (lStr[i] == key);
			lStr[i] = ' ';
		} else if (!isKey)
			lStr[i] = ' ';
	}
} // dcmStrDigitsOnlyKey()

void dcmStrDigitsOnly(char *lStr) {
	// e.g. change "H11" to " 11"
	size_t len = strlen(lStr);
	if (len < 1)
		return;
	for (int i = 0; i < (int)len; i++)
		if (!isdigit(lStr[i]))
			lStr[i] = ' ';
}

// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/
uint32_t mz_crc32X(unsigned char *ptr, size_t buf_len) {
	static const uint32_t s_crc32[16] = {0, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
										 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344,
										 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c};
	uint32_t crcu32 = 0;
	if (!ptr)
		return crcu32;
	crcu32 = ~crcu32;
	while (buf_len--) {
		uint8_t b = *ptr++;
		crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)];
		crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)];
	}
	return ~crcu32;
}

void dcmStr(int lLength, unsigned char lBuffer[], char *lOut, bool isStrLarge = false) {
	if (lLength < 1)
		return;
	char *cString = (char *)malloc(sizeof(char) * (lLength + 1));
	cString[lLength] = 0;
	memcpy(cString, (char *)&lBuffer[0], lLength);
// memcpy(cString, test, lLength);
// printMessage("X%dX\n", (unsigned char)d.patientName[1]);
#ifdef ISO8859
	for (int i = 0; i < lLength; i++)
		// assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1
		if (cString[i] < 1) {
			unsigned char c = (unsigned char)cString[i];
			if ((c >= 192) && (c <= 198))
				cString[i] = 'A';
			if (c == 199)
				cString[i] = 'C';
			if ((c >= 200) && (c <= 203))
				cString[i] = 'E';
			if ((c >= 204) && (c <= 207))
				cString[i] = 'I';
			if (c == 208)
				cString[i] = 'D';
			if (c == 209)
				cString[i] = 'N';
			if ((c >= 210) && (c <= 214))
				cString[i] = 'O';
			if (c == 215)
				cString[i] = 'x';
			if (c == 216)
				cString[i] = 'O';
			if ((c >= 217) && (c <= 220))
				cString[i] = 'O';
			if (c == 221)
				cString[i] = 'Y';
			if ((c >= 224) && (c <= 230))
				cString[i] = 'a';
			if (c == 231)
				cString[i] = 'c';
			if ((c >= 232) && (c <= 235))
				cString[i] = 'e';
			if ((c >= 236) && (c <= 239))
				cString[i] = 'i';
			if (c == 240)
				cString[i] = 'o';
			if (c == 241)
				cString[i] = 'n';
			if ((c >= 242) && (c <= 246))
				cString[i] = 'o';
			if (c == 248)
				cString[i] = 'o';
			if ((c >= 249) && (c <= 252))
				cString[i] = 'u';
			if (c == 253)
				cString[i] = 'y';
			if (c == 255)
				cString[i] = 'y';
		}
#endif
	// we no longer sanitize strings, see issue 425
	int len = lLength;
	if (cString[len - 1] == ' ')
		len--;
	// while ((len > 0) && (cString[len]=='_')) len--; //remove trailing '_'
	cString[len] = 0; // null-terminate, strlcpy does this anyway
	int maxLen = kDICOMStr;
	if (isStrLarge)
		maxLen = kDICOMStrLarge;
	len = dcmStrLen(len, maxLen);
	if (len == maxLen) { // we need space for null-termination
		if (cString[len - 2] == '_')
			len = len - 2;
	}
	memcpy(lOut, cString, len - 1);
	lOut[len - 1] = 0;
	free(cString);
} // dcmStr()

#ifdef MY_OLD
// this code works on Intel but not some older systems https://github.com/rordenlab/dcm2niix/issues/327
float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { // read binary 32-bit float
	// http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian
	bool swap = (littleEndian != littleEndianPlatform());
	float retVal = 0;
	if (lByteLength < 4)
		return retVal;
	memcpy(&retVal, (char *)&lBuffer[0], 4);
	if (!swap)
		return retVal;
	float swapVal;
	char *inFloat = (char *)&retVal;
	char *outFloat = (char *)&swapVal;
	outFloat[0] = inFloat[3];
	outFloat[1] = inFloat[2];
	outFloat[2] = inFloat[1];
	outFloat[3] = inFloat[0];
	// printMessage("swapped val = %f\n",swapVal);
	return swapVal;
} // dcmFloat()

double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { // read binary 64-bit float
	// http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian
	bool swap = (littleEndian != littleEndianPlatform());
	double retVal = 0.0f;
	if (lByteLength < 8)
		return retVal;
	memcpy(&retVal, (char *)&lBuffer[0], 8);
	if (!swap)
		return retVal;
	char *floatToConvert = (char *)&lBuffer;
	char *returnFloat = (char *)&retVal;
	// swap the bytes into a temporary buffer
	returnFloat[0] = floatToConvert[7];
	returnFloat[1] = floatToConvert[6];
	returnFloat[2] = floatToConvert[5];
	returnFloat[3] = floatToConvert[4];
	returnFloat[4] = floatToConvert[3];
	returnFloat[5] = floatToConvert[2];
	returnFloat[6] = floatToConvert[1];
	returnFloat[7] = floatToConvert[0];
	// printMessage("swapped val = %f\n",retVal);
	return retVal;
} // dcmFloatDouble()
#else

float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { // read binary 32-bit float
	// http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian
	if (lByteLength < 4)
		return 0.0;
	bool swap = (littleEndian != littleEndianPlatform());
	union {
		uint32_t i;
		float f;
		uint8_t c[4];
	} i, o;
	memcpy(&i.i, (char *)&lBuffer[0], 4);
	// printf("%02x%02x%02x%02x\n",i.c[0], i.c[1], i.c[2], i.c[3]);
	if (!swap)
		return i.f;
	o.c[0] = i.c[3];
	o.c[1] = i.c[2];
	o.c[2] = i.c[1];
	o.c[3] = i.c[0];
	// printf("swp %02x%02x%02x%02x\n",o.c[0], o.c[1], o.c[2], o.c[3]);
	return o.f;
} // dcmFloat()

double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { // read binary 64-bit float
	// http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian
	if (lByteLength < 8)
		return 0.0;
	bool swap = (littleEndian != littleEndianPlatform());
	union {
		uint32_t i;
		double d;
		uint8_t c[8];
	} i, o;
	memcpy(&i.i, (char *)&lBuffer[0], 8);
	if (!swap)
		return i.d;
	o.c[0] = i.c[7];
	o.c[1] = i.c[6];
	o.c[2] = i.c[5];
	o.c[3] = i.c[4];
	o.c[4] = i.c[3];
	o.c[5] = i.c[2];
	o.c[6] = i.c[1];
	o.c[7] = i.c[0];
	return o.d;
} // dcmFloatDouble()
#endif

// SS/IS datatype
int16_t dcmIntSS(int lByteLength, unsigned char lBuffer[], bool littleEndian) { // read binary 16 or 32 bit integer
	if (littleEndian)
		return (uint16_t)lBuffer[0] | ((uint16_t)lBuffer[1] << 8); // shortint vs word?
	return (uint16_t)lBuffer[1] | ((uint16_t)lBuffer[1] << 0);	   // shortint vs word?
} // dcmInt()

// UL/US unsigned integer
int dcmInt(int lByteLength, unsigned char lBuffer[], bool littleEndian) { // read binary 16 or 32 bit integer
	if (littleEndian) {
		if (lByteLength <= 3)
			return lBuffer[0] | (lBuffer[1] << 8);										 // shortint vs word?
		return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24); // shortint vs word?
	}
	if (lByteLength <= 3)
		return lBuffer[1] | (lBuffer[0] << 8);										 // shortint vs word?
	return lBuffer[3] + (lBuffer[2] << 8) + (lBuffer[1] << 16) + (lBuffer[0] << 24); // shortint vs word?
} // dcmInt()

uint32_t dcmAttributeTag(unsigned char lBuffer[], bool littleEndian) {
	// read Attribute Tag (AT) value
	// return in Group + (Element << 16) format
	if (littleEndian)
		return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24);
	return lBuffer[1] + (lBuffer[0] << 8) + (lBuffer[3] << 16) + (lBuffer[2] << 24);
} // dcmInt()

int dcmStrInt(const int lByteLength, const unsigned char lBuffer[]) { // read int stored as a string
	char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1));
	cString[lByteLength] = 0;
	memcpy(cString, (const unsigned char *)(&lBuffer[0]), lByteLength);
	int ret = atoi(cString);
	free(cString);
	return ret;
} // dcmStrInt()

int dcmStrManufacturer(const int lByteLength, unsigned char lBuffer[]) { // read float stored as a string
	if (lByteLength < 2)
		return kMANUFACTURER_UNKNOWN;
	// #ifdef _MSC_VER
	char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1));
	// #else
	//	char cString[lByteLength + 1];
	// #endif
	int ret = kMANUFACTURER_UNKNOWN;
	cString[lByteLength] = 0;
	memcpy(cString, (char *)&lBuffer[0], lByteLength);
	if ((toupper(cString[0]) == 'S') && (toupper(cString[1]) == 'I'))
		ret = kMANUFACTURER_SIEMENS;
	if ((toupper(cString[0]) == 'G') && (toupper(cString[1]) == 'E'))
		ret = kMANUFACTURER_GE;
	if ((toupper(cString[0]) == 'H') && (toupper(cString[1]) == 'I'))
		ret = kMANUFACTURER_HITACHI;
	if ((toupper(cString[0]) == 'M') && (toupper(cString[1]) == 'E'))
		ret = kMANUFACTURER_MEDISO;
	if ((toupper(cString[0]) == 'P') && (toupper(cString[1]) == 'H'))
		ret = kMANUFACTURER_PHILIPS;
	if ((toupper(cString[0]) == 'T') && (toupper(cString[1]) == 'O'))
		ret = kMANUFACTURER_TOSHIBA;
	if ((toupper(cString[0]) == 'C') && (toupper(cString[1]) == 'A'))
		ret = kMANUFACTURER_CANON;
	if ((toupper(cString[0]) == 'C') && (toupper(cString[1]) == 'P'))
		ret = kMANUFACTURER_SIEMENS; // CPS is a Siemens venture, issue 568
	if ((toupper(cString[0]) == 'U') && (toupper(cString[1]) == 'I'))
		ret = kMANUFACTURER_UIH;
	if ((toupper(cString[0]) == 'B') && (toupper(cString[1]) == 'R'))
		ret = kMANUFACTURER_BRUKER;
	if ((toupper(cString[0]) == 'M') && (toupper(cString[1]) == 'R'))
		ret = kMANUFACTURER_MRSOLUTIONS;
	if ((toupper(cString[0]) == 'H') && (toupper(cString[1]) == 'Y'))
		ret = kMANUFACTURER_HYPERFINE;
	if ((toupper(cString[0]) == 'L') && (toupper(cString[1]) == 'E'))
		ret = kMANUFACTURER_LEICA;

	// 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
	free(cString);
	// #endif
	return ret;
} // dcmStrManufacturer

float csaMultiFloat(unsigned char buff[], int nItems, float Floats[], int *ItemsOK) {
	// warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats]
	// if lnItems == 1, returns first item, if lnItems > 1 returns index of final successful conversion
	TCSAitem itemCSA;
	*ItemsOK = 0;
	if (nItems < 1)
		return 0.0f;
	Floats[1] = 0;
	int lPos = 0;
	for (int lI = 1; lI <= nItems; lI++) {
		memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA));
		lPos += sizeof(itemCSA);
		// Storage order is always little-endian, so byte-swap required values if necessary
		if (!littleEndianPlatform())
			nifti_swap_4bytes(1, &itemCSA.xx2_Len);
		if (itemCSA.xx2_Len > 0) {
			char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len));
			memcpy(cString, &buff[lPos], itemCSA.xx2_Len); // TPX memcpy(&cString, &buff[lPos], sizeof(cString));
			lPos += ((itemCSA.xx2_Len + 3) / 4) * 4;
			// printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString);
			Floats[lI] = (float)atof(cString);
			*ItemsOK = lI; // some sequences have store empty items
			free(cString);
		}
	} // for each item
	return Floats[1];
} // csaMultiFloat()

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;
	if (nItems < 1)
		return false;
	int lPos = 0;
	for (int lI = 1; lI <= nItems; lI++) {
		memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA));
		lPos += sizeof(itemCSA);
		// Storage order is always little-endian, so byte-swap required values if necessary
		if (!littleEndianPlatform())
			nifti_swap_4bytes(1, &itemCSA.xx2_Len);
		if (itemCSA.xx2_Len > 0) {
			char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len + 1));
			memcpy(cString, &buff[lPos], sizeof(itemCSA.xx2_Len)); // TPX memcpy(&cString, &buff[lPos], sizeof(cString));
			lPos += ((itemCSA.xx2_Len + 3) / 4) * 4;
			// printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString);
			if (strcmp(cString, "CC:ComplexAdd") == 0)
				return true;
			free(cString);
		}
	} // for each item
	return false;
} // csaIsPhaseMap()

void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3DAcq) {
	if ((is3DAcq) || (itemsOK < 1)) // we expect 3D sequences to be simultaneous
		return;
	if (itemsOK > kMaxEPI3D) {
		printError("Please increase kMaxEPI3D and recompile\n");
		return;
	}
	float maxTimeValue, minTimeValue, timeValue1;
	minTimeValue = CSA->sliceTiming[0];
	for (int z = 0; z < itemsOK; z++)
		if (CSA->sliceTiming[z] < minTimeValue)
			minTimeValue = CSA->sliceTiming[z];
	// CSA can report negative slice times
	//  https://neurostars.org/t/slice-timing-illegal-values-in-fmriprep/1516/8
	//  Nov 1, 2018 <siemens-healthineers.com> wrote:
	//  If you have an interleaved dataset we can more definitively validate this formula (aka sliceTime(i) - min(sliceTimes())).
	if (minTimeValue < 0) {
		// printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); //if uncommented, overwhelming number of warnings (one per DICOM input), better once per series
		CSA->sliceTiming[kMaxEPI3D - 1] = -2.0; // issue 271: flag for unified warning
		for (int z = 0; z < itemsOK; z++)
			CSA->sliceTiming[z] = CSA->sliceTiming[z] - minTimeValue;
	}
	CSA->multiBandFactor = 1;
	timeValue1 = CSA->sliceTiming[0];
	int nTimeZero = 0;
	if (CSA->sliceTiming[0] == 0)
		nTimeZero++;
	int minTimeIndex = 0;
	int maxTimeIndex = minTimeIndex;
	minTimeValue = CSA->sliceTiming[0];
	maxTimeValue = minTimeValue;
	if (isVerbose > 1)
		printMessage("   sliceTimes %g\t", CSA->sliceTiming[0]);
	for (int z = 1; z < itemsOK; z++) { // find index and value of fastest time
		if (isVerbose > 1)
			printMessage("%g\t", CSA->sliceTiming[z]);
		if (CSA->sliceTiming[z] == 0)
			nTimeZero++;
		if (CSA->sliceTiming[z] < minTimeValue) {
			minTimeValue = CSA->sliceTiming[z];
			minTimeIndex = (float)z;
		}
		if (CSA->sliceTiming[z] > maxTimeValue) {
			maxTimeValue = CSA->sliceTiming[z];
			maxTimeIndex = (float)z;
		}
		if (CSA->sliceTiming[z] == timeValue1)
			CSA->multiBandFactor++;
	}
	if (isVerbose > 1)
		printMessage("\n");
	CSA->slice_start = minTimeIndex;
	CSA->slice_end = maxTimeIndex;
	if (minTimeIndex == maxTimeIndex) {
		if (isVerbose)
			printMessage("No variability in slice times (3D EPI?)\n");
	}
	if (nTimeZero < 2) { // not for multi-band, not 3D
		if (minTimeIndex == 1)
			CSA->sliceOrder = NIFTI_SLICE_ALT_INC2; // e.g. 3,1,4,2
		else if (minTimeIndex == (itemsOK - 2))
			CSA->sliceOrder = NIFTI_SLICE_ALT_DEC2; // e.g. 2,4,1,3 or 5,2,4,1,3
		else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] < CSA->sliceTiming[2]))
			CSA->sliceOrder = NIFTI_SLICE_SEQ_INC; // e.g. 1,2,3,4
		else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] > CSA->sliceTiming[2]))
			CSA->sliceOrder = NIFTI_SLICE_ALT_INC; // e.g. 1,3,2,4
		else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] > CSA->sliceTiming[itemsOK - 2]))
			CSA->sliceOrder = NIFTI_SLICE_SEQ_DEC; // e.g. 4,3,2,1 or 5,4,3,2,1
		else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] < CSA->sliceTiming[itemsOK - 2]))
			CSA->sliceOrder = NIFTI_SLICE_ALT_DEC; // e.g. 4,2,3,1 or 3,5,2,4,1
		else {
			if (!is3DAcq) // we expect 3D sequences to be simultaneous
				printWarning("Unable to determine slice order from CSA tag MosaicRefAcqTimes\n");
		}
	}
	if ((CSA->sliceOrder != NIFTI_SLICE_UNKNOWN) && (nTimeZero > 1) && (nTimeZero < itemsOK)) {
		if (isVerbose)
			printMessage(" Multiband x%d sequence: setting slice order as UNKNOWN (instead of %d)\n", nTimeZero, CSA->sliceOrder);
		CSA->sliceOrder = NIFTI_SLICE_UNKNOWN;
	}
} // checkSliceTimes()

int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, int isVerbose, bool is3DAcq) {
	// see also http://afni.nimh.nih.gov/pub/dist/src/siemens_dicom_csa.c
	// printMessage("%c%c%c%c\n",buff[0],buff[1],buff[2],buff[3]);
	if (lLength < 36)
		return EXIT_FAILURE;
	if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0'))
		return EXIT_FAILURE;
	int lPos = 8; // skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2
	int lnTag = buff[lPos] + (buff[lPos + 1] << 8) + (buff[lPos + 2] << 16) + (buff[lPos + 3] << 24);
	if ((lnTag > 128) || (lnTag < 1)) {
		printError("%d n_tags CSA Image Header corrupted (0029,1010) see issue 633.\n", lnTag);
		return EXIT_FAILURE;
	}
	if (buff[lPos + 4] != 77)
		return EXIT_FAILURE;
	lPos += 8; // skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0
	TCSAtag tagCSA;
	TCSAitem itemCSA;
	int itemsOK;
	float lFloats[7];
	for (int lT = 1; lT <= lnTag; lT++) {
		memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); // read tag
		lPos += sizeof(tagCSA);
		// Storage order is always little-endian, so byte-swap required values if necessary
		if (!littleEndianPlatform())
			nifti_swap_4bytes(1, &tagCSA.nitems);
		if (isVerbose > 1) // extreme verbosity: show every CSA tag
			printMessage("   %d CSA of %s %d\n", lPos, tagCSA.name, tagCSA.nitems);
		if (tagCSA.nitems > 0) {
			if (strcmp(tagCSA.name, "ImageHistory") == 0)
				CSA->isPhaseMap = csaIsPhaseMap(&buff[lPos], tagCSA.nitems);
			else if (strcmp(tagCSA.name, "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) {
				CSA->dtiV[0] = csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK);
				if (CSA->dtiV[0] < 0.0) {
					printWarning("(Corrupt) CSA reports negative b-value! %g\n", CSA->dtiV[0]);
					CSA->dtiV[0] = 0.0;
				}
				CSA->numDti = 1; // triggered by b-value, as B0 images do not have DiffusionGradientDirection tag
			} else if ((strcmp(tagCSA.name, "DiffusionGradientDirection") == 0) && (tagCSA.nitems > 2)) {
				CSA->dtiV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK);
				CSA->dtiV[2] = lFloats[2];
				CSA->dtiV[3] = lFloats[3];
				if (isVerbose)
					printMessage("DiffusionGradientDirection %f %f %f\n", lFloats[1], lFloats[2], lFloats[3]);
			} else if ((strcmp(tagCSA.name, "SliceNormalVector") == 0) && (tagCSA.nitems > 2)) {
				CSA->sliceNormV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK);
				CSA->sliceNormV[2] = lFloats[2];
				CSA->sliceNormV[3] = lFloats[3];
				if (isVerbose > 1)
					printMessage("   SliceNormalVector %f %f %f\n", CSA->sliceNormV[1], CSA->sliceNormV[2], CSA->sliceNormV[3]);
			} else if (strcmp(tagCSA.name, "SliceMeasurementDuration") == 0)
				CSA->sliceMeasurementDuration = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK);
			else if (strcmp(tagCSA.name, "BandwidthPerPixelPhaseEncode") == 0)
				CSA->bandwidthPerPixelPhaseEncode = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK);
			else if (strcmp(tagCSA.name, "ImaRelTablePosition") == 0) { //issue890
				csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK);
				CSA->tablePos[0] = 1.0;
				CSA->tablePos[1] = lFloats[1];
				CSA->tablePos[2] = lFloats[2];	
				CSA->tablePos[3] = -lFloats[3];
			} else if ((strcmp(tagCSA.name, "MosaicRefAcqTimes") == 0) && (tagCSA.nitems > 3)) {
				if (itemsOK > kMaxEPI3D) {
					printError("Please increase kMaxEPI3D and recompile\n");
				} else {
					float *sliceTimes = (float *)malloc(sizeof(float) * (tagCSA.nitems + 1));
					csaMultiFloat(&buff[lPos], tagCSA.nitems, sliceTimes, &itemsOK);
					for (int z = 0; z < kMaxEPI3D; z++)
						CSA->sliceTiming[z] = -1.0;
					for (int z = 0; z < itemsOK; z++)
						CSA->sliceTiming[z] = sliceTimes[z + 1];
					free(sliceTimes);
					checkSliceTimes(CSA, itemsOK, isVerbose, is3DAcq);
				}
			} else if (strcmp(tagCSA.name, "ProtocolSliceNumber") == 0)
				CSA->protocolSliceNumber1 = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK));
			else if (strcmp(tagCSA.name, "PhaseEncodingDirectionPositive") == 0)
				CSA->phaseEncodingDirectionPositive = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK));
			for (int lI = 1; lI <= tagCSA.nitems; lI++) {
				memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA));
				lPos += sizeof(itemCSA);
				// Storage order is always little-endian, so byte-swap required values if necessary
				if (!littleEndianPlatform())
					nifti_swap_4bytes(1, &itemCSA.xx2_Len);
				lPos += ((itemCSA.xx2_Len + 3) / 4) * 4;
			}
		} // if at least 1 item
	} // for lT 1..lnTag
	if (CSA->protocolSliceNumber1 > 1)
		CSA->sliceOrder = NIFTI_SLICE_UNKNOWN;
	return EXIT_SUCCESS;
} // readCSAImageHeader()

void dcmMultiShorts(int lByteLength, unsigned char lBuffer[], int lnShorts, uint16_t *lShorts, bool littleEndian) {
	// read array of unsigned shorts US http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html
	if ((lnShorts < 1) || (lByteLength != (lnShorts * 2)))
		return;
	memcpy(&lShorts[0], (uint16_t *)&lBuffer[0], lByteLength);
	bool swap = (littleEndian != littleEndianPlatform());
	if (swap)
		nifti_swap_2bytes(lnShorts, &lShorts[0]);
} // dcmMultiShorts()

void dcmMultiLongs(int lByteLength, unsigned char lBuffer[], int lnLongs, uint32_t *lLongs, bool littleEndian) {
	// read array of unsigned longs UL http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html
	if ((lnLongs < 1) || (lByteLength != (lnLongs * 4)))
		return;
	memcpy(&lLongs[0], (uint32_t *)&lBuffer[0], lByteLength);
	bool swap = (littleEndian != littleEndianPlatform());
	if (swap)
		nifti_swap_4bytes(lnLongs, &lLongs[0]);
} // dcmMultiLongs()

void dcmMultiFloat(int lByteLength, char lBuffer[], int lnFloats, float *lFloats) {
	// warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats]
	if ((lnFloats < 1) || (lByteLength < 1))
		return;
	char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1));
	memcpy(cString, (char *)&lBuffer[0], lByteLength);
	cString[lByteLength] = 0; // null terminate
	char *temp = (char *)malloc(lByteLength + 1);
	int f = 0, lStart = 0;
	bool isOK = false;
	for (int i = 0; i <= lByteLength; i++) {
		if ((lBuffer[i] >= '0') && (lBuffer[i] <= '9'))
			isOK = true;
		if ((isOK) && ((i == (lByteLength)) || (lBuffer[i] == '/') || (lBuffer[i] == ' ') || (lBuffer[i] == '\\'))) {
			snprintf(temp, i - lStart + 1, "%s", &cString[lStart]);
			// printMessage("dcmMultiFloat %s\n",temp);
			if (f < lnFloats) {
				f++;
				lFloats[f] = (float)atof(temp);
				isOK = false;
				// printMessage("%d == %f\n", f, atof(temp));
			} // if f <= nFloats
			lStart = i + 1;
		} // if isOK
	} // for i to length
	free(temp);
	free(cString);
} // dcmMultiFloat()

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);
	cString[lByteLength] = 0; // null terminate
	float ret = (float)atof(cString);
	free(cString);
	return ret;
} // dcmStrFloat()

int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm) {
	memset(h, 0, sizeof(nifti_1_header)); // zero-fill structure so unused items are consistent
	for (int i = 0; i < 80; i++)
		h->descrip[i] = 0;
	for (int i = 0; i < 24; i++)
		h->aux_file[i] = 0;
	for (int i = 0; i < 18; i++)
		h->db_name[i] = 0;
	for (int i = 0; i < 10; i++)
		h->data_type[i] = 0;
	for (int i = 0; i < 16; i++)
		h->intent_name[i] = 0;
	if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3)) {
		h->intent_code = NIFTI_INTENT_ESTIMATE; // make sure we treat this as RGBRGB...RGB
		h->datatype = DT_RGB24;
	} else if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 1))
		h->datatype = DT_UINT8;
	else if ((d.bitsAllocated == 1) && (d.samplesPerPixel == 1))
		h->datatype = DT_UINT8;
	else if ((d.bitsAllocated == 12) && (d.samplesPerPixel == 1))
		h->datatype = DT_INT16;
	else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (d.isSigned))
		h->datatype = DT_INT16;
	else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (!d.isSigned))
		h->datatype = DT_UINT16;
	else if ((d.bitsAllocated == 32) && (d.isFloat))
		h->datatype = DT_FLOAT32;
	else if (d.bitsAllocated == 32)
		h->datatype = DT_INT32;
	else if ((d.bitsAllocated == 64) && (d.isFloat))
		h->datatype = DT_FLOAT64;
	else {
		printMessage("Unsupported DICOM bit-depth %d with %d samples per pixel\n", d.bitsAllocated, d.samplesPerPixel);
		return EXIT_FAILURE;
	}
	if ((h->datatype == DT_UINT16) && (d.bitsStored > 0) && (d.bitsStored < 16))
		h->datatype = DT_INT16; // DT_INT16 is more widely supported, same representation for values 0..32767
	for (int i = 0; i < 8; i++) {
		h->pixdim[i] = 0.0f;
		h->dim[i] = 0;
	}
	// next items listed as unused in NIfTI format, but zeroed for consistency across runs
	h->extents = 0;
	h->session_error = kSessionOK;
	h->glmin = 0;	  // unused, but make consistent
	h->glmax = 0;	  // unused, but make consistent
	h->regular = 114; // in legacy Analyze this was always 114
	// these are important
	h->scl_inter = d.intenIntercept;
	h->scl_slope = d.intenScale;
	h->cal_max = 0;
	h->cal_min = 0;
	h->magic[0] = 'n';
	h->magic[1] = '+';
	h->magic[2] = '1';
	h->magic[3] = '\0';
	h->vox_offset = (float)d.imageStart;
	if (d.bitsAllocated == 1)
		h->bitpix = 8 * d.samplesPerPixel;
	else if (d.bitsAllocated == 12)
		h->bitpix = 16 * d.samplesPerPixel;
	else
		h->bitpix = d.bitsAllocated * d.samplesPerPixel;
	h->pixdim[1] = d.xyzMM[1];
	h->pixdim[2] = d.xyzMM[2];
	h->pixdim[3] = d.xyzMM[3];
	h->pixdim[4] = d.TR / 1000.0; // TR reported in msec, time is in sec
	h->dim[1] = d.xyzDim[1];
	h->dim[2] = d.xyzDim[2];
	h->dim[3] = d.xyzDim[3];
	h->dim[4] = d.xyzDim[4];
	h->dim[5] = 1;
	h->dim[6] = 1;
	h->dim[7] = 1;
	if (h->dim[4] < 2)
		h->dim[0] = 3;
	else
		h->dim[0] = 4;
	for (int i = 0; i <= 3; i++) {
		h->srow_x[i] = 0.0f;
		h->srow_y[i] = 0.0f;
		h->srow_z[i] = 0.0f;
	}
	h->slice_start = 0;
	h->slice_end = 0;
	h->srow_x[0] = -1;
	h->srow_y[2] = 1;
	h->srow_z[1] = -1;
	h->srow_x[3] = ((float)h->dim[1] / 2);
	h->srow_y[3] = -((float)h->dim[3] / 2);
	h->srow_z[3] = ((float)h->dim[2] / 2);
	h->qform_code = NIFTI_XFORM_UNKNOWN;
	h->sform_code = NIFTI_XFORM_UNKNOWN;
	h->toffset = 0;
	h->intent_code = NIFTI_INTENT_NONE;
	h->dim_info = 0; // Freq, Phase and Slice all unknown
	h->xyzt_units = NIFTI_UNITS_MM + NIFTI_UNITS_SEC;
	h->slice_duration = 0; // avoid +inf/-inf, NaN
	h->intent_p1 = 0;	   // avoid +inf/-inf, NaN
	h->intent_p2 = 0;	   // avoid +inf/-inf, NaN
	h->intent_p3 = 0;	   // avoid +inf/-inf, NaN
	h->pixdim[0] = 1;	   // QFactor should be 1 or -1
	h->sizeof_hdr = 348;   // used to signify header does not need to be byte-swapped
	h->slice_code = d.CSA.sliceOrder;
	if (isComputeSForm)
		headerDcm2Nii2(d, d, h, false);
	return EXIT_SUCCESS;
} // headerDcm2Nii()

bool isFloatDiff(float a, float b) {
	return (fabs(a - b) > FLT_EPSILON);
} // isFloatDiff()

mat33 nifti_mat33_reorder_cols(mat33 m, ivec3 v) {
	// matlab equivalent ret = m(:, v); where v is 1,2,3 [INDEXED FROM ONE!!!!]
	mat33 ret;
	for (int r = 0; r < 3; r++) {
		for (int c = 0; c < 3; c++)
			ret.m[r][c] = m.m[r][v.v[c] - 1];
	}
	return ret;
} // nifti_mat33_reorder_cols()

void changeExt(char *file_name, const char *ext) {
	char *p_extension;
	p_extension = strrchr(file_name, '.');
	if (p_extension)
		strcpy(++p_extension, ext);
} // changeExt()

void cleanStr(char *lOut) {
	// e.g. strings such as image comments with special characters (e.g. "G/6/2009") can disrupt file saves
	size_t lLength = strlen(lOut);
	if (lLength < 1)
		return;
	char *cString = (char *)malloc(sizeof(char) * (lLength + 1));
	cString[lLength] = 0;
	memcpy(cString, (char *)&lOut[0], lLength);
	for (int i = 0; i < (int)lLength; i++)
		// assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1
		if (cString[i] < 1) {
			unsigned char c = (unsigned char)cString[i];
			if ((c >= 192) && (c <= 198))
				cString[i] = 'A';
			if (c == 199)
				cString[i] = 'C';
			if ((c >= 200) && (c <= 203))
				cString[i] = 'E';
			if ((c >= 204) && (c <= 207))
				cString[i] = 'I';
			if (c == 208)
				cString[i] = 'D';
			if (c == 209)
				cString[i] = 'N';
			if ((c >= 210) && (c <= 214))
				cString[i] = 'O';
			if (c == 215)
				cString[i] = 'x';
			if (c == 216)
				cString[i] = 'O';
			if ((c >= 217) && (c <= 220))
				cString[i] = 'O';
			if (c == 221)
				cString[i] = 'Y';
			if ((c >= 224) && (c <= 230))
				cString[i] = 'a';
			if (c == 231)
				cString[i] = 'c';
			if ((c >= 232) && (c <= 235))
				cString[i] = 'e';
			if ((c >= 236) && (c <= 239))
				cString[i] = 'i';
			if (c == 240)
				cString[i] = 'o';
			if (c == 241)
				cString[i] = 'n';
			if ((c >= 242) && (c <= 246))
				cString[i] = 'o';
			if (c == 248)
				cString[i] = 'o';
			if ((c >= 249) && (c <= 252))
				cString[i] = 'u';
			if (c == 253)
				cString[i] = 'y';
			if (c == 255)
				cString[i] = 'y';
		}
	for (int i = 0; i < (int)lLength; i++)
		if ((cString[i] < 1) || (cString[i] == ' ') || (cString[i] == ',') || (cString[i] == '/') || (cString[i] == '\\') || (cString[i] == '%') || (cString[i] == '*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13))
			cString[i] = '_'; // issue398
	// if ((cString[i]<1) || (cString[i]==' ') || (cString[i]==',') || (cString[i]=='^') || (cString[i]=='/') || (cString[i]=='\\') || (cString[i]=='%') || (cString[i]=='*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_';
	int len = 1;
	for (int i = 1; i < (int)lLength; i++) { // remove repeated "_"
		if ((cString[i - 1] != '_') || (cString[i] != '_')) {
			cString[len] = cString[i];
			len++;
		}
	} // for each item
	if (cString[len - 1] == '_')
		len--;
	cString[len] = 0; // null-terminate, strlcpy does this anyway
	int maxLen = kDICOMStr;
	len = dcmStrLen(len, maxLen);
	if (len == maxLen) { // we need space for null-termination
		if (cString[len - 2] == '_')
			len = len - 2;
	}
	memcpy(lOut, cString, len - 1);
	lOut[len - 1] = 0;
	free(cString);
} // cleanStr()

int isSameFloatGE(float a, float b) {
	// Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!!
	// return (a == b); //naive approach does not have any tolerance for rounding errors
	return (fabs(a - b) <= 0.0001);
}

struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase) {
	struct TDICOMdata d = clear_dicom_data();
	dti4D->sliceOrder[0] = -1;
	dti4D->volumeOnsetTime[0] = -1;
	dti4D->decayFactor[0] = -1;
	dti4D->frameDuration[0] = -1;
	dti4D->frameReferenceTime[0] = -1;
	// dti4D->contentTime[0] = -1;
	// dti4D->fragmentOffset[0] = -1;
	dti4D->intenScale[0] = 0.0;
	strcpy(d.protocolName, "");		 // erase dummy with empty
	strcpy(d.seriesDescription, ""); // erase dummy with empty
	strcpy(d.sequenceName, "");		 // erase dummy with empty
	strcpy(d.scanningSequence, "");
	FILE *fp = fopen(parname, "r");
	if (fp == NULL)
		return d;
#define LINESZ 2048
#define kSlice 0
#define kEcho 1
#define kDyn 2
#define kCardiac 3
#define kImageType 4
#define kSequence 5
#define kIndex 6
// V3 only identical for columns 1..6
#define kBitsPerVoxel 7		 // V3: not per slice: "Image pixel size [8 or 16 bits]"
#define kXdim 9				 // V3: not per slice: "Recon resolution (x, y)"
#define kYdim 10			 // V3: not per slice: "Recon resolution (x, y)"
	int kRI = 11;			 // V3: 7
	int kRS = 12;			 // V3: 8
	int kSS = 13;			 // V3: 9
	int kAngulationAPs = 16; // V3: 12
	int kAngulationFHs = 17; // V3: 13
	int kAngulationRLs = 18; // V3: 14
	int kPositionAP = 19;	 // V3: 15
	int kPositionFH = 20;	 // V3: 16
	int kPositionRL = 21;	 // V3: 17
#define kThickmm 22			 // V3: not per slice: "Slice thickness [mm]"
#define kGapmm 23			 // V3: not per slice: "Slice gap [mm]"
	int kSliceOrients = 25;	 // V3: 19
	int kXmm = 28;			 // V3: 22
	int kYmm = 29;			 // V3: 23
	int kTEcho = 30;		 // V3: 24
	int kDynTime = 31;		 // V3: 25
	int kTriggerTime = 32;	 // V3: 26
	int kbval = 33;			 // V3: 27
// the following do not exist in V3
#define kInversionDelayMs 40
#define kbvalNumber 41
#define kGradientNumber 42
// the following do not exist in V40 or earlier
#define kv1 47
#define kv2 45
#define kv3 46
// the following do not exist in V41 or earlier
#define kASL 48
#define kMaxImageType 4 // 4 observed image types: real, imag, mag, phase (in theory also subsequent calculation such as B1)
	printWarning("dcm2niix PAR is not actively supported (hint: use dicm2nii)\n");
	if (isReadPhase)
		printWarning(" Reading phase images from PAR/REC\n");
	char buff[LINESZ];
	// next values: PAR V3 only
	int v3BitsPerVoxel = 16; // V3: not per slice: "Image pixel size [8 or 16 bits]"
	int v3Xdim = 128;		 // not per slice: "Recon resolution (x, y)"
	int v3Ydim = 128;		 // V3: not per slice: "Recon resolution (x, y)"
	float v3Thickmm = 2.0;	 // V3: not per slice: "Slice thickness [mm]"
	float v3Gapmm = 0.0;	 // V3: not per slice: "Slice gap [mm]"
	// from top of header
	int maxNumberOfDiffusionValues = 1;
	int maxNumberOfGradientOrients = 1;
	int maxNumberOfCardiacPhases = 1;
	int maxNumberOfEchoes = 1;
	int maxNumberOfDynamics = 1;
	int maxNumberOfMixes = 1;
	int maxNumberOfLabels = 1; // Number of label types <0=no ASL>
	float maxBValue = 0.0f;
	float maxDynTime = 0.0f;
	float minDynTime = 999999.0f;
	float TE = 0.0;
	int minDyn = 32767;
	int maxDyn = 0;
	int minSlice = 32767;
	int maxSlice = 0;
	bool ADCwarning = false;
	bool isTypeWarning = false;
	bool isType4Warning = false;
	bool isSequenceWarning = false;
	int numSlice2D = 0;
	int prevDyn = -1;
	bool dynNotAscending = false;
	int maxSlice2D = 0;
	int parVers = 0;
	int maxSeq = -1; // maximum value of Seq column
	int seq1 = -1;	 // value of Seq volume for first slice
	int maxEcho = 1;
	int maxCardiac = 1;
	int nCols = 26;
	// int diskSlice = 0;
	int num3DExpected = 0; // number of 3D volumes in the top part of the header
	int num2DExpected = 0; // number of 2D slices described in the top part of the header
	int maxVol = -1;
	int patientPositionNumPhilips = 0;
	d.isValid = false;
	const int kMaxCols = 49;
	float *cols = (float *)malloc(sizeof(float) * (kMaxCols + 1));
	for (int i = 0; i < kMaxCols; i++)
		cols[i] = 0.0; // old versions of PAR do not fill all columns - beware of buffer overflow
	char *p = fgets(buff, LINESZ, fp);
	bool isIntenScaleVaries = false;
	for (int i = 0; i < kMaxDTI4D; i++) {
		dti4D->S[i].V[0] = -1.0;
		dti4D->TE[i] = -1.0;
	}
	for (int i = 0; i < kMaxSlice2D; i++)
		dti4D->sliceOrder[i] = -1;
	while (p) {
		if (strlen(buff) < 1)
			continue;
		if (buff[0] == '#') { // comment
			char Comment[7][50];
			sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3], Comment[4], Comment[5], Comment[6]);
			if ((strcmp(Comment[0], "sl") == 0) && (strcmp(Comment[1], "ec") == 0)) {
				num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes;
				num2DExpected = d.xyzDim[3] * num3DExpected;
				if ((num2DExpected) >= kMaxSlice2D) {
					printError("Use dicm2nii or increase kMaxSlice2D to be more than %d\n", num2DExpected);
					printMessage("  slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n",
								 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
								 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels);
					free(cols);
					return d;
				}
			}
			if (strcmp(Comment[1], "TRYOUT") == 0) {
				// sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3],Comment[4], Comment[5],Comment[6]);
				parVers = (int)round(atof(Comment[6]) * 10); // 4.2 = 42 etc
				if (parVers <= 29) {
					printMessage("Unsupported old PAR version %0.2f (use dicm2nii)\n", parVers / 10.0);
					return d;
					// nCols = 26; //e.g. PAR 3.0 has 26 relevant columns
				}
				if (parVers < 40) {
					nCols = 29; // PAR 3.0?
					kRI = 7;
					kRS = 8;
					kSS = 9;
					kAngulationAPs = 12;
					kAngulationFHs = 13;
					kAngulationRLs = 14;
					kPositionAP = 15;
					kPositionFH = 16;
					kPositionRL = 17;
					kSliceOrients = 19;
					kXmm = 22;
					kYmm = 23;
					kTEcho = 24;
					kDynTime = 25;
					kTriggerTime = 26;
					kbval = 27;
				} else if (parVers < 41)
					nCols = kv1; // e.g PAR 4.0
				else if (parVers < 42)
					nCols = kASL; // e.g. PAR 4.1 - last column is final diffusion b-value
				else
					nCols = kMaxCols; // e.g. PAR 4.2
			}
			// the following do not exist in V3
			p = fgets(buff, LINESZ, fp); // get next line
			continue;
		} // process '#' comment
		if (buff[0] == '.') { // tag
			char Comment[9][50];
			for (int i = 0; i < 9; i++)
				strcpy(Comment[i], "");
			sscanf(buff, ". %s %s %s %s %s %s %s %s %s\n", Comment[0], Comment[1], Comment[2], Comment[3], Comment[4], Comment[5], Comment[6], Comment[7], Comment[8]);
			if ((strcmp(Comment[0], "Acquisition") == 0) && (strcmp(Comment[1], "nr") == 0)) {
				d.acquNum = atoi(Comment[3]);
				d.seriesNum = d.acquNum;
			}
			if ((strcmp(Comment[0], "Recon") == 0) && (strcmp(Comment[1], "resolution") == 0)) {
				v3Xdim = (int)atoi(Comment[5]);
				v3Ydim = (int)atoi(Comment[6]);
				// printMessage("recon %d,%d\n", v3Xdim,v3Ydim);
			}
			if ((strcmp(Comment[1], "pixel") == 0) && (strcmp(Comment[2], "size") == 0)) {
				v3BitsPerVoxel = (int)atoi(Comment[8]);
				// printMessage("bits %d\n", v3BitsPerVoxel);
			}
			if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "gap") == 0)) {
				v3Gapmm = (float)atof(Comment[4]);
				// printMessage("gap %g\n", v3Gapmm);
			}
			if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "thickness") == 0)) {
				v3Thickmm = (float)atof(Comment[4]);
				// printMessage("thick %g\n", v3Thickmm);
			}
			if ((strcmp(Comment[0], "Repetition") == 0) && (strcmp(Comment[1], "time") == 0))
				d.TR = (float)atof(Comment[4]);
			if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "name") == 0)) {
				strcpy(d.patientName, Comment[3]);
				strcat(d.patientName, Comment[4]);
				strcat(d.patientName, Comment[5]);
				strcat(d.patientName, Comment[6]);
				strcat(d.patientName, Comment[7]);
				cleanStr(d.patientName);
				// printMessage("%s\n",d.patientName);
			}
			if ((strcmp(Comment[0], "Technique") == 0) && (strcmp(Comment[1], ":") == 0)) {
				strcpy(d.patientID, Comment[2]);
				strcat(d.patientID, Comment[3]);
				strcat(d.patientID, Comment[4]);
				strcat(d.patientID, Comment[5]);
				strcat(d.patientID, Comment[6]);
				strcat(d.patientID, Comment[7]);
				cleanStr(d.patientID);
			}
			if ((strcmp(Comment[0], "Protocol") == 0) && (strcmp(Comment[1], "name") == 0)) {
				strcpy(d.protocolName, Comment[3]);
				strcat(d.protocolName, Comment[4]);
				strcat(d.protocolName, Comment[5]);
				strcat(d.protocolName, Comment[6]);
				strcat(d.protocolName, Comment[7]);
				cleanStr(d.protocolName);
			}
			if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "name") == 0)) {
				strcpy(d.imageComments, Comment[3]);
				strcat(d.imageComments, Comment[4]);
				strcat(d.imageComments, Comment[5]);
				strcat(d.imageComments, Comment[6]);
				strcat(d.imageComments, Comment[7]);
				cleanStr(d.imageComments);
			}
			if ((strcmp(Comment[0], "Series") == 0) && (strcmp(Comment[1], "Type") == 0)) {
				strcpy(d.seriesDescription, Comment[3]);
				strcat(d.seriesDescription, Comment[4]);
				strcat(d.seriesDescription, Comment[5]);
				strcat(d.seriesDescription, Comment[6]);
				strcat(d.seriesDescription, Comment[7]);
				cleanStr(d.seriesDescription);
#ifdef USING_DCM2NIIXFSWRAPPER
				remove_specialchars(d.seriesDescription);
#endif
			}
			if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "date/time") == 0)) {
				if ((strlen(Comment[3]) >= 10) && (strlen(Comment[5]) >= 8)) {
					// DICOM date format is YYYYMMDD, but PAR stores YYYY.MM.DD 2016.03.25
					d.studyDate[0] = Comment[3][0];
					d.studyDate[1] = Comment[3][1];
					d.studyDate[2] = Comment[3][2];
					d.studyDate[3] = Comment[3][3];
					d.studyDate[4] = Comment[3][5];
					d.studyDate[5] = Comment[3][6];
					d.studyDate[6] = Comment[3][8];
					d.studyDate[7] = Comment[3][9];
					d.studyDate[8] = '\0';
					// DICOM time format is HHMMSS.FFFFFF, but PAR stores HH:MM:SS, e.g. 18:00:42 or 09:34:16
					d.studyTime[0] = Comment[5][0];
					d.studyTime[1] = Comment[5][1];
					d.studyTime[2] = Comment[5][3];
					d.studyTime[3] = Comment[5][4];
					d.studyTime[4] = Comment[5][6];
					d.studyTime[5] = Comment[5][7];
					d.studyTime[6] = '\0';
					d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime);
				}
			}
			if ((strcmp(Comment[0], "Off") == 0) && (strcmp(Comment[1], "Centre") == 0)) {
				// Off Centre midslice(ap,fh,rl) [mm]
				d.stackOffcentre[2] = (float)atof(Comment[5]);
				d.stackOffcentre[3] = (float)atof(Comment[6]);
				d.stackOffcentre[1] = (float)atof(Comment[7]);
			}
			if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "position") == 0)) {
				// Off Centre midslice(ap,fh,rl) [mm]
				d.patientOrient[0] = toupper(Comment[3][0]);
				d.patientOrient[1] = toupper(Comment[4][0]);
				d.patientOrient[2] = toupper(Comment[5][0]);
				d.patientOrient[3] = 0;
			}
			if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "slices/locations") == 0)) {
				d.xyzDim[3] = atoi(Comment[5]);
			}
			if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "diffusion") == 0)) {
				maxNumberOfDiffusionValues = atoi(Comment[6]);
				// if (maxNumberOfDiffusionValues > 1) maxNumberOfDiffusionValues -= 1; //if two listed, one is B=0
			}
			if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "gradient") == 0)) {
				maxNumberOfGradientOrients = atoi(Comment[6]);
				// Warning ISOTROPIC scans may be stored that are not reported here! 32 directions plus isotropic = 33 volumes
			}
			if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "cardiac") == 0)) {
				maxNumberOfCardiacPhases = atoi(Comment[6]);
			}
			if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "echoes") == 0)) {
				maxNumberOfEchoes = atoi(Comment[5]);
				if (maxNumberOfEchoes > 1)
					d.isMultiEcho = true;
			}
			if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "dynamics") == 0)) {
				maxNumberOfDynamics = atoi(Comment[5]);
			}
			if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "mixes") == 0)) {
				maxNumberOfMixes = atoi(Comment[5]);
				if (maxNumberOfMixes > 1)
					printError("maxNumberOfMixes > 1. Please update this software to support these images\n");
			}
			if ((strcmp(Comment[0], "Number") == 0) && (strcmp(Comment[2], "label") == 0)) {
				maxNumberOfLabels = atoi(Comment[7]);
				if (maxNumberOfLabels < 1)
					maxNumberOfLabels = 1;
			}
			p = fgets(buff, LINESZ, fp); // get next line
			continue;
		} // process '.' tag
		if (strlen(buff) < 24) {		 // empty line
			p = fgets(buff, LINESZ, fp); // get next line
			continue;
		}
		if (parVers < 20) {
			printError("PAR files should have 'CLINICAL TRYOUT' line with a version from 2.0-4.2: %s\n", parname);
			free(cols);
			return d;
		}
		for (int i = 0; i <= nCols; i++)
			cols[i] = strtof(p, &p); // p+1 skip comma, read a float
		// printMessage("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1],(int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]);
		if ((int)cols[kSlice] == 0) {	 // line does not contain attributes
			p = fgets(buff, LINESZ, fp); // get next line
			continue;
		}
		// diskSlice ++;
		bool isADC = false;
		if ((maxNumberOfGradientOrients >= 2) && (cols[kbval] > 50) && isSameFloat(0.0, cols[kv1]) && isSameFloat(0.0, cols[kv2]) && isSameFloat(0.0, cols[kv3])) {
			isADC = true;
			ADCwarning = true;
		}
		if (numSlice2D < 1) {
			d.xyzMM[1] = cols[kXmm];
			d.xyzMM[2] = cols[kYmm];
			if (parVers < 40) { // v3 does things differently
				// cccc
				d.xyzDim[1] = v3Xdim;
				d.xyzDim[2] = v3Ydim;
				d.xyzMM[3] = v3Thickmm + v3Gapmm;
				d.bitsAllocated = v3BitsPerVoxel;
				d.bitsStored = v3BitsPerVoxel;
			} else {
				d.xyzDim[1] = (int)cols[kXdim];
				d.xyzDim[2] = (int)cols[kYdim];
				d.xyzMM[3] = cols[kThickmm] + cols[kGapmm];
				d.bitsAllocated = (int)cols[kBitsPerVoxel];
				d.bitsStored = (int)cols[kBitsPerVoxel];
			}
			d.patientPosition[1] = cols[kPositionRL];
			d.patientPosition[2] = cols[kPositionAP];
			d.patientPosition[3] = cols[kPositionFH];
			d.angulation[1] = cols[kAngulationRLs];
			d.angulation[2] = cols[kAngulationAPs];
			d.angulation[3] = cols[kAngulationFHs];
			d.sliceOrient = (int)cols[kSliceOrients];
			d.TE = cols[kTEcho];
			d.echoNum = cols[kEcho];
			d.TI = cols[kInversionDelayMs];
			d.intenIntercept = cols[kRI];
			d.intenScale = cols[kRS];
			d.intenScalePhilips = cols[kSS];
		} else {
			if (parVers >= 40) {
				if ((d.xyzDim[1] != cols[kXdim]) || (d.xyzDim[2] != cols[kYdim]) || (d.bitsAllocated != cols[kBitsPerVoxel])) {
					printError("Slice dimensions or bit depth varies %s\n", parname);
					printError("xDim %dv%d yDim %dv%d bits  %dv%d\n", d.xyzDim[1], (int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]);
					return d;
				}
			}
			if ((d.intenScale != cols[kRS]) || (d.intenIntercept != cols[kRI]))
				isIntenScaleVaries = true;
		}
		if (isSameFloat(cols[kImageType], 18)) {
			// printWarning("Field map in Hz will be saved as the 'real' image.\n");
			// isTypeWarning = true;
			d.isRealIsPhaseMapHz = true;
		} else if (((cols[kImageType] < 0.0) || (cols[kImageType] > 4.0)) && (!isTypeWarning)) {
			printError("Unknown type %g: not magnitude[0], real[1], imaginary[2] or phase[3].\n", cols[kImageType]);
			isTypeWarning = true;
		}
		if (cols[kDyn] > maxDyn)
			maxDyn = (int)cols[kDyn];
		if (cols[kDyn] < minDyn)
			minDyn = (int)cols[kDyn];
		if (cols[kDyn] < prevDyn)
			dynNotAscending = true;
		prevDyn = cols[kDyn];
		if (cols[kDynTime] > maxDynTime)
			maxDynTime = cols[kDynTime];
		if (cols[kDynTime] < minDynTime)
			minDynTime = cols[kDynTime];
		if (cols[kEcho] > maxEcho)
			maxEcho = cols[kEcho];
		if (cols[kCardiac] > maxCardiac)
			maxCardiac = cols[kCardiac];
		if ((cols[kEcho] == 1) && (cols[kDyn] == 1) && (cols[kCardiac] == 1) && (cols[kGradientNumber] == 1)) {
			if (cols[kSlice] == 1) {
				d.patientPosition[1] = cols[kPositionRL];
				d.patientPosition[2] = cols[kPositionAP];
				d.patientPosition[3] = cols[kPositionFH];
			}
			patientPositionNumPhilips++;
		}
		if (true) { // for every slice
			int slice = (int)cols[kSlice];
			if (slice < minSlice)
				minSlice = slice;
			if (slice > maxSlice) {
				maxSlice = slice;
				d.patientPositionLast[1] = cols[kPositionRL];
				d.patientPositionLast[2] = cols[kPositionAP];
				d.patientPositionLast[3] = cols[kPositionFH];
			}
			int volStep = maxNumberOfDynamics;
			int vol = ((int)cols[kDyn] - 1);
#ifdef old
			int gradDynVol = (int)cols[kGradientNumber] - 1;
			if (gradDynVol < 0)
				gradDynVol = 0; // old PAREC without cols[kGradientNumber]
			vol = vol + (volStep * (gradDynVol));
			if (vol < 0)
				vol = 0;
			volStep = volStep * maxNumberOfGradientOrients;
			int bval = (int)cols[kbvalNumber];
			if (bval > 2) // b=0 is 0, b=1000 is 1, b=2000 is 2 - b=0 does not have multiple directions
				bval = bval - 1;
			else
				bval = 1;
			// if (slice == 1) printMessage("bVal %d bVec %d isADC %d nbVal %d nGrad %d\n",(int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients);
			vol = vol + (volStep * (bval - 1));
			volStep = volStep * (maxNumberOfDiffusionValues - 1);
			if (isADC)
				vol = volStep + (bval - 1);
#else
			if (maxNumberOfDiffusionValues > 1) {
				int grad = (int)cols[kGradientNumber] - 1;
				if (grad < 0)
					grad = 0; // old v4 does not have this tag
				int bval = (int)cols[kbvalNumber] - 1;
				if (bval < 0)
					bval = 0; // old v4 does not have this tag
				if (isADC)
					vol = vol + (volStep * maxNumberOfDiffusionValues * maxNumberOfGradientOrients) + bval;
				else
					vol = vol + (volStep * grad) + (bval * maxNumberOfGradientOrients);

				volStep = volStep * (maxNumberOfDiffusionValues + 1) * maxNumberOfGradientOrients;
				// if (slice == 1) printMessage("vol %d step %d bVal %d bVec %d isADC %d nbVal %d nGrad %d\n", vol, volStep, (int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients);
			}
#endif
			vol = vol + (volStep * ((int)cols[kEcho] - 1));
			volStep = volStep * maxNumberOfEchoes;
			vol = vol + (volStep * ((int)cols[kCardiac] - 1));
			volStep = volStep * maxNumberOfCardiacPhases;
			int ASL = (int)cols[kASL];
			if (ASL < 1)
				ASL = 1;
			vol = vol + (volStep * (ASL - 1));
			volStep = volStep * maxNumberOfLabels;
			// if ((int)cols[kSequence] > 0)
			int seq = (int)cols[kSequence];
			if (seq1 < 0)
				seq1 = seq;
			if (seq > maxSeq)
				maxSeq = seq;
			if (seq != seq1) { // sequence varies within this PAR file
				if (!isSequenceWarning) {
					isSequenceWarning = true;
					printWarning("'scanning sequence' column varies within a single file. This behavior is not described at the top of the header.\n");
				}
				vol = vol + (volStep * 1);
				volStep = volStep * 2;
			}
			// if (slice == 1)  printMessage("%d\t%d\t%d\t%d\t%d\n", isADC,(int)cols[kbvalNumber], (int)cols[kGradientNumber], bval, vol);
			if (vol > maxVol)
				maxVol = vol;
			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");
					isType4Warning = true;
				}
				isPhase = true; // 2019
			}
			if ((cols[kImageType] != 18) && ((cols[kImageType] < 0.0) || (cols[kImageType] > 3.0))) {
				if (!isType4Warning) {
					printWarning("Unknown image type (%g). Be aware the 'phase' image is of an unknown type.\n", round(cols[kImageType]));
					isType4Warning = true;
				}
				isReal = true; //<- this is not correct, kludge for bug in ROGERS_20180526_WIP_B0_NS_8_1.PAR
			}
			if (isReal)
				vol += num3DExpected;
			if (isImaginary)
				vol += (2 * num3DExpected);
			if (isPhase)
				vol += (3 * num3DExpected);
			if (vol >= kMaxDTI4D) {
				printError("Use dicm2nii or increase kMaxDTI4D (currently %d)to be more than %d\n", kMaxDTI4D, kMaxImageType * num2DExpected);
				printMessage("  slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n",
							 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
							 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels);
				free(cols);
				return d;
			}
			// dti4D->S[vol].V[0] = cols[kbval];
			// dti4D->gradDynVol[vol] = gradDynVol;
			dti4D->TE[vol] = cols[kTEcho];
			if (isSameFloatGE(cols[kTEcho], 0))
				dti4D->TE[vol] = TE; // kludge for cols[kImageType]==18 where TE set as 0
			else
				TE = cols[kTEcho];
			dti4D->triggerDelayTime[vol] = cols[kTriggerTime];
			if (dti4D->TE[vol] < 0)
				dti4D->TE[vol] = 0; // used to detect sparse volumes
			// dti4D->intenIntercept[vol] = cols[kRI];
			// dti4D->intenScale[vol] = cols[kRS];
			// dti4D->intenScalePhilips[vol] = cols[kSS];
			dti4D->isReal[vol] = isReal;
			dti4D->isImaginary[vol] = isImaginary;
			dti4D->isPhase[vol] = isPhase;
			if ((maxNumberOfGradientOrients > 1) && (parVers > 40)) {
				dti4D->S[vol].V[0] = cols[kbval];
				dti4D->S[vol].V[1] = cols[kv1];
				dti4D->S[vol].V[2] = cols[kv2];
				dti4D->S[vol].V[3] = cols[kv3];
				if ((vol + 1) > d.CSA.numDti)
					d.CSA.numDti = vol + 1;
			}
			if (numSlice2D < kMaxDTI4D) { // issue 363: intensity can vary with each 2D slice of 4D volume
				dti4D->intenIntercept[numSlice2D] = cols[kRI];
				dti4D->intenScale[numSlice2D] = cols[kRS];
				dti4D->intenScalePhilips[numSlice2D] = cols[kSS];
			}
			// if (slice == 1) printWarning("%d\n", (int)cols[kEcho]);
			slice = slice + (vol * d.xyzDim[3]);
			// offset images by type: mag+0,real+1, imag+2,phase+3
			// if (cols[kImageType] != 0) //yikes - phase maps!
			//	slice = slice + numExpected;
#ifdef USING_DCM2NIIXFSWRAPPER
			// fix ubuntu18 compiler error
			maxSlice2D = (slice > maxSlice2D) ? slice : maxSlice2D;
#else
			maxSlice2D = std::max(slice, maxSlice2D);
#endif
			if ((slice >= 0) && (slice < kMaxSlice2D) && (numSlice2D < kMaxSlice2D) && (numSlice2D >= 0)) {
				dti4D->sliceOrder[slice - 1] = numSlice2D;
				// printMessage("%d\t%d\t%d\n", numSlice2D, slice, (int)cols[kSlice],(int)vol);
			}
			numSlice2D++;
		}
		// printMessage("%f %f %lu\n",cols[9],cols[kGradientNumber], strlen(buff))
		p = fgets(buff, LINESZ, fp); // get next line
	}
	free(cols);
	fclose(fp);
	if ((parVers <= 0) || (numSlice2D < 1)) {
		printError("Invalid PAR format header (unable to detect version or slices) %s\n", parname);
		return d;
	}
	if (numSlice2D > kMaxSlice2D) { // check again after reading, as top portion of header does not report image types or isotropics
		printError("Increase kMaxSlice2D from %d to at least %d (or use dicm2nii).\n", kMaxSlice2D, numSlice2D);
		return d;
	}
	if (numSlice2D > kMaxDTI4D) { // since issue460, kMaxSlice2D == kMaxSlice4D, so we should never get here
		printError("Increase kMaxDTI4D from %d to at least %d (or use dicm2nii).\n", kMaxDTI4D, numSlice2D);
		return d;
	}
	d.manufacturer = kMANUFACTURER_PHILIPS;
	d.isValid = true;
	d.isSigned = true;
	// remove unused volumes - this will happen if unless we have all 4 image types: real, imag, mag, phase
	maxVol = 0;
	for (int i = 0; i < kMaxDTI4D; i++) {
		if (dti4D->TE[i] > -1.0) {
			dti4D->TE[maxVol] = dti4D->TE[i];
			dti4D->triggerDelayTime[maxVol] = dti4D->triggerDelayTime[i];
			// dti4D->intenIntercept[maxVol] = dti4D->intenIntercept[i];
			// dti4D->intenScale[maxVol] = dti4D->intenScale[i];
			// dti4D->intenScalePhilips[maxVol] = dti4D->intenScalePhilips[i];
			dti4D->isReal[maxVol] = dti4D->isReal[i];
			dti4D->isImaginary[maxVol] = dti4D->isImaginary[i];
			dti4D->isPhase[maxVol] = dti4D->isPhase[i];
			dti4D->S[maxVol].V[0] = dti4D->S[i].V[0];
			dti4D->S[maxVol].V[1] = dti4D->S[i].V[1];
			dti4D->S[maxVol].V[2] = dti4D->S[i].V[2];
			dti4D->S[maxVol].V[3] = dti4D->S[i].V[3];
			maxVol = maxVol + 1;
		}
	}
	if (d.CSA.numDti > 0)
		d.CSA.numDti = maxVol; // e.g. gradient 2 can skip B=0 but include isotropic
	// remove unused slices - this will happen if unless we have all 4 image types: real, imag, mag, phase
	int slice = 0;
	for (int i = 0; i < kMaxSlice2D; i++) {
		if (dti4D->sliceOrder[i] > -1) { // this slice was populated
			dti4D->sliceOrder[slice] = dti4D->sliceOrder[i];
			slice = slice + 1;
		}
	}
	if (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++;
#ifdef USING_DCM2NIIXFSWRAPPER
	// fix ubuntu18 compiler error
	nType = (nType > 1) ? nType : 1;
#else
	nType = std::max(nType, 1);
#endif
	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*types = %d*%d*%d*%d*%d*%d*%d*%d*%d\n",
					 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
					 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels, nType);
		d.isValid = false;
	}
	for (int i = 0; i < numSlice2D; i++) { // issue363
		if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0])
			d.isScaleVariesEnh = true;
		if (dti4D->intenScale[i] != dti4D->intenScale[0])
			d.isScaleVariesEnh = true;
		if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0])
			d.isScaleVariesEnh = true;
		// printf("%g --> %g\n", dti4D->intenIntercept[i], dti4D->intenScale[i]);
	}
	if (d.isScaleVariesEnh) { // juggle to sorted order, required for subsequent rescaling
		printWarning("PAR/REC intensity scaling varies between slices (please validate output).\n");
		TDTI4D tmp;
		for (int i = 0; i < numSlice2D; i++) { // issue363
			tmp.intenIntercept[i] = dti4D->intenIntercept[i];
			tmp.intenScale[i] = dti4D->intenScale[i];
			tmp.intenScalePhilips[i] = dti4D->intenScalePhilips[i];
		}
		for (int i = 0; i < numSlice2D; i++) {
			int j = dti4D->sliceOrder[i];
			dti4D->intenIntercept[i] = tmp.intenIntercept[j];
			dti4D->intenScale[i] = tmp.intenScale[j];
			dti4D->intenScalePhilips[i] = tmp.intenScalePhilips[j];
		}
	}
	d.isScaleOrTEVaries = true;
	if (numSlice2D > kMaxSlice2D) {
		printError("Overloaded slice re-ordering. Number of slices (%d) exceeds kMaxSlice2D (%d)\n", numSlice2D, kMaxSlice2D);
		dti4D->sliceOrder[0] = -1;
		dti4D->intenScale[0] = 0.0;
	}
	if ((maxSlice - minSlice + 1) != d.xyzDim[3]) {
		int numSlice = (maxSlice - minSlice) + 1;
		printWarning("Expected %d slices, but found %d (%d..%d). %s\n", d.xyzDim[3], numSlice, minSlice, maxSlice, parname);
		if (numSlice <= 0)
			d.isValid = false;
		d.xyzDim[3] = numSlice;
		num2DExpected = d.xyzDim[3] * num3DExpected;
	}
	if ((maxBValue <= 0.0f) && (maxDyn > minDyn) && (maxDynTime > minDynTime)) { // use max vs min Dyn instead of && (d.CSA.numDti > 1)
		int numDyn = (maxDyn - minDyn) + 1;
		if (numDyn != maxNumberOfDynamics) {
			printWarning("Expected %d dynamics, but found %d (%d..%d).\n", maxNumberOfDynamics, numDyn, minDyn, maxDyn);
			maxNumberOfDynamics = numDyn;
			num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes;
			num2DExpected = d.xyzDim[3] * num3DExpected;
		}
		float TRms = 1000.0f * (maxDynTime - minDynTime) / (float)(numDyn - 1); //-1 for fence post
		if (fabs(TRms - d.TR) > 0.005f)
			printWarning("Reported TR=%gms, measured TR=%gms (prospect. motion corr.?)\n", d.TR, TRms);
		d.TR = TRms;
	}
	if ((isTypeWarning) && ((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0)) {
		num2DExpected = numSlice2D;
	}
	if (((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0)) {
		num2DExpected = d.xyzDim[3] * (int)(numSlice2D / d.xyzDim[3]);
		if (!ADCwarning)
			printWarning("More volumes than described in header (ADC or isotropic?)\n");
	}
	if ((numSlice2D % num2DExpected) != 0) {
		printMessage("Found %d slices, but expected divisible by %d: slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d %s\n", numSlice2D, num2DExpected,
					 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
					 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels, parname);
		d.isValid = false;
	}
	if (dynNotAscending) {
		printWarning("PAR file volumes not saved in ascending temporal order (please check re-ordering)\n");
	}
	if ((slice % d.xyzDim[3]) != 0) {
		printError("Total number of slices (%d) not divisible by slices per 3D volume (%d) [acquisition aborted]. Try dicm2nii or R2AGUI: %s\n", slice, d.xyzDim[3], parname);
		d.isValid = false;
		return d;
	}
	d.xyzDim[4] = slice / d.xyzDim[3];
	d.locationsInAcquisition = d.xyzDim[3];
	if (ADCwarning)
		printWarning("PAR/REC dataset includes derived (isotropic, ADC, etc) map(s) that could disrupt analysis. Please remove volume and ensure vectors are reported correctly\n");
	if (isIntenScaleVaries)
		printWarning("Intensity slope/intercept varies between slices! [check resulting images]\n");
	if ((isVerbose) && (d.isValid)) {
		printMessage("  slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n",
					 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
					 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels);
	}
	if ((d.xyzDim[3] > 1) && (minSlice == 1) && (maxSlice > minSlice)) { // issue 273
		float dx[4];
		dx[1] = (d.patientPosition[1] - d.patientPositionLast[1]);
		dx[2] = (d.patientPosition[2] - d.patientPositionLast[2]);
		dx[3] = (d.patientPosition[3] - d.patientPositionLast[3]);
		// compute error using 3D pythagorean theorm
		float sliceMM = sqrt(pow(dx[1], 2) + pow(dx[2], 2) + pow(dx[3], 2));
		sliceMM = sliceMM / (maxSlice - minSlice);
		if (!(isSameFloatGE(sliceMM, d.xyzMM[3]))) {
			// if (d.xyzMM[3] > 0.0)
			printWarning("Distance between slices reported by slice gap+thick does not match estimate from slice positions (issue 273).\n");
			d.xyzMM[3] = sliceMM;
		}
	} // issue 273
	printMessage("Done reading PAR header version %.1f, with %d slices\n", (float)parVers / 10, numSlice2D);
	// see Xiangrui Li 's dicm2nii (also BSD license)
	//  http://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter
	//  Rotation order and signs are figured out by trial and error, not 100% sure
	float d2r = (float)(M_PI / 180.0);
	vec3 ca = setVec3(cos(d.angulation[1] * d2r), cos(d.angulation[2] * d2r), cos(d.angulation[3] * d2r));
	vec3 sa = setVec3(sin(d.angulation[1] * d2r), sin(d.angulation[2] * d2r), sin(d.angulation[3] * d2r));
	mat33 rx, ry, rz;
	LOAD_MAT33(rx, 1.0f, 0.0f, 0.0f, 0.0f, ca.v[0], -sa.v[0], 0.0f, sa.v[0], ca.v[0]);
	LOAD_MAT33(ry, ca.v[1], 0.0f, sa.v[1], 0.0f, 1.0f, 0.0f, -sa.v[1], 0.0f, ca.v[1]);
	LOAD_MAT33(rz, ca.v[2], -sa.v[2], 0.0f, sa.v[2], ca.v[2], 0.0f, 0.0f, 0.0f, 1.0f);
	mat33 R = nifti_mat33_mul(rx, ry);
	R = nifti_mat33_mul(R, rz);
	ivec3 ixyz = setiVec3(1, 2, 3);
	if (d.sliceOrient == kSliceOrientSag) {
		ixyz = setiVec3(2, 3, 1);
		for (int r = 0; r < 3; r++)
			for (int c = 0; c < 3; c++)
				if (c != 1)
					R.m[r][c] = -R.m[r][c]; // invert first and final columns
	} else if (d.sliceOrient == kSliceOrientCor) {
		ixyz = setiVec3(1, 3, 2);
		for (int r = 0; r < 3; r++)
			R.m[r][2] = -R.m[r][2]; // invert rows of final column
	}
	R = nifti_mat33_reorder_cols(R, ixyz); // dicom rotation matrix
	d.orient[1] = R.m[0][0];
	d.orient[2] = R.m[1][0];
	d.orient[3] = R.m[2][0];
	d.orient[4] = R.m[0][1];
	d.orient[5] = R.m[1][1];
	d.orient[6] = R.m[2][1];
	mat33 diag;
	LOAD_MAT33(diag, d.xyzMM[1], 0.0f, 0.0f, 0.0f, d.xyzMM[2], 0.0f, 0.0f, 0.0f, d.xyzMM[3]);
	R = nifti_mat33_mul(R, diag);
	mat44 R44;
	LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.stackOffcentre[1],
			   R.m[1][0], R.m[1][1], R.m[1][2], d.stackOffcentre[2],
			   R.m[2][0], R.m[2][1], R.m[2][2], d.stackOffcentre[3]);
	vec3 x;
	if (parVers > 40) // guess
		x = setVec3(((float)d.xyzDim[1] - 1) / 2, ((float)d.xyzDim[2] - 1) / 2, ((float)d.xyzDim[3] - 1) / 2);
	else
		x = setVec3((float)d.xyzDim[1] / 2, (float)d.xyzDim[2] / 2, ((float)d.xyzDim[3] - 1) / 2);
	mat44 eye;
	LOAD_MAT44(eye, 1.0f, 0.0f, 0.0f, x.v[0],
			   0.0f, 1.0f, 0.0f, x.v[1],
			   0.0f, 0.0f, 1.0f, x.v[2]);
	eye = nifti_mat44_inverse(eye); // we wish to compute R/eye, so compute invEye and calculate R*invEye
	R44 = nifti_mat44_mul(R44, eye);
	vec4 y;
	y.v[0] = 0.0f;
	y.v[1] = 0.0f;
	y.v[2] = (float)d.xyzDim[3] - 1.0f;
	y.v[3] = 1.0f;
	y = nifti_vect44mat44_mul(y, R44);
	int iOri = 2; // for axial, slices are 3rd dimension (indexed from 0) (k)
	if (d.sliceOrient == kSliceOrientSag)
		iOri = 0; // for sagittal, slices are 1st dimension (i)
	if (d.sliceOrient == kSliceOrientCor)
		iOri = 1;		   // for coronal, slices are 2nd dimension (j)
	if (d.xyzDim[3] > 1) { // detect and fix Philips Bug
		// Est: assuming "image offcentre (ap,fh,rl in mm )" is correct
		float stackOffcentreEst[4];
		stackOffcentreEst[1] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5;
		stackOffcentreEst[2] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5;
		stackOffcentreEst[3] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5;
		// compute error using 3D pythagorean theorm
		stackOffcentreEst[0] = sqrt(pow(stackOffcentreEst[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreEst[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreEst[3] - d.stackOffcentre[3], 2));
		// Est: assuming "image offcentre (ap,fh,rl in mm )" is stored in order rl,ap,fh
		float stackOffcentreRev[4];
		stackOffcentreRev[1] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5;
		stackOffcentreRev[2] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5;
		stackOffcentreRev[3] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5;
		// compute error using 3D pythagorean theorm
		stackOffcentreRev[0] = sqrt(pow(stackOffcentreRev[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreRev[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreRev[3] - d.stackOffcentre[3], 2));
		// detect, report and fix error
		if ((stackOffcentreEst[0] > 1.0) && (stackOffcentreRev[0] < stackOffcentreEst[0])) {
			// error detected: the ">1.0" handles the low precision of the "Off Centre" values
			printMessage("Order of 'image offcentre (ap,fh,rl in mm )' appears incorrect (assuming rl,ap,fh)\n");
			printMessage(" err[ap,fh,rl]= %g (%g %g %g) \n", stackOffcentreEst[0], stackOffcentreEst[1], stackOffcentreEst[2], stackOffcentreEst[3]);
			printMessage(" err[rl,ap,fh]= %g (%g %g %g) \n", stackOffcentreRev[0], stackOffcentreRev[1], stackOffcentreRev[2], stackOffcentreRev[3]);
			printMessage(" orient\t%d\tOffCentre 1st->mid->nth\t%g\t%g\t%g\t->\t%g\t%g\t%g\t->\t%g\t%g\t%g\t=\t%g\t%s\n", iOri,
						 d.patientPosition[1], d.patientPosition[2], d.patientPosition[3],
						 d.stackOffcentre[1], d.stackOffcentre[2], d.stackOffcentre[3],
						 d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3], (d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]), parname);
			// correct patientPosition
			for (int i = 1; i < 4; i++)
				stackOffcentreRev[i] = d.patientPosition[i];
			d.patientPosition[1] = stackOffcentreRev[2];
			d.patientPosition[2] = stackOffcentreRev[3];
			d.patientPosition[3] = stackOffcentreRev[1];
			// correct patientPositionLast
			for (int i = 1; i < 4; i++)
				stackOffcentreRev[i] = d.patientPositionLast[i];
			d.patientPositionLast[1] = stackOffcentreRev[2];
			d.patientPositionLast[2] = stackOffcentreRev[3];
			d.patientPositionLast[3] = stackOffcentreRev[1];
		} // if bug: report and fix
	} // if 3D data
	bool flip = false;
	// assume head first supine
	if ((iOri == 0) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) > 0)))
		flip = true; // 6/2018 : TODO, not sure if this is >= or >
	if ((iOri == 1) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0)))
		flip = true; //<= not <, leslie_dti_6_1.PAR
	if ((iOri == 2) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0)))
		flip = true; //<= not <, see leslie_dti_3_1.PAR
	if (flip) {
		// if ((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) < 0) {
		// if (( (y.v[iOri]-R44.m[iOri][3])>0 ) == ( (y.v[iOri]-d.stackOffcentre[iOri+1])>0 ) ) {
		d.patientPosition[1] = R44.m[0][3];
		d.patientPosition[2] = R44.m[1][3];
		d.patientPosition[3] = R44.m[2][3];
		d.patientPositionLast[1] = y.v[0];
		d.patientPositionLast[2] = y.v[1];
		d.patientPositionLast[3] = y.v[2];
		// printWarning(" Flipping slice order: please verify %s\n", parname);
	} else {
		// printWarning(" NOT Flipping slice order: please verify %s\n", parname);
		d.patientPosition[1] = y.v[0];
		d.patientPosition[2] = y.v[1];
		d.patientPosition[3] = y.v[2];
		d.patientPositionLast[1] = R44.m[0][3];
		d.patientPositionLast[2] = R44.m[1][3];
		d.patientPositionLast[3] = R44.m[2][3];
	}
	// finish up
	changeExt(parname, "REC");
#ifndef _MSC_VER // Linux is case sensitive, #include <unistd.h>
	if (access(parname, F_OK) != 0)
		changeExt(parname, "rec");
#endif
	d.locationsInAcquisition = d.xyzDim[3];
	d.imageStart = 0;
	if (d.CSA.numDti >= kMaxDTI4D) {
		printError("Unable to convert DTI [increase kMaxDTI4D] found %d directions\n", d.CSA.numDti);
		printMessage("  slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels);
		d.CSA.numDti = 0;
	};
	// check if dimensions vary
	if (maxVol > 0) { // maxVol indexed from 0
		for (int i = 1; i <= maxVol; i++) {
			// if (dti4D->gradDynVol[i] > d.maxGradDynVol) d.maxGradDynVol = dti4D->gradDynVol[i];
			// issue363 slope/intercept can vary for each 2D slice, not only between 3D volumes in a 4D time series
			// if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleOrTEVaries = true;
			// if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleOrTEVaries = true;
			// if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleOrTEVaries = true;
			if (dti4D->isPhase[i] != dti4D->isPhase[0])
				d.isScaleOrTEVaries = true;
			if (dti4D->isReal[i] != dti4D->isReal[0])
				d.isScaleOrTEVaries = true;
			if (dti4D->isImaginary[i] != dti4D->isImaginary[0])
				d.isScaleOrTEVaries = true;
			if (dti4D->triggerDelayTime[i] != dti4D->triggerDelayTime[0])
				d.isScaleOrTEVaries = true;
		}
		// if (d.isScaleOrTEVaries)
		//	printWarning("Varying dimensions (echoes, phase maps, intensity scaling) will require volumes to be saved separately (hint: you may prefer dicm2nii output)\n");
	}
	// if (d.CSA.numDti > 1)
	//	for (int i = 0; i < d.CSA.numDti; i++)
	//		printMessage("%d\tb=\t%g\tv=\t%g\t%g\t%g\n",i,dti4D->S[i].V[0],dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]);
	// check DTI makes sense
	if (d.CSA.numDti > 1) {
		bool v1varies = false;
		bool v2varies = false;
		bool v3varies = false;
		for (int i = 1; i < d.CSA.numDti; i++) {
			if (dti4D->S[0].V[1] != dti4D->S[i].V[1])
				v1varies = true;
			if (dti4D->S[0].V[2] != dti4D->S[i].V[2])
				v2varies = true;
			if (dti4D->S[0].V[3] != dti4D->S[i].V[3])
				v3varies = true;
		}
		if ((!v1varies) || (!v2varies) || (!v3varies))
			printError("Bizarre b-vectors %s\n", parname);
	}
	if ((maxEcho > 1) || (maxCardiac > 1))
		printWarning("Multiple Echo (%d) or Cardiac (%d). Carefully inspect output\n", maxEcho, maxCardiac);
	if ((maxEcho > 1) || (maxCardiac > 1))
		d.isScaleOrTEVaries = true;
	return d;
} // nii_readParRec()

size_t nii_SliceBytes(struct nifti_1_header hdr) {
	// size of 2D slice
	size_t imgsz = hdr.bitpix / 8;
	for (int i = 1; i < 3; i++)
		if (hdr.dim[i] > 1)
			imgsz = imgsz * hdr.dim[i];
	return imgsz;
} // nii_SliceBytes()

size_t nii_ImgBytes(struct nifti_1_header hdr) {
	size_t imgsz = hdr.bitpix / 8;
	for (int i = 1; i < 8; i++)
		if (hdr.dim[i] > 1)
			imgsz = imgsz * hdr.dim[i];
	return imgsz;
} // nii_ImgBytes()

// unsigned char * nii_demosaic(unsigned char* inImg, struct nifti_1_header *hdr, int nMosaicSlices, int ProtocolSliceNumber1) {
unsigned char *nii_demosaic(unsigned char *inImg, struct nifti_1_header *hdr, int nMosaicSlices, bool isUIH) {
	// demosaic http://nipy.org/nibabel/dicom/dicom_mosaic.html
	if (nMosaicSlices < 2)
		return inImg;
	// Byte inImg[ [img length] ];
	//[img getBytes:&inImg length:[img length]];
	int nCol = (int)ceil(sqrt((double)nMosaicSlices));
	int nRow = nCol;
	// n.b. Siemens store 20 images as 5x5 grid, UIH as 5rows, 4 Col https://github.com/rordenlab/dcm2niix/issues/225
	if (isUIH)
		nRow = ceil((float)nMosaicSlices / (float)nCol);
	// printf("%d = %dx%d\n", nMosaicSlices, nCol, nRow);
	int colBytes = hdr->dim[1] / nCol * hdr->bitpix / 8;
	int lineBytes = hdr->dim[1] * hdr->bitpix / 8;
	int rowBytes = hdr->dim[1] * hdr->dim[2] / nRow * hdr->bitpix / 8;
	int col = 0;
	int row = 0;
	int lOutPos = 0;
	hdr->dim[1] = hdr->dim[1] / nCol;
	hdr->dim[2] = hdr->dim[2] / nRow;
	hdr->dim[3] = nMosaicSlices;
	size_t imgsz = nii_ImgBytes(*hdr);
	unsigned char *outImg = (unsigned char *)malloc(imgsz);
	for (int m = 1; m <= nMosaicSlices; m++) {
		int lPos = (row * rowBytes) + (col * colBytes);
		for (int y = 0; y < hdr->dim[2]; y++) {
			memcpy(&outImg[lOutPos], &inImg[lPos], colBytes); // dest, src, bytes
			lPos += lineBytes;
			lOutPos += colBytes;
		}
		col++;
		if (col >= nCol) {
			row++;
			col = 0;
		} // start new column
	} // for m = each mosaic slice
	free(inImg);
	return outImg;
} // nii_demosaic()

unsigned char *nii_flipImgY(unsigned char *bImg, struct nifti_1_header *hdr) {
	// DICOM row order opposite from NIfTI
	int dim3to7 = 1;
	for (int i = 3; i < 8; i++)
		if (hdr->dim[i] > 1)
			dim3to7 = dim3to7 * hdr->dim[i];
	size_t lineBytes = hdr->dim[1] * hdr->bitpix / 8;
	if ((hdr->datatype == DT_RGB24) && (hdr->bitpix == 24) && (hdr->intent_code == NIFTI_INTENT_NONE)) {
		// we use the intent code to indicate planar vs triplet...
		lineBytes = hdr->dim[1];
		dim3to7 = dim3to7 * 3;
	} // rgb data saved planar (RRR..RGGGG..GBBB..B
	unsigned char *line = (unsigned char *)malloc(sizeof(unsigned char) * (lineBytes));
	size_t sliceBytes = hdr->dim[2] * lineBytes;
	int halfY = hdr->dim[2] / 2;		   // note truncated toward zero, so halfY=2 regardless of 4 or 5 columns
	for (int sl = 0; sl < dim3to7; sl++) { // for each 2D slice
		size_t slBottom = (size_t)sl * sliceBytes;
		size_t slTop = (((size_t)sl + 1) * sliceBytes) - lineBytes;
		for (int y = 0; y < halfY; y++) {
			// swap order of lines
			memcpy(line, &bImg[slBottom], lineBytes); // memcpy(&line, &bImg[slBottom], lineBytes);
			memcpy(&bImg[slBottom], &bImg[slTop], lineBytes);
			memcpy(&bImg[slTop], line, lineBytes); // tpx memcpy(&bImg[slTop], &line, lineBytes);
			slTop -= lineBytes;
			slBottom += lineBytes;
		} // for y
	} // for each slice
	free(line);
	return bImg;
} // nii_flipImgY()

unsigned char *nii_flipImgZ(unsigned char *bImg, struct nifti_1_header *hdr) {
	// DICOM row order opposite from NIfTI
	int halfZ = hdr->dim[3] / 2; // note truncated toward zero, so halfY=2 regardless of 4 or 5 columns
	if (halfZ < 1)
		return bImg;
	int dim4to7 = 1;
	for (int i = 4; i < 8; i++)
		if (hdr->dim[i] > 1)
			dim4to7 = dim4to7 * hdr->dim[i];
	size_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8;
	size_t volBytes = sliceBytes * hdr->dim[3];
	unsigned char *slice = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes));
	for (int vol = 0; vol < dim4to7; vol++) { // for each 2D slice
		size_t slBottom = vol * volBytes;
		size_t slTop = ((vol + 1) * volBytes) - sliceBytes;
		for (int z = 0; z < halfZ; z++) {
			// swap order of lines
			memcpy(slice, &bImg[slBottom], sliceBytes); // TPX memcpy(&slice, &bImg[slBottom], sliceBytes);
			memcpy(&bImg[slBottom], &bImg[slTop], sliceBytes);
			memcpy(&bImg[slTop], slice, sliceBytes); // TPX
			slTop -= sliceBytes;
			slBottom += sliceBytes;
		} // for Z
	} // for each volume
	free(slice);
	return bImg;
} // nii_flipImgZ()

unsigned char *nii_flipZ(unsigned char *bImg, struct nifti_1_header *h) {
	// flip slice order
	if (h->dim[3] < 2)
		return bImg;
	mat33 s;
	mat44 Q44;
	LOAD_MAT33(s, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_y[0], h->srow_y[1], h->srow_y[2],
			   h->srow_z[0], h->srow_z[1], h->srow_z[2]);
	LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3],
			   h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3],
			   h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]);
	vec4 v = setVec4(0.0f, 0.0f, (float)h->dim[3] - 1.0f);
	v = nifti_vect44mat44_mul(v, Q44); // after flip this voxel will be the origin
	mat33 mFlipZ;
	LOAD_MAT33(mFlipZ, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f);
	s = nifti_mat33_mul(s, mFlipZ);
	LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0],
			   s.m[1][0], s.m[1][1], s.m[1][2], v.v[1],
			   s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]);
	// printMessage(" ----------> %f %f %f\n",v.v[0],v.v[1],v.v[2]);
	setQSForm(h, Q44, true);
	// printMessage("nii_flipImgY dims %dx%dx%d %d \n",h->dim[1],h->dim[2], dim3to7,h->bitpix/8);
	return nii_flipImgZ(bImg, h);
} // nii_flipZ()

unsigned char *nii_flipY(unsigned char *bImg, struct nifti_1_header *h) {
	mat33 s;
	mat44 Q44;
	LOAD_MAT33(s, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_y[0], h->srow_y[1], h->srow_y[2],
			   h->srow_z[0], h->srow_z[1], h->srow_z[2]);
	LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3],
			   h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3],
			   h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]);
	vec4 v = setVec4(0, (float)h->dim[2] - 1, 0);
	v = nifti_vect44mat44_mul(v, Q44); // after flip this voxel will be the origin
	mat33 mFlipY;
	LOAD_MAT33(mFlipY, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	s = nifti_mat33_mul(s, mFlipY);
	LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0],
			   s.m[1][0], s.m[1][1], s.m[1][2], v.v[1],
			   s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]);
	setQSForm(h, Q44, true);
	// printMessage("nii_flipImgY dims %dx%d %d \n",h->dim[1],h->dim[2], h->bitpix/8);
	return nii_flipImgY(bImg, h);
} // nii_flipY()

void conv1bit16bit(unsigned char *img, struct nifti_1_header hdr) { // issue572
	printWarning("Support for images that allocate 1 bits is experimental\n");
	int nVox = (int)nii_ImgBytes(hdr) / (hdr.bitpix / 8);
	for (int i = (nVox - 1); i >= 0; i--) {
		int ibyte = i >> 3; // byte to sample
		int ibit = (i % 8); // bit 0..7
		// if (highBit > 0)
		//	ibit = 7 - ibit;
		int val = img[ibyte] >> ibit;
		img[i] = (val & 1);
	}
} // conv1bit16bit()

void conv12bit16bit(unsigned char *img, struct nifti_1_header hdr) {
	// convert 12-bit allocated data to 16-bit
	//  works for MR-MONO2-12-angio-an1 from http://www.barre.nom.fr/medical/samples/
	//  looks wrong: this sample toggles between big and little endian stores
	int nVox = (int)nii_ImgBytes(hdr) / (hdr.bitpix / 8);
	for (int i = (nVox - 1); i >= 0; i--) {
		int i16 = i * 2;
		int i12 = floor(i * 1.5);
		uint16_t val;
		if ((i % 2) != 1) {
			val = img[i12 + 1] + (img[i12 + 0] << 8);
			val = val >> 4;
		} else {
			val = img[i12 + 0] + (img[i12 + 1] << 8);
		}
		img[i16 + 0] = val & 0xFF;
		img[i16 + 1] = (val >> 8) & 0xFF;
	}
} // conv12bit16bit()

unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bitsAllocated, int imageStart32) {
	size_t imgsz = nii_ImgBytes(hdr);
	size_t imgszRead = imgsz;
	size_t imageStart = imageStart32;
	if (bitsAllocated == 1)
		imgszRead = (imgsz + 7) >> 3;
	if (bitsAllocated == 12)
		imgszRead = round(imgsz * 0.75);
	FILE *file = fopen(imgname, "rb");
	if (!file) {
		printError("Unable to open '%s'\n", imgname);
		return NULL;
	}
// fseek(file, 0, SEEK_END);
// long fileLen = ftell(file);
#ifdef _MSC_VER
	_fseeki64(file, 0, SEEK_END);
	size_t fileLen = _ftelli64(file);
#else
	fseeko(file, 0, SEEK_END);	   // Windows _fseeki64
	size_t fileLen = ftello(file); // Windows _ftelli64
#endif
	if (fileLen < (imgszRead + imageStart)) {
		// note hdr.vox_offset is a float: issue507
		// https://www.nitrc.org/forum/message.php?msg_id=27155
		printMessage("FileSize < (ImageSize+HeaderSize): %zu < (%zu+%zu) \n", fileLen, imgszRead, imageStart);
		printWarning("File not large enough to store image data: %s\n", imgname);
		return NULL;
	}
	fseek(file, (long)imageStart, SEEK_SET);
	unsigned char *bImg = (unsigned char *)malloc(imgsz);
	// int i = 0;
	// while (bImg[i] == 0) i++;
	// printMessage("%d %d<\n",i,bImg[i]);
	size_t sz = fread(bImg, 1, imgszRead, file);
	fclose(file);
	if (sz < imgszRead) {
		printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname);
		return NULL;
	}
	if (bitsAllocated == 1)
		conv1bit16bit(bImg, hdr);
	if (bitsAllocated == 12)
		conv12bit16bit(bImg, hdr);
	return bImg;
} // nii_loadImgCore()

unsigned char *nii_planar2rgb(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar) {
	if (bImg == NULL || hdr->datatype != DT_RGB24 || isPlanar == 0)
		return bImg;
	int nx = hdr->dim[1];
	int ny = hdr->dim[2];
	int nSlice = 1;
	for (int i = 3; i < 8; i++)
		if (hdr->dim[i] > 1)
			nSlice *= hdr->dim[i];
	size_t nPix = nx * ny;
	size_t sliceSizePlanar = nPix * 3; // planar size: RRR...RGGG...GBBB...
	// Allocate temporary buffer to hold the RGB interleaved data for one slice
	unsigned char *tempRGB = (unsigned char *)malloc(nPix * 3);
	if (tempRGB == NULL)
		return NULL;
	for (int sl = 0; sl < nSlice; sl++) {
		size_t base = sl * sliceSizePlanar;
		unsigned char *r = &bImg[base + 0 * nPix];
		unsigned char *g = &bImg[base + 1 * nPix];
		unsigned char *b = &bImg[base + 2 * nPix];
		for (size_t i = 0; i < nPix; i++) {
			tempRGB[i * 3 + 0] = r[i];
			tempRGB[i * 3 + 1] = g[i];
			tempRGB[i * 3 + 2] = b[i];
		}
		memcpy(&bImg[base], tempRGB, nPix * 3); // copy interleaved RGB back
	}
	free(tempRGB);
	return bImg;
}

static inline uint8_t clamp255(int x) {
	return (x < 0) ? 0 : (x > 255 ? 255 : x);
}

unsigned char *nii_ybr2rgb(unsigned char *bImg, struct nifti_1_header *hdr) {
	// YBR->RGB: PhotometricInterpretation (0028,0004) YBR_FULL
	// ITU-R BT.601 YCbCr → RGB (full-range) transform 
	if (bImg == NULL)
		return NULL;
	if (hdr->datatype != DT_RGB24)
		return bImg;
	size_t dim3to7 = 1;
	for (int i = 3; i < 8; i++)
		if (hdr->dim[i] > 1)
			dim3to7 = dim3to7 * hdr->dim[i];
	size_t sliceBytes8 = hdr->dim[1] * hdr->dim[2];
	size_t sliceBytes24 = sliceBytes8 * 3;
	size_t sliceOffsetR = 0;
	for (int sl = 0; sl < dim3to7; sl++) {					// for each 2D slice
		size_t sliceOffsetG = sliceOffsetR + sliceBytes8;
		size_t sliceOffsetB = sliceOffsetR + 2 * sliceBytes8;
		for (int rgb = 0; rgb < sliceBytes8; rgb++) {
			int Y = bImg[sliceOffsetR + rgb];
			int Cb = bImg[sliceOffsetG + rgb];
			int Cr = bImg[sliceOffsetB + rgb];
			float r = Y + 1.402f   * (Cr - 128);
			float g = Y - 0.344136f * (Cb - 128) - 0.714136f * (Cr - 128);
			float b = Y + 1.772f   * (Cb - 128);
			bImg[sliceOffsetR + rgb] = (uint8_t)clamp255((int)(r + 0.5f));
			bImg[sliceOffsetG + rgb] = (uint8_t)clamp255((int)(g + 0.5f));
			bImg[sliceOffsetB + rgb] = (uint8_t)clamp255((int)(b + 0.5f));
		}
		sliceOffsetR += sliceBytes24;
	} // for each slice
	return bImg;
}

unsigned char *nii_rgb2planar(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar) {
	// DICOM data saved in triples RGBRGBRGB, Analyze RGB saved in planes RRR..RGGG..GBBBB..B
	if (bImg == NULL)
		return NULL;
	if (hdr->datatype != DT_RGB24)
		return bImg;
	if (isPlanar == 1)
		return bImg; // return nii_bgr2rgb(bImg,hdr);
	int dim3to7 = 1;
	for (int i = 3; i < 8; i++)
		if (hdr->dim[i] > 1)
			dim3to7 = dim3to7 * hdr->dim[i];
	int sliceBytes8 = hdr->dim[1] * hdr->dim[2];
	int sliceBytes24 = sliceBytes8 * 3;
	unsigned char *slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24));
	// printMessage("rgb->planar %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7);
	int sliceOffsetR = 0;
	for (int sl = 0; sl < dim3to7; sl++) {					// for each 2D slice
		memcpy(slice24, &bImg[sliceOffsetR], sliceBytes24); // TPX memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24);
		int sliceOffsetG = sliceOffsetR + sliceBytes8;
		int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8;
		int i = 0;
		int j = 0;
		for (int rgb = 0; rgb < sliceBytes8; rgb++) {
			bImg[sliceOffsetR + j] = slice24[i++];
			bImg[sliceOffsetG + j] = slice24[i++];
			bImg[sliceOffsetB + j] = slice24[i++];
			j++;
		}
		sliceOffsetR += sliceBytes24;
	} // for each slice
	free(slice24);
	return bImg;
} // nii_rgb2Planar()

unsigned char *nii_iVaries(unsigned char *img, struct nifti_1_header *hdr, struct TDTI4D *dti4D) {
	// each DICOM image can have its own intensity scaling, whereas NIfTI requires the same scaling for all images in a file
	// WARNING: do this BEFORE nii_check16bitUnsigned!!!!
	// if (hdr->datatype != DT_INT16) return img;
	int dim3to7 = 1;
	for (int i = 3; i < 8; i++)
		if (hdr->dim[i] > 1)
			dim3to7 = dim3to7 * hdr->dim[i];
	int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7;
	if (nVox < 1)
		return img;
	float *img32 = (float *)malloc(nVox * sizeof(float));
	if (hdr->datatype == DT_UINT8) {
		uint8_t *img8i = (uint8_t *)img;
		for (int i = 0; i < nVox; i++)
			img32[i] = img8i[i];
	} else if (hdr->datatype == DT_UINT16) {
		uint16_t *img16ui = (uint16_t *)img;
		for (int i = 0; i < nVox; i++)
			img32[i] = img16ui[i];
	} else if (hdr->datatype == DT_INT16) {
		int16_t *img16i = (int16_t *)img;
		for (int i = 0; i < nVox; i++)
			img32[i] = img16i[i];
	} else if (hdr->datatype == DT_INT32) {
		int32_t *img32i = (int32_t *)img;
		for (int i = 0; i < nVox; i++)
			img32[i] = (float)img32i[i];
	}
	free(img);												// release previous image
	if ((dti4D != NULL) && (dti4D->intenScale[0] != 0.0)) { // enhanced dataset, intensity varies across slices of a single file
		if (dti4D->RWVScale[0] != 0.0)
			printWarning("Intensity scale/slope using 0028,1053 and 0028,1052\n"); // to do: real-world values and precise values
		int dim1to2 = hdr->dim[1] * hdr->dim[2];
		int slice = -1;
		//(0028,1052) SS = scale slope (2005,100E) RealWorldIntercept = (0040,9224) Real World Slope = (0040,9225)
		// printf("vol\tRS(0028,1053)\tRI(0028,1052)\tSS(2005,100E)\trwS(0040,9225)\trwI(0040,9224)\n");
		for (int i = 0; i < nVox; i++) { // issue 363
			if ((i % dim1to2) == 0) {
				slice++;
				// printf("%d\t%g\t%g\t%g\t%g\t%g\n", slice, dti4D->intenScale[slice], dti4D->intenIntercept[slice],dti4D->intenScalePhilips[slice], dti4D->RWVScale[slice], dti4D->RWVIntercept[slice]);
			}
			img32[i] = (img32[i] * dti4D->intenScale[slice]) + dti4D->intenIntercept[slice];
		}
	} else { //
		for (int i = 0; i < nVox; i++)
			img32[i] = (img32[i] * hdr->scl_slope) + hdr->scl_inter;
	}
	hdr->scl_slope = 1;
	hdr->scl_inter = 0;
	hdr->datatype = DT_FLOAT;
	hdr->bitpix = 32;
	return (unsigned char *)img32;
} // nii_iVaries()

unsigned char *nii_reorderSlicesX(unsigned char *bImg, struct nifti_1_header *hdr, struct TDTI4D *dti4D) {
	// Philips can save slices in any random order... rearrange all of them
	int dim3to7 = 1;
	for (int i = 3; i < 8; i++)
		if (hdr->dim[i] > 1)
			dim3to7 = dim3to7 * hdr->dim[i];
	if (dim3to7 < 2)
		return bImg;
	if (dim3to7 > kMaxSlice2D)
		return bImg;
	uint64_t imgSz = nii_ImgBytes(*hdr);
	uint64_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8;
	unsigned char *outImg = (unsigned char *)malloc(imgSz);
	memcpy(&outImg[0], &bImg[0], imgSz);
	for (int i = 0; i < dim3to7; i++) { // for each volume
		int fromSlice = dti4D->sliceOrder[i];
		// if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice);
		// printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", fromSlice, i);
		if ((i < 0) || (fromSlice >= dim3to7)) {
			printError("Re-ordered slice out-of-volume %d\n", fromSlice);
		} else if (i != fromSlice) {
			uint64_t inPos = fromSlice * sliceBytes;
			uint64_t outPos = i * sliceBytes;
			memcpy(&bImg[outPos], &outImg[inPos], sliceBytes);
		}
	}
	free(outImg);
	return bImg;
}

unsigned char *nii_byteswap(unsigned char *img, struct nifti_1_header *hdr) {
	if (hdr->bitpix < 9)
		return img;
	uint64_t nvox = nii_ImgBytes(*hdr) / (hdr->bitpix / 8);
	void *ar = (void *)img;
	if (hdr->bitpix == 16)
		nifti_swap_2bytes(nvox, ar);
	if (hdr->bitpix == 32)
		nifti_swap_4bytes(nvox, ar);
	if (hdr->bitpix == 64)
		nifti_swap_8bytes(nvox, ar);
	return img;
} // nii_byteswap()

#ifdef myEnableJasper
unsigned char *nii_loadImgCoreJasper(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) {
#if defined(JAS_VERSION_MAJOR) && JAS_VERSION_MAJOR >= 3
	jas_conf_clear();
	jas_conf_set_debug_level(0);
	jas_conf_set_max_mem_usage(1 << 30); // 1 GiB
	if (jas_init_library()) {
		return NULL;
	}
	if (jas_init_thread()) {
		jas_cleanup_library();
		return NULL;
	}
#else
	if (jas_init()) {
		return NULL;
	}
	jas_setdbglevel(0);
#endif
	jas_stream_t *in;
	jas_image_t *image;
	if (!(in = jas_stream_fopen(imgname, "rb"))) {
		printError("Cannot open input image file %s\n", imgname);
		return NULL;
	}
	// int isSeekable = jas_stream_isseekable(in);
	jas_stream_seek(in, dcm.imageStart, 0);
	int infmt = jas_image_getfmt(in);
	if (infmt < 0) {
		printError("Input image has unknown format %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes);
		return NULL;
	}
	char opt[] = "\0";
	char *inopts = opt;
	if (!(image = jas_image_decode(in, infmt, inopts))) {
		printError("Cannot decode image data %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes);
		return NULL;
	}
	int numcmpts;
	int cmpts[4];
	switch (jas_clrspc_fam(jas_image_clrspc(image))) {
	case JAS_CLRSPC_FAM_RGB:
		if (jas_image_clrspc(image) != JAS_CLRSPC_SRGB)
			printWarning("Inaccurate color\n");
		numcmpts = 3;
		if ((cmpts[0] = jas_image_getcmptbytype(image,
												JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R))) < 0 ||
			(cmpts[1] = jas_image_getcmptbytype(image,
												JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G))) < 0 ||
			(cmpts[2] = jas_image_getcmptbytype(image,
												JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B))) < 0) {
			printError("Missing color component\n");
			return NULL;
		}
		break;
	case JAS_CLRSPC_FAM_GRAY:
		if (jas_image_clrspc(image) != JAS_CLRSPC_SGRAY)
			printWarning("Inaccurate color\n");
		numcmpts = 1;
		if ((cmpts[0] = jas_image_getcmptbytype(image,
												JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y))) < 0) {
			printError("Missing color component\n");
			return NULL;
		}
		break;
	default:
		printError("Unsupported color space\n");
		return NULL;
		break;
	}
	int width = jas_image_cmptwidth(image, cmpts[0]);
	int height = jas_image_cmptheight(image, cmpts[0]);
	int prec = jas_image_cmptprec(image, cmpts[0]);
	int sgnd = jas_image_cmptsgnd(image, cmpts[0]);
#ifdef MY_DEBUG
	printMessage("offset %d w*h %d*%d bpp %d sgnd %d components %d '%s' Jasper=%s\n", dcm.imageStart, width, height, prec, sgnd, numcmpts, imgname, jas_getversion());
#endif
	for (int cmptno = 0; cmptno < numcmpts; ++cmptno) {
		if (jas_image_cmptwidth(image, cmpts[cmptno]) != width ||
			jas_image_cmptheight(image, cmpts[cmptno]) != height ||
			jas_image_cmptprec(image, cmpts[cmptno]) != prec ||
			jas_image_cmptsgnd(image, cmpts[cmptno]) != sgnd ||
			jas_image_cmpthstep(image, cmpts[cmptno]) != jas_image_cmpthstep(image, 0) ||
			jas_image_cmptvstep(image, cmpts[cmptno]) != jas_image_cmptvstep(image, 0) ||
			jas_image_cmpttlx(image, cmpts[cmptno]) != jas_image_cmpttlx(image, 0) ||
			jas_image_cmpttly(image, cmpts[cmptno]) != jas_image_cmpttly(image, 0)) {
			printMessage("The NIfTI format cannot be used to represent an image with this geometry.\n");
			return NULL;
		}
	}
	// extract the data
	int bpp = (prec + 7) >> 3; // e.g. 12 bits requires 2 bytes
	int imgbytes = bpp * width * height * numcmpts;
	if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) {
		printError("Catastrophic decompression error\n");
		return NULL;
	}
	jas_seqent_t v;
	unsigned char *img = (unsigned char *)malloc(imgbytes);
	uint16_t *img16ui = (uint16_t *)img; // unsigned 16-bit
	int16_t *img16i = (int16_t *)img;	 // signed 16-bit
	if (sgnd)
		bpp = -bpp;
	if (bpp == -1) {
		printError("Signed 8-bit DICOM?\n");
		return NULL;
	}
	jas_matrix_t *data;
	jas_seqent_t *d;
	data = 0;
	int cmptno, y, x;
	int pix = 0;
	for (cmptno = 0; cmptno < numcmpts; ++cmptno) {
		if (!(data = jas_matrix_create(1, width))) {
			free(img);
			return NULL;
		}
	}
	// n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB
	for (cmptno = 0; cmptno < numcmpts; ++cmptno) {
		for (y = 0; y < height; ++y) {
			if (jas_image_readcmpt(image, cmpts[cmptno], 0, y, width, 1, data)) {
				free(img);
				return NULL;
			}
			d = jas_matrix_getref(data, 0, 0);
			for (x = 0; x < width; ++x) {
				v = *d;
				switch (bpp) {
				case 1:
					img[pix] = v;
					break;
				case 2:
					img16ui[pix] = v;
					break;
				case -2:
					img16i[pix] = v;
					break;
				}
				pix++;
				++d;
			} // for x
		} // for y
	} // for each component
	jas_matrix_destroy(data);
	jas_image_destroy(image);
	jas_image_clearfmts();
#if defined(JAS_VERSION_MAJOR) && JAS_VERSION_MAJOR >= 3
	jas_cleanup_thread();
	jas_cleanup_library();
#endif
	return img;
} // nii_loadImgCoreJasper()
#endif

struct TJPEG {
	long offset;
	long size;
};

TJPEG *decode_JPEG_SOF_0XC3_stack(const char *fn, int skipBytes, int isVerbose, int frames, bool isLittleEndian) {
#define abortGoto()  \
	free(lOffsetRA); \
	return NULL;
	TJPEG *lOffsetRA = (TJPEG *)malloc(frames * sizeof(TJPEG));
	FILE *reader = fopen(fn, "rb");
	fseek(reader, 0, SEEK_END);
	long lRawSz = ftell(reader) - skipBytes;
	if (lRawSz <= 8) {
		printError("Unable to open %s\n", fn);
		abortGoto(); // read failure
	}
	fseek(reader, skipBytes, SEEK_SET);
	unsigned char *lRawRA = (unsigned char *)malloc(lRawSz);
	size_t lSz = fread(lRawRA, 1, lRawSz, reader);
	fclose(reader);
	if (lSz < (size_t)lRawSz) {
		printError("Unable to read %s\n", fn);
		abortGoto(); // read failure
	}
	long lRawPos = 0; // starting position
	int frame = 0;
	while ((frame < frames) && ((lRawPos + 10) < lRawSz)) {
		int tag = dcmInt(4, &lRawRA[lRawPos], isLittleEndian);
		lRawPos += 4; // read tag
		int tagLength = dcmInt(4, &lRawRA[lRawPos], isLittleEndian);
		long tagEnd = lRawPos + tagLength + 4;
		if (isVerbose)
			printMessage("Frame %d Tag %#x length %d end at %ld\n", frame + 1, tag, tagLength, tagEnd + skipBytes);
		lRawPos += 4; // read tag length
		if ((lRawRA[lRawPos] != 0xFF) || (lRawRA[lRawPos + 1] != 0xD8) || (lRawRA[lRawPos + 2] != 0xFF)) {
			if (isVerbose)
				printWarning("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn);
		} else {
			lOffsetRA[frame].offset = lRawPos + skipBytes;
			lOffsetRA[frame].size = tagLength;
			frame++;
		}
		lRawPos = tagEnd;
	}
	free(lRawRA);
	if (frame < frames) {
		printMessage("Only found %d of %d JPEG fragments. Please use dcmdjpeg or gdcmconv to uncompress data.\n", frame, frames);
		abortGoto();
	}
	return lOffsetRA;
}

unsigned char *nii_loadImgJPEGC3(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int isVerbose) {
	// arcane and inefficient lossless compression method popularized by dcmcjpeg, examples at http://www.osirix-viewer.com/resources/dicom-image-library/
	int dimX, dimY, bits, frames;
	// clock_t start = clock();
	//  https://github.com/rii-mango/JPEGLosslessDecoderJS/blob/master/tests/data/jpeg_lossless_sel1-8bit.dcm
	// N.B. this current code can not extract a 2D image that is saved as multiple fragments, for example see the JPLL files at
	//  ftp://medical.nema.org/MEDICAL/Dicom/DataSets/WG04/
	// Live javascript code that can handle these is at
	//  https://github.com/chafey/cornerstoneWADOImageLoader
	// I have never seen these segmented images in the wild, so we will simply warn the user if we encounter such a file
	// int Sz = JPEG_SOF_0XC3_sz (imgname, (dcm.imageStart - 4), dcm.isLittleEndian);
	// printMessage("Sz %d %d\n", Sz, dcm.imageBytes );
	// This behavior is legal but appears extremely rare
	// ftp://medical.nema.org/medical/dicom/final/cp900_ft.pdf
	if (65536 == dcm.imageBytes)
		printError("One frame may span multiple fragments. SOFxC3 lossless JPEG. Please extract with dcmdjpeg or gdcmconv.\n");
	unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, dcm.imageStart, isVerbose, &dimX, &dimY, &bits, &frames, 0);
	if (ret == NULL) {
		printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n");
		return NULL;
	}
	if (hdr.dim[3] != frames) { // multi-slice image saved as multiple image fragments rather than a single image
		// printMessage("Unable to decode all slices (%d/%d). Please use dcmdjpeg to uncompress data.\n", frames, hdr.dim[3]);
		if (ret != NULL)
			free(ret);
		TJPEG *offsetRA = decode_JPEG_SOF_0XC3_stack(imgname, dcm.imageStart - 8, isVerbose, hdr.dim[3], dcm.isLittleEndian);
		if (offsetRA == NULL)
			return NULL;
		size_t slicesz = nii_SliceBytes(hdr);
		size_t imgsz = slicesz * hdr.dim[3];
		size_t pos = 0;
		unsigned char *bImg = (unsigned char *)malloc(imgsz);
		for (int frame = 0; frame < hdr.dim[3]; frame++) {
			if (isVerbose)
				printMessage("JPEG frame %d has %ld bytes @ %ld\n", frame, offsetRA[frame].size, offsetRA[frame].offset);
			unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, (int)offsetRA[frame].offset, false, &dimX, &dimY, &bits, &frames, (int)offsetRA[frame].size);
			if (ret == NULL) {
				printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n");
				free(bImg);
				return NULL;
			}
			memcpy(&bImg[pos], ret, slicesz); // dest, src, size
			free(ret);
			pos += slicesz;
		}
		free(offsetRA);
		return bImg;
	}
	return ret;
}

#ifndef F_OK
#define F_OK 0 /* existence check */
#endif

#ifndef myDisableClassicJPEG

#ifdef myTurboJPEG // if turboJPEG instead of nanoJPEG for classic JPEG decompression

// unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) {
	// decode classic JPEG using nanoJPEG
	// printMessage("50 offset %d\n", dcm.imageStart);
	if ((dcm.samplesPerPixel != 1) && (dcm.samplesPerPixel != 3)) {
		printError("%d components (expected 1 or 3) in a JPEG image '%s'\n", dcm.samplesPerPixel, imgname);
		return NULL;
	}
	if (access(imgname, F_OK) == -1) {
		printError("Unable to find '%s'\n", imgname);
		return NULL;
	}
	// load compressed data
	FILE *f = fopen(imgname, "rb");
	size_t _jpegSize = dcm.imageBytes;
	if (_jpegSize < 8) {
		printError("File too small\n");
		fclose(f);
		return NULL;
	}
	unsigned char *_compressedImage = (unsigned char *)malloc(_jpegSize);
	fseek(f, dcm.imageStart, SEEK_SET);
	_jpegSize = (long unsigned int)fread(_compressedImage, 1, _jpegSize, f);
	fclose(f);
	int jpegSubsamp, width, height;
	// printMessage("Decoding with turboJPEG\n");
	tjhandle _jpegDecompressor = tjInitDecompress();
	tjDecompressHeader2(_jpegDecompressor, _compressedImage, _jpegSize, &width, &height, &jpegSubsamp);
	int COLOR_COMPONENTS = dcm.samplesPerPixel;
	// printMessage("turboJPEG h*w %d*%d sampling %d components %d\n", width, height, jpegSubsamp, COLOR_COMPONENTS);
	if ((jpegSubsamp == TJSAMP_GRAY) && (COLOR_COMPONENTS != 1)) {
		printError("Grayscale jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname);
	}
	if ((jpegSubsamp != TJSAMP_GRAY) && (COLOR_COMPONENTS != 3)) {
		printError("Color jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname);
	}
	// unsigned char bImg[width*height*COLOR_COMPONENTS]; //!< will contain the decompressed image
	unsigned char *bImg = (unsigned char *)malloc(width * height * COLOR_COMPONENTS);
	if (COLOR_COMPONENTS == 1) // TJPF_GRAY
		tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_GRAY, TJFLAG_FASTDCT);
	else
		tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_RGB, TJFLAG_FASTDCT);
	// printMessage("turboJPEG h*w %d*%d (sampling %d)\n", width, height, jpegSubsamp);
	tjDestroy(_jpegDecompressor);
	return bImg;
}

#else // if turboJPEG else use nanojpeg...

// unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) {
	// decode classic JPEG using nanoJPEG
	// printMessage("50 offset %d\n", dcm.imageStart);
	if (dcm.imageBytes < 8) {
		printError("File too small '%s'\n", imgname);
		return NULL;
	}
	if (access(imgname, F_OK) == -1) {
		printError("Unable to find '%s'\n", imgname);
		return NULL;
	}
	// load compressed data
	FILE *f = fopen(imgname, "rb");
	size_t size = dcm.imageBytes;
	char *buf = (char *)malloc(size);
	fseek(f, dcm.imageStart, SEEK_SET);
	size = (int)fread(buf, 1, size, f);
	fclose(f);
	// decode
	njInit();
	if (njDecode(buf, size)) {
		printError("Unable to decode baseline JPEG image offset %d bytes %d (hint compile dcm2niix with turboJPEG).\n", dcm.imageStart, dcm.imageBytes);
		return NULL;
	}
	free(buf);
	unsigned char *bImg = (unsigned char *)malloc(njGetImageSize());
	memcpy(bImg, njGetImage(), njGetImageSize()); // dest, src, size
	njDone();
	return bImg;
}
#endif
#endif

uint32_t rleInt(int lIndex, unsigned char lBuffer[], bool swap) { // read binary 32-bit integer
	uint32_t retVal = 0;
	memcpy(&retVal, (char *)&lBuffer[lIndex * 4], 4);
	if (!swap)
		return retVal;
	uint32_t swapVal;
	char *inInt = (char *)&retVal;
	char *outInt = (char *)&swapVal;
	outInt[0] = inInt[3];
	outInt[1] = inInt[2];
	outInt[2] = inInt[1];
	outInt[3] = inInt[0];
	return swapVal;
} // rleInt()

unsigned char *nii_loadImgPMSCT_RLE1(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
	// Transfer Syntax 1.3.46.670589.33.1.4.1 also handled by TomoVision and GDCM's rle2img
	// https://github.com/malaterre/GDCM/blob/a923f206060e85e8d81add565ae1b9dd7b210481/Examples/Cxx/rle2img.cxx
	// see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1
	if (dcm.imageBytes < 66) { // 64 for header+ 2 byte minimum image
		printError("%d is not enough bytes for PMSCT_RLE1 compression '%s'\n", dcm.imageBytes, imgname);
		return NULL;
	}
	int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8);
	if (bytesPerSample != 2) { // there is an RGB variation of this format, but we have not seen it in the wild
		printError("PMSCT_RLE1 should be 16-bits per sample (please report on Github and use pmsct_rgb1).\n");
		return NULL;
	}
	FILE *file = fopen(imgname, "rb");
	if (!file) {
		printError("Unable to open %s\n", imgname);
		return NULL;
	}
	fseek(file, 0, SEEK_END);
	long fileLen = ftell(file);
	if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) {
		printMessage("File not large enough to store image data: %s\n", imgname);
		fclose(file);
		return NULL;
	}
	fseek(file, (long)dcm.imageStart, SEEK_SET);
	size_t imgsz = nii_ImgBytes(hdr);
	char *cImg = (char *)malloc(dcm.imageBytes); // compressed input
	size_t sz = fread(cImg, 1, dcm.imageBytes, file);
	fclose(file);
	if (sz < (size_t)dcm.imageBytes) {
		printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname);
		free(cImg);
		return NULL;
	}
	if ((int)imgsz == dcm.imageBytes) { // Handle special case that data is not compressed:
		return (unsigned char *)cImg;
	}
	unsigned char *bImg = (unsigned char *)malloc(imgsz); // binary output
	// RLE pass: compressed -> temp (bImg -> tImg)
	char *tImg = (char *)malloc(imgsz); // temp output
	size_t o = 0;
	for (size_t i = 0; (int)i < dcm.imageBytes; ++i) {
		if (cImg[i] == (char)0xa5) {
			int repeat = (unsigned char)cImg[i + 1] + 1;
			char value = cImg[i + 2];
			while (repeat) {
				tImg[o] = value;
				o++;
				--repeat;
			}
			i += 2;
		} else {
			tImg[o] = cImg[i];
			o++;
		}
	} // for i
	free(cImg);
	size_t tempsize = o;
	// Looks like this RLE is pretty ineffective...
	//  printMessage("RLE %d -> %d\n", dcm.imageBytes, o);
	// Delta encoding pass: temp -> output (tImg -> bImg)
	unsigned short delta = 0;
	o = 0;
	size_t n16 = (int)imgsz >> 1;
	unsigned short *bImg16 = (unsigned short *)bImg;
	for (size_t i = 0; i < tempsize; ++i) {
		if (tImg[i] == (unsigned char)0x5a) {
			unsigned char v1 = (unsigned char)tImg[i + 1];
			unsigned char v2 = (unsigned char)tImg[i + 2];
			unsigned short value = (unsigned short)(v2 * 256 + v1);
			if (o < n16)
				bImg16[o] = value;
			o++;
			delta = value;
			i += 2;
		} else {
			unsigned short value = (unsigned short)(tImg[i] + delta);
			if (o < n16)
				bImg16[o] = value;
			o++;
			delta = value;
		}
	} // for i
	// printMessage("Delta %d -> %d (of %d)\n", tempsize, 2*(o-1), imgsz);
	free(tImg);
	return bImg;
} // nii_loadImgPMSCT_RLE1()

unsigned char *nii_loadImgRLE(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
	// decompress PackBits run-length encoding https://en.wikipedia.org/wiki/PackBits
	if (dcm.imageBytes < 66) { // 64 for header+ 2 byte minimum image
		printError("%d is not enough bytes for RLE compression '%s'\n", dcm.imageBytes, imgname);
		return NULL;
	}
	FILE *file = fopen(imgname, "rb");
	if (!file) {
		printError("Unable to open %s\n", imgname);
		return NULL;
	}
	fseek(file, 0, SEEK_END);
	long fileLen = ftell(file);
	if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) {
		printMessage("File not large enough to store image data: %s\n", imgname);
		fclose(file);
		return NULL;
	}
	fseek(file, (long)dcm.imageStart, SEEK_SET);
	size_t imgsz = nii_ImgBytes(hdr);
	unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); // compressed input
	size_t sz = fread(cImg, 1, dcm.imageBytes, file);
	fclose(file);
	if (sz < (size_t)dcm.imageBytes) {
		printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname);
		free(cImg);
		return NULL;
	}
	// read header http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html
	bool swap = (dcm.isLittleEndian != littleEndianPlatform());
	int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8);
	uint32_t bytesPerSampleRLE = rleInt(0, cImg, swap);
	if ((bytesPerSample < 0) || (bytesPerSampleRLE != (uint32_t)bytesPerSample)) {
		printError("RLE header corrupted %d != %d\n", bytesPerSampleRLE, bytesPerSample);
		free(cImg);
		return NULL;
	}
	unsigned char *bImg = (unsigned char *)malloc(imgsz); // binary output
	for (size_t i = 0; i < imgsz; i++)
		bImg[i] = 0;
	for (int i = 0; i < bytesPerSample; i++) {
		uint32_t offset = rleInt(i + 1, cImg, swap);
		if ((dcm.imageBytes < 0) || (offset > (uint32_t)dcm.imageBytes)) {
			printError("RLE header error\n");
			free(cImg);
			free(bImg);
			return NULL;
		}
		// save in platform's endian:
		//  The first Segment is generated by stripping off the most significant byte of each Padded Composite Pixel Code...
		size_t vx = i;
		if ((dcm.samplesPerPixel == 1) && (littleEndianPlatform())) // endian, except for RGB
			vx = (bytesPerSample - 1) - i;
		while (vx < imgsz) {
			int8_t n = (int8_t)cImg[offset];
			offset++;
			// http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html
			// if ((n >= 0) && (n <= 127)) { //not needed: int8_t always <=127
			if (n >= 0) { // literal bytes
				int reps = 1 + (int)n;
				for (int r = 0; r < reps; r++) {
					int8_t v = cImg[offset];
					offset++;
					if (vx >= imgsz)
						; // printMessage("literal overflow %d %d\n", r, reps);
					else
						bImg[vx] = v;
					vx = vx + bytesPerSample;
				}
			} else if ((n <= -1) && (n >= -127)) { // repeated run
				int8_t v = cImg[offset];
				offset++;
				int reps = -(int)n + 1;
				for (int r = 0; r < reps; r++) {
					if (vx >= imgsz)
						; // printMessage("repeat overflow %d\n", reps);
					else
						bImg[vx] = v;
					vx = vx + bytesPerSample;
				}
			}; // n.b. we ignore -128!
		} // while vx < imgsz
	} // for i < bytesPerSample
	free(cImg);
	return bImg;
} // nii_loadImgRLE()

#ifdef myDisableOpenJPEG
#ifndef myEnableJasper
// avoid compiler warning, see https://stackoverflow.com/questions/3599160/unused-parameter-warnings-in-c
#define UNUSED(x) (void)(x)
#endif
#endif

#if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) // Support for JPEG-LS
// JPEG-LS: Transfer Syntaxes 1.2.840.10008.1.2.4.80 1.2.840.10008.1.2.4.81

#ifdef myEnableJPEGLS1 // use CharLS v1.* requires c++03
//-std=c++03 -DmyEnableJPEGLS1 charls1/header.cpp charls1/jpegls.cpp charls1/jpegmarkersegment.cpp charls1/interface.cpp charls1/jpegstreamwriter.cpp
#include "charls1/interface.h"
#else // use latest release of CharLS: CharLS 2.x requires c++14
//-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp
#include "charls/charls.h"
#endif
#include "charls/publictypes.h"

unsigned char *nii_loadImgJPEGLS(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
	// load compressed data
	FILE *file = fopen(imgname, "rb");
	if (!file) {
		printError("Unable to open %s\n", imgname);
		return NULL;
	}
	fseek(file, 0, SEEK_END);
	long fileLen = ftell(file);
	if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) {
		printMessage("File not large enough to store JPEG-LS data: %s\n", imgname);
		fclose(file);
		return NULL;
	}
	fseek(file, (long)dcm.imageStart, SEEK_SET);
	unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); // compressed input
	size_t sz = fread(cImg, 1, dcm.imageBytes, file);
	fclose(file);
	if (sz < (size_t)dcm.imageBytes) {
		printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname);
		free(cImg);
		return NULL;
	}
	// create buffer for uncompressed data
	size_t imgsz = nii_ImgBytes(hdr);
	unsigned char *bImg = (unsigned char *)malloc(imgsz); // binary output
	JlsParameters params = {};
#ifdef myEnableJPEGLS1
	if (JpegLsReadHeader(cImg, dcm.imageBytes, &params) != OK) {
#else
	using namespace charls;
	if (JpegLsReadHeader(cImg, dcm.imageBytes, &params, nullptr) != ApiResult::OK) {
#endif
		printMessage("CharLS failed to read header.\n");
		return NULL;
	}
#ifdef myEnableJPEGLS1
	if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, &params) != OK) {
#else
	if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, &params, nullptr) != ApiResult::OK) {
#endif
		free(bImg);
		printMessage("CharLS failed to read image.\n");
		return NULL;
	}
	return (bImg);
}
#endif

unsigned char *nii_loadImgXLCore(char *imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D) {
	// provided with a filename (imgname) and DICOM header (dcm), creates NIfTI header (hdr) and img
	// n.b. must ALWAYS be called from nii_loadImgXLCore()
	unsigned char *img;
	if (dcm.compressionScheme == kCompress50) {
#ifdef myDisableClassicJPEG
		printMessage("Software not compiled to decompress classic JPEG DICOM images\n");
		return NULL;
#else
		img = nii_loadImgJPEG50(imgname, dcm);
		if (hdr->datatype == DT_RGB24)						 // convert to planar
			img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); // do this BEFORE Y-Flip, or RGB order can be flipped
		// n.b. turboJPEG and nanoJPEG should both automatically convert YBR to RGB
		// if (dcm.isYBRfull)
		//   img = nii_ybr2rgb(img, hdr);
#endif
	} else if (dcm.compressionScheme == kCompressJPEGLS) {
#if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1)
		img = nii_loadImgJPEGLS(imgname, *hdr, dcm);
		if (hdr->datatype == DT_RGB24)						 // convert to planar
			img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); // do this BEFORE Y-Flip, or RGB order can be flipped
		if (dcm.isYBRfull)
			img = nii_ybr2rgb(img, hdr);
#else
		printMessage("Software not compiled to decompress JPEG-LS DICOM images\n");
		return NULL;
#endif
	} else if (dcm.compressionScheme == kCompressPMSCT_RLE1) {
		img = nii_loadImgPMSCT_RLE1(imgname, *hdr, dcm);
	} else if (dcm.compressionScheme == kCompressRLE) {
		img = nii_loadImgRLE(imgname, *hdr, dcm);
		if (hdr->datatype == DT_RGB24)						 // convert to planar
			img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); // do this BEFORE Y-Flip, or RGB order can be flipped
	} else if (dcm.compressionScheme == kCompressC3) {
		img = nii_loadImgJPEGC3(imgname, *hdr, dcm, isVerbose);
		if (dcm.isYBRfull)
			img = nii_ybr2rgb(img, hdr);
	} else
#ifndef myDisableOpenJPEG
		if (((dcm.compressionScheme == kCompress50) || (dcm.compressionScheme == kCompressYes)) && (compressFlag != kCompressNone)) {
			img = nii_loadImgCoreOpenJPEG(imgname, *hdr, dcm, compressFlag);
			if (dcm.isYBRfull)
				img = nii_ybr2rgb(img, hdr);
		}
	else
#else
#ifdef myEnableJasper
		if ((dcm.compressionScheme == kCompressYes) && (compressFlag != kCompressNone))
		img = nii_loadImgCoreJasper(imgname, *hdr, dcm, compressFlag);
	else
#endif
#endif
		if (dcm.compressionScheme == kCompressYes) {
		printMessage("%d Unable to decompress DICOM transfer syntax '%s'\n", compressFlag, dcm.transferSyntax);
		return NULL;
	} else
		img = nii_loadImgCore(imgname, *hdr, dcm.bitsAllocated, dcm.imageStart);
	if (img == NULL)
		return img;
	if ((dcm.compressionScheme == kCompressNone) && (dcm.isLittleEndian != littleEndianPlatform()) && (hdr->bitpix > 8))
		img = nii_byteswap(img, hdr);
	if ((dcm.compressionScheme == kCompressNone) && (hdr->datatype == DT_RGB24)) {
		img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); // do this BEFORE Y-Flip, or RGB order can be flipped
		if (dcm.isYBRfull)
			img = nii_ybr2rgb(img, hdr);
	}
	dcm.isPlanarRGB = true;
	if (dcm.CSA.mosaicSlices > 1) {
		img = nii_demosaic(img, hdr, dcm.CSA.mosaicSlices, (dcm.manufacturer == kMANUFACTURER_UIH)); //, dcm.CSA.protocolSliceNumber1);
	}
	if ((dti4D == NULL) && (!dcm.isFloat) && (iVaries)) // must do after
		img = nii_iVaries(img, hdr, NULL);
	int nAcq = dcm.locationsInAcquisition;
	if ((nAcq > 1) && (hdr->dim[0] < 4) && ((hdr->dim[3] % nAcq) == 0) && (hdr->dim[3] > nAcq)) {
		hdr->dim[4] = hdr->dim[3] / nAcq;
		hdr->dim[3] = nAcq;
		hdr->dim[0] = 4;
	}
	if ((dti4D != NULL) && (dti4D->sliceOrder[0] >= 0))
		img = nii_reorderSlicesX(img, hdr, dti4D);
	if ((dti4D != NULL) && (!dcm.isFloat) && (iVaries))
		img = nii_iVaries(img, hdr, dti4D);
	headerDcm2NiiSForm(dcm, dcm, hdr, false);
	return img;
} // nii_loadImgXLCore()

unsigned char *nii_loadImgXL(char *imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D) {
	// provided with a filename (imgname) and DICOM header (dcm), creates NIfTI header (hdr) and img
	if (headerDcm2Nii(dcm, hdr, true) == EXIT_FAILURE)
		return NULL;
	if (dcm.offsetTableItems <= 1) 
		return nii_loadImgXLCore(imgname, hdr, dcm, iVaries, compressFlag, isVerbose, dti4D);
	int frames = dcm.xyzDim[3];
	if (dcm.xyzDim[4] > 1)
		frames *= dcm.xyzDim[4];
	if (frames != dcm.offsetTableItems)
	printMessage("Number of frames %d does not match offset table %d\n", frames, dcm.offsetTableItems);
	size_t bpp = hdr->bitpix / 8;
	size_t sliceBytes2D = dcm.xyzDim[1] * dcm.xyzDim[2] * bpp;
	if (sliceBytes2D == 0) {
		printError("DICOM header does not make sense\n");
		return NULL;
	}
	unsigned char *img  = (unsigned char *)malloc(sliceBytes2D * frames);
	if (!img) return NULL;
	struct nifti_1_header *hdr2D = (struct nifti_1_header *)malloc(sizeof(struct nifti_1_header));
	if (!hdr2D) {
		printError("Memory allocation failed for hdr2D\n");
		free(img);
		return NULL;
	}
	memcpy(hdr2D, hdr, sizeof(struct nifti_1_header));
	for (int i = 3; i < 8; i++)
		 hdr2D->dim[i] = 1;
	int lastimageBytes = dcm.imageBytes;
	for (int i = 0; i < frames; i++) {
		dcm.imageStart = dti4D->offsetTable[i];
		dcm.imageBytes = lastimageBytes;
		if (i < (frames - 1))
			dcm.imageBytes = dti4D->offsetTable[i+1] - dcm.imageStart;
		unsigned char *img2D = nii_loadImgXLCore(imgname, hdr2D, dcm, iVaries, compressFlag, isVerbose, dti4D);
		if (!img2D) {
			printError("Failed to decode frame %d/%d offset: %d bytes: %d format: %s\n", (i+1), frames, dcm.imageStart, dcm.imageBytes, dcm.transferSyntax);
			free(img);
			free(img2D);
			return NULL;
		}
		// Copy the 2D slice into the correct position in the 3D/4D image buffer
		memcpy(img + i * sliceBytes2D, img2D, sliceBytes2D);
		free(img2D);
	}
	return img;
} // nii_loadImgXL()

int isSQ(uint32_t groupElement, bool isPhilips) { // Detect sequence VR ("SQ") for implicit tags
	static const int array_size = 35;
	uint32_t array[array_size] = {0x0008 + (uint32_t(0x1111) << 16), 0x0008 + (uint32_t(0x1115) << 16), 0x0008 + (uint32_t(0x1140) << 16), 0x0008 + (uint32_t(0x1199) << 16), 0x0008 + (uint32_t(0x2218) << 16), 0x0008 + (uint32_t(0x9092) << 16), 0x0018 + (uint32_t(0x9006) << 16), 0x0018 + (uint32_t(0x9042) << 16), 0x0018 + (uint32_t(0x9045) << 16), 0x0018 + (uint32_t(0x9049) << 16), 0x0018 + (uint32_t(0x9112) << 16), 0x0018 + (uint32_t(0x9114) << 16), 0x0018 + (uint32_t(0x9115) << 16), 0x0018 + (uint32_t(0x9117) << 16), 0x0018 + (uint32_t(0x9119) << 16), 0x0018 + (uint32_t(0x9125) << 16), 0x0018 + (uint32_t(0x9152) << 16), 0x0018 + (uint32_t(0x9176) << 16), 0x0018 + (uint32_t(0x9226) << 16), 0x0018 + (uint32_t(0x9239) << 16), 0x0020 + (uint32_t(0x9071) << 16), 0x0020 + (uint32_t(0x9111) << 16), 0x0020 + (uint32_t(0x9113) << 16), 0x0020 + (uint32_t(0x9116) << 16), 0x0020 + (uint32_t(0x9221) << 16), 0x0020 + (uint32_t(0x9222) << 16), 0x0028 + (uint32_t(0x9110) << 16), 0x0028 + (uint32_t(0x9132) << 16), 0x0028 + (uint32_t(0x9145) << 16), 0x0040 + (uint32_t(0x0260) << 16), 0x0040 + (uint32_t(0x0555) << 16), 0x0040 + (uint32_t(0xa170) << 16), 0x5200 + (uint32_t(0x9229) << 16), 0x5200 + (uint32_t(0x9230) << 16)};
	// uint32_t array[array_size] = {0x2005 + (uint32_t(0x140F) << 16), 0x0008 + (uint32_t(0x1111) << 16), 0x0008 + (uint32_t(0x1115) << 16), 0x0008 + (uint32_t(0x1140) << 16), 0x0008 + (uint32_t(0x1199) << 16), 0x0008 + (uint32_t(0x2218) << 16), 0x0008 + (uint32_t(0x9092) << 16), 0x0018 + (uint32_t(0x9006) << 16), 0x0018 + (uint32_t(0x9042) << 16), 0x0018 + (uint32_t(0x9045) << 16), 0x0018 + (uint32_t(0x9049) << 16), 0x0018 + (uint32_t(0x9112) << 16), 0x0018 + (uint32_t(0x9114) << 16), 0x0018 + (uint32_t(0x9115) << 16), 0x0018 + (uint32_t(0x9117) << 16), 0x0018 + (uint32_t(0x9119) << 16), 0x0018 + (uint32_t(0x9125) << 16), 0x0018 + (uint32_t(0x9152) << 16), 0x0018 + (uint32_t(0x9176) << 16), 0x0018 + (uint32_t(0x9226) << 16), 0x0018 + (uint32_t(0x9239) << 16), 0x0020 + (uint32_t(0x9071) << 16), 0x0020 + (uint32_t(0x9111) << 16), 0x0020 + (uint32_t(0x9113) << 16), 0x0020 + (uint32_t(0x9116) << 16), 0x0020 + (uint32_t(0x9221) << 16), 0x0020 + (uint32_t(0x9222) << 16), 0x0028 + (uint32_t(0x9110) << 16), 0x0028 + (uint32_t(0x9132) << 16), 0x0028 + (uint32_t(0x9145) << 16), 0x0040 + (uint32_t(0x0260) << 16), 0x0040 + (uint32_t(0x0555) << 16), 0x0040 + (uint32_t(0xa170) << 16), 0x5200 + (uint32_t(0x9229) << 16), 0x5200 + (uint32_t(0x9230) << 16)};
	for (int i = 0; i < array_size; i++) {
		// if (array[i] == groupElement) printMessage(" implicitSQ %04x,%04x\n",  groupElement & 65535,groupElement>>16);
		if (array[i] == groupElement)
			return 1;
	}
	// issue 775: Canon/Toshiba use 2005,140f but not as a SQ?
	uint32_t kPhilipsSQ = 0x2005 + (uint32_t(0x140F) << 16);
	if ((isPhilips) && (kPhilipsSQ == groupElement))
		return 1;
	return 0;
} // isSQ()

int isDICOMfile(const char *fname) { // 0=NotDICOM, 1=DICOM, 2=Maybe(not Part 10 compliant)
	// Someday: it might be worthwhile to detect "IMGF" at offset 3228 to warn user if they attempt to convert Signa data
	FILE *fp = fopen(fname, "rb");
	if (!fp)
		return 0;
	fseek(fp, 0, SEEK_END);
	long fileLen = ftell(fp);
	if (fileLen < 256) {
		fclose(fp);
		return 0;
	}
	fseek(fp, 0, SEEK_SET);
	unsigned char buffer[256];
	size_t sz = fread(buffer, 1, 256, fp);
	fclose(fp);
	if (sz < 256)
		return 0;
	if ((buffer[128] == 'D') && (buffer[129] == 'I') && (buffer[130] == 'C') && (buffer[131] == 'M'))
		return 1; // valid DICOM
	if ((buffer[0] == 8) && (buffer[1] == 0) && (buffer[3] == 0))
		return 2; // not valid Part 10 file, perhaps DICOM object
	return 0;
} // isDICOMfile()

// START RIR 12/2017 Robert I. Reid

// Gathering spot for all the info needed to get the b value and direction
// for a volume.
struct TVolumeDiffusion {
	struct TDICOMdata *pdd; // The multivolume
	struct TDTI4D *pdti4D;	// permanent records.
	uint8_t manufacturer;	// kMANUFACTURER_UNKNOWN, kMANUFACTURER_SIEMENS, etc.

	// void set_manufacturer(const uint8_t m) {manufacturer = m; update();} // unnecessary

	// Everything after this in the structure would be private if it were a C++
	// class, but it has been rewritten as a struct for C compatibility. I am
	// using _ as a hint of that, although _ for privacy is not really a
	// universal convention in C. Privacy is desired because immediately
	// any of these are updated _update_tvd() should be called.

	bool _isAtFirstPatientPosition; // Limit b vals and vecs to 1 per volume.

	// float bVal0018_9087; // kDiffusion_b_value, always present in Philips/Siemens.
	// float bVal2001_1003; // kDiffusionBFactor
	//  float dirRL2005_10b0; // kDiffusionDirectionRL
	//  float dirAP2005_10b1; // kDiffusionDirectionAP
	//  float dirFH2005_10b2; // kDiffusionDirectionFH
	//  Philips diffusion scans tend to have a "trace" (average of the diffusion
	//  weighted volumes) volume tacked on, usually but not always at the end,
	//  so b is > 0, but the direction is meaningless.  Most software versions
	//  explicitly set the direction to 0, but version 3 does not, making (0x18,
	//  0x9075) necessary.
	bool _isPhilipsNonDirectional;
	// char _directionality0018_9075[16]; // DiffusionDirectionality, not in Philips 2.6.
	//  float _orientation0018_9089[3]; // kDiffusionOrientation, always present in Philips/Siemens for volumes with a direction.
	// char _seq0018_9117[64]; // MRDiffusionSequence, not in Philips 2.6.
	float _dtiV[4];
	double _symBMatrix[6];
	// uint16_t numDti;
};
struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D);
void clear_volume(struct TVolumeDiffusion *ptvd); // Blank the volume-specific members or set them to impossible values.
void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf);
void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian);
void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, bool iafpp);
int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf);
void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis);
void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, int axis);
void set_bVal(struct TVolumeDiffusion *ptvd, float b);
void set_bMatrix(struct TVolumeDiffusion *ptvd, float b, int component);
void _update_tvd(struct TVolumeDiffusion *ptvd);

struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D) {
	struct TVolumeDiffusion tvd;
	tvd.pdd = ptdd;
	tvd.pdti4D = dti4D;
	clear_volume(&tvd);
	return tvd;
} // initTVolumeDiffusion()

void clear_volume(struct TVolumeDiffusion *ptvd) {
	ptvd->_isAtFirstPatientPosition = false;
	ptvd->manufacturer = kMANUFACTURER_UNKNOWN;
	// bVal0018_9087 = -1;
	// ptvd->_directionality0018_9075[0] = 0;
	// ptvd->seq0018_9117[0] = 0;
	// bVal2001_1003 = -1;
	//  dirRL2005_10b0 = 2;
	//  dirAP2005_10b1 = 2;
	//  dirFH2005_10b2 = 2;
	ptvd->_isPhilipsNonDirectional = false;
	ptvd->_dtiV[0] = -1;
	for (int i = 1; i < 4; ++i)
		ptvd->_dtiV[i] = 2;
	for (int i = 0; i < 6; ++i)
		ptvd->_symBMatrix[i] = NAN;
	// numDti = 0;
} // clear_volume()

void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf) {
	// if(!strncmp(( char*)(inbuf), "BMATRIX", 4)) printf("FOUND BMATRIX----%s\n",inbuf );
	// n.b. strncmp returns 0 if the contents of both strings are equal, for boolean 0 = false!
	//  elsewhere we use strstr() which returns 0/null if match is not present
	if (strncmp((char *)(inbuf), "DIRECTIONAL", 11) && // strncmp = 0 for ==.
													   // strncmp(( char*)(inbuf), "NONE", 4) && //issue 256
		strncmp((char *)(inbuf), "BMATRIX", 7)) {	   // Siemens XA10
		ptvd->_isPhilipsNonDirectional = true;
		// Explicitly set the direction to 0 now, because there may
		// not be a 0018,9089 for this frame.
		for (int i = 1; i < 4; ++i) // 1-3 is intentional.
			ptvd->_dtiV[i] = 0.0;
	} else {
		ptvd->_isPhilipsNonDirectional = false;
		// Wait for 0018,9089 to get the direction.
	}
	_update_tvd(ptvd);
} // set_directionality0018_9075()

int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf) {
	// see Series 16 https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/ge-mr750-dwi-b-vals#table b750 = 1000000750\8\0\0 b1500 = 1000001500\8\0\0
	int bVal = dcmStrInt(lLength, inbuf);
	// GE bias value for b-value https://github.com/rordenlab/dcm2niix/issues/602
	bVal = (bVal % 1000000000);
	ptvd->_dtiV[0] = bVal;
	// printf("(0043,1039) '%s' Slop_int_6 -->%d \n", inbuf, bVal);
	// dd.CSA.numDti = 1; // Always true for GE.
	_update_tvd(ptvd);
	return bVal;
} // set_bValGE()

// axis: 0 -> x, 1 -> y , 2 -> z
void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis) {
	ptvd->_dtiV[axis + 1] = vec;
	// printf("(2005,10b0..2) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]);
	_update_tvd(ptvd);
} // set_diffusion_directionPhilips()

// axis: 0 -> x, 1 -> y , 2 -> z
void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, const int axis) {
	ptvd->_dtiV[axis + 1] = dcmStrFloat(lLength, inbuf);
	// printf("(0019,10bb..d) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]);
	_update_tvd(ptvd);
} // set_diffusion_directionGE()

void dcmMultiFloatDouble(size_t lByteLength, unsigned char lBuffer[], size_t lnFloats, float *lFloats, bool isLittleEndian) {
	size_t floatlen = lByteLength / lnFloats;
	for (size_t i = 0; i < lnFloats; ++i)
		lFloats[i] = dcmFloatDouble((int)floatlen, lBuffer + i * floatlen, isLittleEndian);
} // dcmMultiFloatDouble()

void dcmMultiFloatSingle(size_t lByteLength, unsigned char lBuffer[], size_t lnFloats, float *lFloats, bool isLittleEndian) {
	// dcmFloat kTRArray
	size_t floatlen = lByteLength / lnFloats;
	for (size_t i = 0; i < lnFloats; ++i)
		lFloats[i] = dcmFloat((int)floatlen, lBuffer + i * floatlen, isLittleEndian);
} // dcmMultiFloatSingle()

void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian) {
	if (ptvd->_isPhilipsNonDirectional) {
		for (int i = 1; i < 4; ++i) // Deliberately ignore inbuf; it might be nonsense.
			ptvd->_dtiV[i] = 0.0;
	} else
		dcmMultiFloatDouble(lLength, inbuf, 3, ptvd->_dtiV + 1, isLittleEndian);
	_update_tvd(ptvd);
} // set_orientation0018_9089()

void set_bVal(struct TVolumeDiffusion *ptvd, const float b) {
	ptvd->_dtiV[0] = b;
	_update_tvd(ptvd);
} // set_bVal()

void set_bMatrix(struct TVolumeDiffusion *ptvd, double b, int idx) {
	if ((idx < 0) || (idx > 5))
		return;
	ptvd->_symBMatrix[idx] = b;
	_update_tvd(ptvd);
}

void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, const bool iafpp) {
	ptvd->_isAtFirstPatientPosition = iafpp;
	_update_tvd(ptvd);
} // set_isAtFirstPatientPosition_tvd()

// Update the diffusion info in dd and *pdti4D for a volume once all the
// diffusion info for that volume has been read into pvd.
//
// Note that depending on the scanner software the diffusion info can arrive in
// different tags, in different orders (because of enclosing sequence tags),
// and the values in some tags may be invalid, or may be essential, depending
// on the presence of other tags. Thus it is best to gather all the diffusion
// info for a volume (frame) before taking action on it.
//
// On the other hand, dd and *pdti4D need to be updated as soon as the
// diffusion info is ready, before diffusion info for the next volume is read
// in.
void _update_tvd(struct TVolumeDiffusion *ptvd) {
	// Figure out if we have both the b value and direction (if any) for this
	// volume, and if isFirstPosition.
	// // GE (software version 27) is liable to NOT include kDiffusion_b_value for the
	// // slice if it is 0, but should still have kDiffusionBFactor, which comes
	// // after PatientPosition.
	// if(isAtFirstPatientPosition && manufacturer == kMANUFACTURER_GE && dtiV[0] < 0)
	// dtiV[0] = 0; // Implied 0.
	bool isReady = (ptvd->_isAtFirstPatientPosition && (ptvd->_dtiV[0] >= 0));
	if (!isReady)
		return; // no B=0
	if (isReady) {
		for (int i = 1; i < 4; ++i) {
			// Check that _dtiV is not still at its default of [-1, 2, 2, 2] from
			// clear_volume(struct TVolumeDiffusion *ptvd). This is mostly avoided
			// because of the ptvd->_dtiV[0] >= 0 check above, but was supposedly
			// needed at some point.
			// issue 769: some bvec components may be slightly (~5%) > 1. AFAIK,
			//            the relevant value to guard against would be +2.
			if (ptvd->_dtiV[i] > 1.5) {
				isReady = false;
				break;
			}
		}
	}
	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]))
				isReady = false;
		if (!isReady)
			return; //	symBMatrix not filled
		//START BRUKER KLUDGE
		//see issue 265: Bruker stores xx,xy,xz,yx,yy,yz instead of xx,xy,xz,yy,yz,zz
		// we can recover since xx+yy+zz = bval
		// since any value squared is positive, a negative diagonal reveals fault
		double xx = ptvd->_symBMatrix[0]; //x*x
		double xy = ptvd->_symBMatrix[1]; //x*y
		double xz = ptvd->_symBMatrix[2]; //x*z
		double yy = ptvd->_symBMatrix[3]; //y*y
		double yz = ptvd->_symBMatrix[4]; //y*z
		double zz = ptvd->_symBMatrix[5]; //z*z
		bool isBrukerBug = false;
		if ((xx < 0.0) || (yy < 0.0) || (zz < 0.0))
			isBrukerBug = true;
		double sumDiag = ptvd->_symBMatrix[0] + ptvd->_symBMatrix[3] + ptvd->_symBMatrix[5]; //if correct xx+yy+zz = bval
		double bVecError = fabs(sumDiag - ptvd->pdd->CSA.dtiV[0]);
		if (bVecError > 0.5)
			isBrukerBug = true;
		//next: check diagonals
		double x = sqrt(xx);
		double y = sqrt(yy);
		double z = sqrt(zz);
		if ((fabs((x * y) - xy)) > 0.5)
			isBrukerBug = true;
		if ((fabs((x * z) - xz)) > 0.5)
			isBrukerBug = true;
		if ((fabs((y * z) - yz)) > 0.5)
			isBrukerBug = true;
		if (isBrukerBug)
			printWarning("Fixing corrupt bmat (issue 265). [%g %g %g %g %g %g]\n", xx, xy, xz, yy, yz, zz);
		if (isBrukerBug) {
			ptvd->_symBMatrix[3] = ptvd->_symBMatrix[4];
			ptvd->_symBMatrix[4] = ptvd->_symBMatrix[5];
			//next: solve for zz given bvalue, xx, and yy
			ptvd->_symBMatrix[5] = ptvd->_dtiV[0] - ptvd->_symBMatrix[0] - ptvd->_symBMatrix[3];
			if ((ptvd->_symBMatrix[0] < 0.0) || (ptvd->_symBMatrix[5] < 0.0))
				printError("DICOM BMatrix corrupt.\n");
		}
		//END BRUKER_KLUDGE
		vec3 bVec = nifti_mat33_eig3(ptvd->_symBMatrix[0], ptvd->_symBMatrix[1], ptvd->_symBMatrix[2], ptvd->_symBMatrix[3], ptvd->_symBMatrix[4], ptvd->_symBMatrix[5]);
		ptvd->_dtiV[1] = bVec.v[0];
		ptvd->_dtiV[2] = bVec.v[1];
		ptvd->_dtiV[3] = bVec.v[2];
	}*/
	if (!isReady)
		return;
	// If still here, update dd and *pdti4D.
	ptvd->pdd->CSA.numDti++;
	if (ptvd->pdd->CSA.numDti == 2) {						  // First time we know that this is a 4D DTI dataset;
		for (int i = 0; i < 4; ++i)							  // Start *pdti4D before ptvd->pdd->CSA.dtiV
			ptvd->pdti4D->S[0].V[i] = ptvd->pdd->CSA.dtiV[i]; // is updated.
	}
	for (int i = 0; i < 4; ++i) // Update pdd
		ptvd->pdd->CSA.dtiV[i] = ptvd->_dtiV[i];
	if ((ptvd->pdd->CSA.numDti > 1) && (ptvd->pdd->CSA.numDti < kMaxDTI4D)) { // Update *pdti4D
		// d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI));
		for (int i = 0; i < 4; ++i)
			ptvd->pdti4D->S[ptvd->pdd->CSA.numDti - 1].V[i] = ptvd->_dtiV[i];
	}
	clear_volume(ptvd); // clear the slate for the next volume.
} //_update_tvd()
// END RIR

struct TDCMdim { // DimensionIndexValues
	uint32_t dimIdx[MAX_NUMBER_OF_DIMENSIONS];
	uint32_t diskPos;
	float triggerDelayTime, TE, TR, intenScale, intenIntercept, intenScalePhilips, RWVScale, RWVIntercept;
	float V[4];
	bool isPhase;
	bool isReal;
	bool isImaginary;
};

void getFileNameX(char *pathParent, const char *path, int maxLen) { // if path is c:\d1\d2 then filename is 'd2'
	const char *filename = strrchr(path, '/');						// UNIX
	const char *filenamew = strrchr(path, '\\');					// Windows
	if (filename == NULL)
		filename = filenamew;
	// if ((filename != NULL) && (filenamew != NULL)) filename = std::max(filename, filenamew);
	if ((filename != NULL) && (filenamew != NULL) && (filenamew > filename))
		filename = filenamew; // for mixed file separators, e.g. "C:/dir\filenane.tmp"
	if (filename == NULL) {	  // no path separator
		strcpy(pathParent, path);
		return;
	}
	filename++;
	strncpy(pathParent, filename, maxLen - 1);
}

void getFileName(char *pathParent, const char *path) { // if path is c:\d1\d2 then filename is 'd2'
	getFileNameX(pathParent, path, kDICOMStr);
}

struct fidx {
	float value;
	int index;
};

int fcmp(const void *a, const void *b) {
	struct fidx *a1 = (struct fidx *)a;
	struct fidx *a2 = (struct fidx *)b;
	if ((*a1).value > (*a2).value)
		return 1;
	else if ((*a1).value < (*a2).value)
		return -1;
	else
		return 0;
}

int compareTDCMdim(void const *item1, void const *item2) {
	struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1;
	struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2;
	// for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) {
	for (int i = MAX_NUMBER_OF_DIMENSIONS - 1; i >= 0; i--) {
		if (dcm1->dimIdx[i] < dcm2->dimIdx[i])
			return -1;
		else if (dcm1->dimIdx[i] > dcm2->dimIdx[i])
			return 1;
	}
	return 0;
} // compareTDCMdim()

int compareTDCMdimRev(void const *item1, void const *item2) {
	struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1;
	struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2;
	for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) {
		if (dcm1->dimIdx[i] < dcm2->dimIdx[i])
			return -1;
		else if (dcm1->dimIdx[i] > dcm2->dimIdx[i])
			return 1;
	}
	return 0;
} // compareTDCMdimRev()

struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D *dti4D) {
	// struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) {
	int isVerbose = prefs->isVerbose;
	int compressFlag = prefs->compressFlag;
	struct TDICOMdata d = clear_dicom_data();
	d.imageNum = 0;					 // not set
	strcpy(d.protocolName, "");		 // erase dummy with empty
	strcpy(d.protocolName, "");		 // erase dummy with empty
	strcpy(d.seriesDescription, ""); // erase dummy with empty
	strcpy(d.sequenceName, "");		 // erase dummy with empty
	// do not read folders - code specific to GCC (LLVM/Clang seems to recognize a small file size)
	dti4D->sliceOrder[0] = -1;
	dti4D->volumeOnsetTime[0] = -1;
	dti4D->decayFactor[0] = -1;
	dti4D->frameDuration[0] = -1;
	dti4D->frameReferenceTime[0] = -1;
	dti4D->offsetTable[0] = 0;
	// dti4D->fragmentOffset[0] = -1;
	dti4D->intenScale[0] = 0.0;
	// Ensure dti4D fields are initialised, as in nii_readParRec()
	for (int i = 0; i < kMaxDTI4D; i++) {
		dti4D->S[i].V[0] = -1.0;
		dti4D->TE[i] = -1.0;
	}
	d.deID_CS_n = 0;
	for (int i = 0; i < MAX_DEID_CS; i++) {
		// n.b. knowing deID_CS_n is insufficient to know number of strings
		// e.g. dcm_qa_deident CodingSchemeVersion (0008,0103) provided for only some entries
		strcpy(dti4D->deID_CS[i].CodeValue, "");
		strcpy(dti4D->deID_CS[i].CodeMeaning, "");
		strcpy(dti4D->deID_CS[i].CodingSchemeDesignator, "");
		strcpy(dti4D->deID_CS[i].CodingSchemeVersion, "");
	}
	
	struct TVolumeDiffusion volDiffusion = initTVolumeDiffusion(&d, dti4D);
	struct stat s;
	if (stat(fname, &s) == 0) {
		if (!(s.st_mode & S_IFREG)) {
			printMessage("DICOM read fail: not a valid file (perhaps a directory) %s\n", fname);
			return d;
		}
	}
	bool isPart10prefix = true;
	int isOK = isDICOMfile(fname);
	if (isOK == 0)
		return d;
	if (isOK == 2) {
		d.isExplicitVR = false;
		isPart10prefix = false;
	}
	FILE *file = fopen(fname, "rb");
	if (!file) {
		printMessage("Unable to open file %s\n", fname);
		return d;
	}
#ifdef _MSC_VER
	_fseeki64(file, 0, SEEK_END);
	size_t fileLen = _ftelli64(file);
#else
	fseeko(file, 0, SEEK_END);	   // Windows _fseeki64
	size_t fileLen = ftello(file); // Windows _ftelli64
#endif
	if (fileLen < 256) {
		printMessage("File too small to be a DICOM image %s\n", fname);
		return d;
	}
// Since size of DICOM header is unknown, we will load it in 1mb segments
// This uses less RAM and makes is faster for computers with slow disk access
// Benefit is largest for 4D images.
// To disable caching and load entire file to RAM, compile with "-dmyLoadWholeFileToReadHeader"
// To implement the segments, we define these variables:
//  fileLen = size of file in bytes
//  MaxBufferSz = maximum size of buffer in bytes
//  Buffer = array with n elements, where n is smaller of fileLen or MaxBufferSz
//  lPos = position in Buffer (indexed from 0), 0..(n-1)
//  lFileOffset = offset of Buffer in file: true file position is lOffset+lPos (initially 0)
#ifdef myLoadWholeFileToReadHeader
	size_t MaxBufferSz = fileLen;
#else
	size_t MaxBufferSz = 1000000; // ideally size of DICOM header, but this varies from 2D to 4D files
#endif
	if (MaxBufferSz > (size_t)fileLen)
		MaxBufferSz = fileLen;
	// printf("%d -> %d\n", MaxBufferSz, fileLen);
	size_t lFileOffset = 0;
	fseek(file, 0, SEEK_SET);
	// Allocate memory
	unsigned char *buffer = (unsigned char *)malloc(MaxBufferSz + 1);
	if (!buffer) {
		printError("Memory exhausted!");
		fclose(file);
		return d;
	}
	// Read file contents into buffer
	size_t sz = fread(buffer, 1, MaxBufferSz, file);
	if (sz < MaxBufferSz) {
		printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname);
		fclose(file);
		return d;
	}
#ifdef myLoadWholeFileToReadHeader
	fclose(file);
#endif
	// DEFINE DICOM TAGS
#define kUnused 0x0001 + (0x0001 << 16)
#define kStart 0x0002 + (0x0000 << 16)
#define kMediaStorageSOPClassUID 0x0002 + (0x0002 << 16)
#define kMediaStorageSOPInstanceUID 0x0002 + (0x0003 << 16)
#define kTransferSyntax 0x0002 + (0x0010 << 16)
#define kImplementationVersionName 0x0002 + (0x0013 << 16)
#define kSourceApplicationEntityTitle 0x0002 + (0x0016 << 16)
#define kDirectoryRecordSequence 0x0004 + (0x1220 << 16)
//#define kSpecificCharacterSet 0x0008+(0x0005 << 16 ) //someday we should handle foreign characters...
#define kImageTypeTag 0x0008 + (0x0008 << 16)
#define kSOPInstanceUID 0x0008 + (0x0018 << 16) // Philips inserts time as last item, e.g. ?.?.?.YYYYMMDDHHmmSS.SSSS
// not reliable https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/21
#define kStudyDate 0x0008 + (0x0020 << 16)
#define kAcquisitionDate 0x0008 + (0x0022 << 16)
#define kAcquisitionDateTime 0x0008 + (0x002A << 16)
#define kStudyTime 0x0008 + (0x0030 << 16)
#define kSeriesTime 0x0008 + (0x0031 << 16)
#define kAcquisitionTime 0x0008 + (0x0032 << 16) // TM
// #define kContentTime 0x0008+(0x0033 << 16 ) //TM
#define kAccessionNumber 0x0008 + (0x0050 << 16)
#define kModality 0x0008 + (0x0060 << 16) // CS
#define kManufacturer 0x0008 + (0x0070 << 16)
#define kInstitutionName 0x0008 + (0x0080 << 16)
#define kInstitutionAddress 0x0008 + (0x0081 << 16)
#define kReferringPhysicianName 0x0008 + (0x0090 << 16)
#define kTracerRadionuclide1 0x0008 + (0x0100 << 16) // SH
#define kTracerRadionuclide2 0x0008 + (0x0104 << 16) // LO
#define kStationName 0x0008 + (0x1010 << 16)
#define kStudyDescription 0x0008 + (0x1030 << 16)  // LO
#define kSeriesDescription 0x0008 + (0x103E << 16) // '0008' '103E' 'LO' 'SeriesDescription'
#define kInstitutionalDepartmentName 0x0008 + (0x1040 << 16)
#define kFrameNum 0x0008 + (0x1160 << 16) // IS
#define kManufacturersModelName 0x0008 + (0x1090 << 16)
#define kDerivationDescription 0x0008 + (0x2111 << 16)
#define kReferencedImageEvidenceSQ (uint32_t)0x0008 + (0x9092 << 16)
#define kComplexImageComponent (uint32_t)0x0008 + (0x9208 << 16) //'0008' '9208' 'CS' 'ComplexImageComponent'
#define kAcquisitionContrast (uint32_t)0x0008 + (0x9209 << 16)	 //'0008' '9209' 'CS' 'AcquisitionContrast'
#define kInjectedVolumeGE 0x0009 + (0x103A << 16) // FL
#define kReconFilterSizeGE 0x0009 + (0x108F << 16) // FL bp_filter_cutoff
#define kIconSQ 0x0009 + (0x1110 << 16)
#define kPatientName 0x0010 + (0x0010 << 16)
#define kPatientID 0x0010 + (0x0020 << 16)
#define kPatientBirthDate 0x0010 + (0x0030 << 16)
#define kPatientSex 0x0010 + (0x0040 << 16)
#define kPatientAge 0x0010 + (0x1010 << 16)
#define kPatientSize 0x0010 + (0x1020 << 16) // DS
#define kPatientWeight 0x0010 + (0x1030 << 16)
#define kAnatomicalOrientationType 0x0010 + (0x2210 << 16)
#define kDeidentificationMethod 0x0012 + (0x0063 << 16) //[DICOMANON, issue 383
#define kDeidentificationMethodCodeSequence 0x0012 + (0x0064 << 16)
#define kCodeValue 0x0008 + (0x0100 << 16)
#define kCodingSchemeDesignator 0x0008 + (0x0102 << 16)
#define kCodingSchemeVersion 0x0008 + (0x0103 << 16)
#define kCodeMeaning 0x0008 + (0x0104 << 16)
#define kBodyPartExamined 0x0018 + (0x0015 << 16)
#define kBodyPartExamined 0x0018 + (0x0015 << 16)
#define kScanningSequence 0x0018 + (0x0020 << 16)
#define kSequenceVariant 0x0018 + (0x0021 << 16)
#define kScanOptions 0x0018 + (0x0022 << 16)
#define kMRAcquisitionType 0x0018 + (0x0023 << 16)
#define kSequenceName 0x0018 + (0x0024 << 16)
#define kRadiopharmaceutical 0x0018 + (0x0031 << 16) // LO
#define kZThick 0x0018 + (0x0050 << 16)
#define kTR 0x0018 + (0x0080 << 16)
#define kTE 0x0018 + (0x0081 << 16)
#define kTI 0x0018 + (0x0082 << 16)					   // Inversion time
#define kNumberOfAverages 0x0018 + (0x0083 << 16)	   // DS
#define kImagingFrequency 0x0018 + (0x0084 << 16)	   // DS
#define kEchoNum 0x0018 + (0x0086 << 16)			   // IS
#define kMagneticFieldStrength 0x0018 + (0x0087 << 16) // DS
#define kZSpacing 0x0018 + (0x0088 << 16)			   //'DS' 'SpacingBetweenSlices'
#define kPhaseEncodingSteps 0x0018 + (0x0089 << 16)	   // IS
#define kEchoTrainLength 0x0018 + (0x0091 << 16)	   // IS
#define kPercentSampling 0x0018 + (0x0093 << 16)	   //'DS'
#define kPhaseFieldofView 0x0018 + (0x0094 << 16)	   //'DS'
#define kPixelBandwidth 0x0018 + (0x0095 << 16)		   //'DS' 'PixelBandwidth'
#define kDeviceSerialNumber 0x0018 + (0x1000 << 16)	   // LO
#define kSoftwareVersions 0x0018 + (0x1020 << 16)	   // LO
#define kProtocolName 0x0018 + (0x1030 << 16)
#define kTriggerTime 0x0018 + (0x1060 << 16) // DS
#define kRadionuclideTotalDose 0x0018 + (0x1074 << 16)
#define kRadionuclideHalfLife 0x0018 + (0x1075 << 16)
#define kRadionuclidePositronFraction 0x0018 + (0x1076 << 16)
#define kGantryTilt 0x0018 + (0x1120 << 16)
#define kXRayTimeMS 0x0018 + (0x1150 << 16)		 // IS
#define kXRayTubeCurrent 0x0018 + (0x1151 << 16) // IS
#define kXRayExposure 0x0018 + (0x1152 << 16)
#define kConvolutionKernel 0x0018 + (0x1210 << 16) // SH
#define kFrameDuration 0x0018 + (0x1242 << 16)	   // IS
#define kReceiveCoilName 0x0018 + (0x1250 << 16)   // SH
// #define kTransmitCoilName 0x0018 + (0x1251 << 16) // SH issue527
#define kAcquisitionMatrix 0x0018 + (0x1310 << 16)			   // US
#define 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'
#define kInversionRecovery 0x0018 + uint32_t(0x9009 << 16)					 //'CS' 'YES'/'NO'
#define kSpoiling 0x0018 + uint32_t(0x9016 << 16)							 //'CS'
#define kEchoPlanarPulseSequence 0x0018 + uint32_t(0x9018 << 16)			 //'CS' 'YES'/'NO'
#define kMagnetizationTransferAttribute 0x0018 + uint32_t(0x9020 << 16)		 //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE'
#define kRectilinearPhaseEncodeReordering 0x0018 + uint32_t(0x9034 << 16)	 //'CS' 'REVERSE_LINEAR'/'LINEAR'
#define kPartialFourierDirection 0x0018 + uint32_t(0x9036 << 16)			 //'CS'
#define kCardiacSynchronizationTechnique 0x0018 + uint32_t(0x9037 << 16)	 //'CS'
#define 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 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 + uint32_t(0x9082 << 16);			 // FD
// #define kDiffusionBFactorSiemens 0x0019+(0x100C<< 16 ) // 0019;000C;SIEMENS MR HEADER;B_value
#define kDiffusionGradientDirectionSQ 0x0018 + uint32_t(0x9076 << 16)			   // SQ
#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 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
//TODO ASL LabelingOrientation 0018,9255, VascularCrushing 0x0018,9259 CS, VascularCrushingVENC 0018,925A
#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 kNumberOfImagesInMosaic 0x0019 + (0x100A << 16) // US NumberOfImagesInMosaic
// https://nmrimaging.wordpress.com/2011/12/20/when-we-process/
//  https://nciphub.org/groups/qindicom/wiki/DiffusionrelatedDICOMtags:experienceacrosssites?action=pdf
#define kDiffusion_bValueSiemens 0x0019 + (0x100C << 16)		   // IS
#define kDiffusionGradientDirectionSiemens 0x0019 + (0x100E << 16) // FD
#define kImaRelTablePosition 0x0019 + (0x1014 << 16)			   // IS[] Siemens V*
#define kSeriesPlaneGE 0x0019 + (0x1017 << 16)					   // SS
#define kDwellTime 0x0019 + (0x1018 << 16)						   // IS in NSec, see https://github.com/rordenlab/dcm2niix/issues/127
#define kLastScanLoc 0x0019 + (0x101B << 16)
#define kBandwidthPerPixelPhaseEncode 0x0019 + (0x1028 << 16) // FD
#define kSliceTimeSiemens 0x0019 + (0x1029 << 16)			  /// FD
#define kAcquisitionDurationGE 0x0019 + (0x105a << 16)		  // FL Acquisition Duration in microsecond, Duration of Scan (series)
#define kTableDeltaGE 0x0019 + (0x107f << 16)				  // DS Table delta
#define kPulseSequenceNameGE 0x0019 + (0x109C << 16)		  // LO 'epiRT' or 'epi'
#define kInternalPulseSequenceNameGE 0x0019 + (0x109E << 16)  // LO 'EPI' or 'EPI2'
#define kRawDataRunNumberGE 0x0019 + (0x10A2 << 16)			  // SL
#define kMaxEchoNumGE 0x0019 + (0x10A9 << 16)				  // DS
#define 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 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)
#define kAcquNum 0x0020 + (0x0012 << 16)
#define kImageNum 0x0020 + (0x0013 << 16)
#define kStudyInstanceUID 0x0020 + (0x000D << 16)
#define kSeriesInstanceUID 0x0020 + (0x000E << 16)
#define kImagePositionPatient 0x0020 + (0x0032 << 16) // Actually !
#define kOrientationACR 0x0020 + (0x0035 << 16)
#define kOrientation 0x0020 + (0x0037 << 16)
#define kTemporalPosition 0x0020 + (0x0100 << 16) // IS
// #define kNumberOfTemporalPositions 0x0020+(0x0105 << 16 ) //IS public tag for NumberOfDynamicScans
#define kTemporalResolution 0x0020 + (0x0110 << 16)	 // DS
#define kImagesInAcquisition 0x0020 + (0x1002 << 16) // IS
// #define kSliceLocation 0x0020+(0x1041 << 16 ) //DS would be useful if not optional type 3
#define kImageComments 0x0020 + (0x4000 << 16)				   // '0020' '4000' 'LT' 'ImageComments'
#define kFrameContentSequence 0x0020 + uint32_t(0x9111 << 16)  // SQ
#define kTriggerDelayTime 0x0020 + uint32_t(0x9153 << 16)	   // FD
#define kDimensionIndexValues 0x0020 + uint32_t(0x9157 << 16)  // UL n-dimensional index of frame.
#define kInStackPositionNumber 0x0020 + uint32_t(0x9057 << 16) // UL can help determine slices in volume
#define kTemporalPositionIndex 0x0020 + uint32_t(0x9128 << 16) // UL
#define kDimensionIndexPointer 0x0020 + uint32_t(0x9165 << 16)
// Private Group 21 as Used by Siemens:
#define kRelTablePosition 0x0021 + (0x1005 << 16)		 // IS Siemens XA
#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
#define kFrameNumberInSeries 0x0021 + (0x118A << 16)	 // IS issue837
#define kCSASeriesHeaderInfoXA 0x0021 + (0x1019 << 16)
#define kCSASeriesHeaderInfoXA2 0x0021 + (0x11FE << 16)
#define kTimeAfterStart 0x0021 + (0x1104 << 16)						   // DS
#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 kICE_dimsLong 0x0021 + (0x118e << 16)
#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
#define kProtocolDataBlockGE 0x0025 + (0x101B << 16)	  // OB
#define kNumberOfPointsPerArm 0x0027 + (0x1060 << 16)	  // FL
#define kNumberOfArms 0x0027 + (0x1061 << 16)			  // FL
#define kNumberOfExcitations 0x0027 + (0x1062 << 16)	  // FL
#define kSamplesPerPixel 0x0028 + (0x0002 << 16)
#define kPhotometricInterpretation 0x0028 + (0x0004 << 16)
#define kPlanarRGB 0x0028 + (0x0006 << 16)
#define kDim3 0x0028 + (0x0008 << 16) // number of frames - for Philips this is Dim3*Dim4
#define kDim2 0x0028 + (0x0010 << 16)
#define kDim1 0x0028 + (0x0011 << 16)
#define kXYSpacing 0x0028 + (0x0030 << 16) // DS 'PixelSpacing'
#define kBitsAllocated 0x0028 + (0x0100 << 16)
#define kBitsStored 0x0028 + (0x0101 << 16)				// US 'BitsStored'
#define kHighBit 0x0028 + (0x0102 << 16)				// US 'HighBit'
#define kIsSigned 0x0028 + (0x0103 << 16)				// PixelRepresentation
#define kPixelPaddingValue 0x0028 + (0x0120 << 16)		// https://github.com/rordenlab/dcm2niix/issues/262
#define kFloatPixelPaddingValue 0x0028 + (0x0122 << 16) // https://github.com/rordenlab/dcm2niix/issues/262
#define kIntercept 0x0028 + (0x1052 << 16)
#define kSlope 0x0028 + (0x1053 << 16)
// #define kRescaleType 0x0028+(0x1053 << 16 ) //LO e.g. for Philips Fieldmap: [Hz]
// #define kSpectroscopyDataPointColumns 0x0028+(0x9002 << 16 ) //IS
#define kGeiisFlag 0x0029 + (0x0010 << 16) // warn user if dreaded GEIIS was used to process image
#define kCSAImageHeaderInfo 0x0029 + (0x1010 << 16)
#define kCSASeriesHeaderInfo 0x0029 + (0x1020 << 16)
#define kStudyComments 0x0032 + (0x4000 << 16) // LT StudyComments
// #define kObjectGraphics 0x0029+(0x1210 << 16 ) //0029,1210 syngoPlatformOOGInfo Object Oriented Graphics
#define kProcedureStepDescription 0x0040 + (0x0254 << 16)
#define kRealWorldIntercept 0x0040 + uint32_t(0x9224 << 16)	 // IS dicm2nii's SlopInt_6_9
#define kRealWorldSlope 0x0040 + uint32_t(0x9225 << 16)		 // IS dicm2nii's SlopInt_6_9
#define 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
#define kImageTypeGE 0x0043 + (0x102F << 16)				 // SS 0/1/2/3 for magnitude/phase/real/imaginary
#define kDiffusion_bValueGE 0x0043 + (0x1039 << 16)			 // IS dicm2nii's SlopInt_6_9
#define kEpiRTGroupDelayGE 0x0043 + (0x107C << 16)			 // FL
#define kAssetRFactorsGE 0x0043 + (0x1083 << 16)			 // DS
#define kASLContrastTechniqueGE 0x0043 + (0x10A3 << 16)		 // CS
#define kASLLabelingTechniqueGE 0x0043 + (0x10A4 << 16)		 // LO
#define kDurationLabelPulseGE 0x0043 + (0x10A5 << 16)		 // IS
#define kMRTablePositionInformation 0x0043 + (0x10B2 << 16)	 // LO GE
#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)
#define kImaPATModeText 0x0051 + (0x1011 << 16)
#define kLocationsInAcquisition 0x0054 + (0x0081 << 16)
#define kUnitsPT 0x0054 + (0x1001 << 16)					 // CS
#define kAttenuationCorrectionMethod 0x0054 + (0x1101 << 16) // LO
#define kDecayCorrection 0x0054 + (0x1102 << 16)			 // CS
#define kReconstructionMethod 0x0054 + (0x1103 << 16)		 // LO
#define kFrameReferenceTime 0x0054 + (0x1300 << 16)			 // DS
#define kDecayFactor 0x0054 + (0x1321 << 16)				 // DS
#define kScatterFraction 0x0054 + (0x1323 << 16)			 // DS
// ftp://dicom.nema.org/MEDICAL/dicom/2014c/output/chtml/part03/sect_C.8.9.4.html
// If ImageType is REPROJECTION we slice direction is reversed - need example to test
//  #define kSeriesType 0x0054+(0x1000 << 16 )
#define kDoseCalibrationFactor 0x0054 + (0x1322 << 16)
#define kPETImageIndex 0x0054 + (0x1330 << 16)
#define kReferencedSegmentNumber 0x0062 + (0x000B << 16)	   // US
#define kPEDirectionDisplayedUIH 0x0065 + (0x1005 << 16)	   // SH
#define kDiffusion_bValueUIH 0x0065 + (0x1009 << 16)		   // FD
#define kParallelInformationUIH 0x0065 + (0x100D << 16)		   // SH
#define kNumberOfImagesInGridUIH 0x0065 + (0x1050 << 16)	   // DS
#define kDiffusionGradientDirectionUIH 0x0065 + (0x1037 << 16) // FD
// #define kMRVFrameSequenceUIH 0x0065+(0x1050<< 16 ) //SQ
#define kPhaseEncodingDirectionPositiveUIH 0x0065 + (0x1058 << 16) // IS issue410
#define kIconImageSequence 0x0088 + (0x0200 << 16)
#define kElscintIcon 0x07a3 + (0x10ce << 16)			 // see kGeiisFlag and https://github.com/rordenlab/dcm2niix/issues/239
#define kPMSCT_RLE1 0x07a1 + (0x100a << 16)				 // Elscint/Philips compression
#define kPrivateCreator 0x2001 + (0x0010 << 16)			 // LO (Private creator is any tag where group is odd and element is x0010-x00FF
#define kDiffusion_bValuePhilips 0x2001 + (0x1003 << 16) // FL
#define kPhaseNumber 0x2001 + (0x1008 << 16)			 // IS
#define kCardiacSync 0x2001 + (0x1010 << 16)			 // CS
// #define kDiffusionDirectionPhilips 0x2001+(0x1004 << 16 )//CS Diffusion Direction
#define kSliceNumberMrPhilips 0x2001 + (0x100A << 16)		 // IS Slice_Number_MR
#define kSliceOrient 0x2001 + (0x100B << 16)				 // 2001,100B Philips slice orientation (TRANSVERSAL, AXIAL, SAGITTAL)
#define kEPIFactorPhilips 0x2001 + (0x1013 << 16)			 // SL
#define kPrepulseDelay 0x2001 + (0x101B << 16)				 // FL
#define kPrepulseType 0x2001 + (0x101C << 16)				 // CS
#define kRespirationSync 0x2001 + (0x101F << 16)			 // CS
#define kNumberOfSlicesMrPhilips 0x2001 + (0x1018 << 16)	 // SL 0x2001, 0x1018 ), "Number_of_Slices_MR"
#define kPartialMatrixScannedPhilips 0x2001 + (0x1019 << 16) // CS
#define kWaterFatShiftPhilips 0x2001 + (0x1022 << 16)		 // FL
// #define kMRSeriesAcquisitionNumber 0x2001+(0x107B << 16 ) //IS
// #define kNumberOfLocationsPhilips 0x2001+(0x1015 << 16 ) //SS
// #define kStackSliceNumber 0x2001+(0x1035 << 16 )//? Potential way to determine slice order for Philips?
#define kNumberOfDynamicScans 0x2001 + (0x1081 << 16) //'2001' '1081' 'IS' 'NumberOfDynamicScans'
// #define kTRPhilips 0x2005 + (0x1030 << 16) //(2005,1030) FL 30\150
#define kTRArray 0x2005 + (0x1030 << 16) // FL Array, e.g. "15\75", see issue 743
#define kMRfMRIStatusIndicationPhilips 0x2005 + (0x1063 << 16)
#define kMRAcquisitionTypePhilips 0x2005 + (0x106F << 16) // SS
#define kAngulationAP 0x2005 + (0x1071 << 16)			  //'2005' '1071' 'FL' 'MRStackAngulationAP'
#define kAngulationFH 0x2005 + (0x1072 << 16)			  //'2005' '1072' 'FL' 'MRStackAngulationFH'
#define kAngulationRL 0x2005 + (0x1073 << 16)			  //'2005' '1073' 'FL' 'MRStackAngulationRL'
#define kMRStackOffcentreAP 0x2005 + (0x1078 << 16)
#define kMRStackOffcentreFH 0x2005 + (0x1079 << 16)
#define kMRStackOffcentreRL 0x2005 + (0x107A << 16)
#define kPhilipsSlope 0x2005 + (0x100E << 16)
#define kMRImageDynamicScanBeginTime 0x2005 + (0x10A0 << 16) // FL
#define kDiffusionDirectionRL 0x2005 + (0x10B0 << 16)
#define kDiffusionDirectionAP 0x2005 + (0x10B1 << 16)
#define kDiffusionDirectionFH 0x2005 + (0x10B2 << 16)
#define kDeepLearningPhilips 0x2005 + (0x1110 << 16)
#define kPrivatePerFrameSq 0x2005 + (0x140F << 16)
#define kMRStackTablePosLong 0x2005 + (0x143C << 16)			  // FL Philips
#define kMRImageDiffBValueNumber 0x2005 + (0x1412 << 16)		  // IS
#define kMRImageGradientOrientationNumber 0x2005 + (0x1413 << 16) // IS
#define kMRImageLabelType 0x2005 + (0x1429 << 16)				  // CS ASL LBL_CTL https://github.com/physimals/dcm_convert_phillips/
#define kMRImageDiffVolumeNumber 0x2005 + (0x1596 << 16)		  // IS
#define kOriginalAttributesSq 0x0400 + (0x0561 << 16)
#define kSharedFunctionalGroupsSequence 0x5200 + uint32_t(0x9229 << 16)	  // SQ
#define kPerFrameFunctionalGroupsSequence 0x5200 + uint32_t(0x9230 << 16) // SQ
#define kWaveformSq 0x5400 + (0x0100 << 16)
#define kSpectroscopyData 0x5600 + (0x0020 << 16) // OF
#define kImageStart 0x7FE0 + (0x0010 << 16)
#define kImageStartFloat 0x7FE0 + (0x0008 << 16)
#define kImageStartDouble 0x7FE0 + (0x0009 << 16)
	uint32_t kItemTag = 0xFFFE + (0xE000 << 16);
	uint32_t kItemDelimitationTag = 0xFFFE + (0xE00D << 16);
	uint32_t kSequenceDelimitationItemTag = 0xFFFE + (0xE0DD << 16);
#define salvageAgfa
#ifdef salvageAgfa	  // issue435
					  // handle PrivateCreator renaming e.g. 0021,10xx -> 0021,11xx
					  // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl
					  // https://github.com/neurolabusc/dcm_qa_agfa
					  // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.8.html
#define kMaxRemaps 16 // no vendor uses more than 5 private creator groups
	// we need to keep track of multiple remappings, e.g. issue 437 2005,0014->2005,0012; 2005,0015->2005,0011
	int nRemaps = 0;
	uint32_t privateCreatorMasks[kMaxRemaps];  // 0 -> none
	uint32_t privateCreatorRemaps[kMaxRemaps]; // 0 -> none
#endif
	double 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 isSliceOrientVaries = false; //issue894
	int sqDepth04000561 = -1;
	bool is00089092SQ = false; // Referenced Image Evidence SQ
	bool overlayOK = true;
	int userData11GE = 0;
	int userData12GE = 0;
	float userData15GE = 0;
	float accelFactPE = 0.0;
	float tableDeltaGE = 0.0;
	int overlayRows = 0;
	int overlayCols = 0;
	bool isNeologica = false;
	// bool isTriggerSynced = false;
	// bool isProspectiveSynced = false;
	bool isDICOMANON = false; // issue383
	bool isMATLAB = false;	  // issue383
	bool isDWI_UIH = false;	  // issue836
	// bool isASL = false;
	bool has00200013 = false;
	// double contentTime = 0.0;
	int echoTrainLengthPhil = 0;
	int philMRImageDiffBValueNumber = 0;
	int philMRImageDiffVolumeNumber = -1;
	int sqDepth = 0;
	int seriesInstanceUIDsqDepth = 65535; // issue655
	int acquisitionTimesGE_UIH = 0;
	int sqDepth00189114 = -1;
	bool hasDwiDirectionality = false;
	// float sliceLocation = INFINITY; //useless since this tag is optional
	// int numFirstPatientPosition = 0;
	int nDimIndxVal = -1; // tracks Philips kDimensionIndexValues
	int locationsInAcquisitionGE = 0;
	int PETImageIndex = 0;
	int inStackPositionNumber = 0;
	int isIssue839 = false;
	float patientPosition1[kMaxSlice2D];
	float patientPosition2[kMaxSlice2D];
	float patientPosition3[kMaxSlice2D];
	bool isKludgeIssue809 = false;
	uint32_t dimensionIndexPointer[MAX_NUMBER_OF_DIMENSIONS];
	size_t dimensionIndexPointerCounter = 0;
	int maxInStackPositionNumber = 0;
	int temporalPositionIndex = 0;
	int minTemporalPositionIndex = 65535;
	int maxTemporalPositionIndex = 0;
	int minReferencedSegmentNumber = 65535;
	int maxReferencedSegmentNumber = 0;
	// int temporalPositionIdentifier = 0;
	int locationsInAcquisitionPhilips = 0;
	int imagesInAcquisition = 0;
	int highBit = 0;
	// int sumSliceNumberMrPhilips = 0;
	int sliceNumberMrPhilips = 0;
	int volumeNumber = -1;
	int gradientOrientationNumberPhilips = -1;
	int numberOfFrames = 0;
	int numberOfFramesICEdims = 0;
	int frameNumberInSeries = -1; // issue837
	// int MRImageGradientOrientationNumber = 0;
	// int minGradNum = kMaxDTI4D + 1;
	// int maxGradNum = -1;
	int numberOfDynamicScans = 0;
	// int mRSeriesAcquisitionNumber = 0;
	uint32_t lLength;
	uint32_t groupElement;
	size_t lPos = 0;
	bool isPhilipsDerived = false;
	// bool isPhilipsDiffusion = false;
	if (isPart10prefix) { // for part 10 files, skip preamble and prefix
		lPos = 128 + 4;	  // 4-byte signature starts at 128
		groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
		if (groupElement != kStart)
			printMessage("DICOM appears corrupt: first group:element should be 0x0002:0x0000 '%s'\n", fname);
	} else { // no isPart10prefix - need to work out if this is explicit VR!
		if (isVerbose > 1)
			printMessage("DICOM preamble and prefix missing: this is not a valid DICOM image.\n");
		// See Toshiba Aquilion images from https://www.aliza-dicom-viewer.com/download/datasets
		lLength = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24);
		if (lLength > fileLen) {
			if (isVerbose > 1)
				printMessage("Guessing this is an explicit VR image.\n");
			d.isExplicitVR = true;
		}
	}
	char vr[2];
	// float intenScalePhilips = 0.0;
	char scanOptionsSiemens[kDICOMStrLarge] = "";
	char seriesTimeTxt[kDICOMStr] = "";
	char acquisitionDateTimeTxt[kDICOMStr] = "";
	char scanningSequenceSiemens[kDICOMStr] = "";
	char imageType1st[kDICOMStr] = "";
	bool isEncapsulatedData = false;
	int diffusionDirectionTypeGE = 0;		// issue690
	int seriesdiffusionDirectionTypeGE = 0; // issue690, 777
	int multiBandFactor = 0;
	int frequencyRows = 0;
	int numberOfImagesInMosaic = 0;
	int swVers = -1;
	int encapsulatedDataFragments = 0;
	int encapsulatedDataFragmentStart = 0; // position of first FFFE,E000 for compressed images
	int encapsulatedDataImageStart = 0;	   // position of 7FE0,0010 for compressed images (where actual image start should be start of first fragment)
	bool isOrient = false;
	// bool isDcm4Che = false;
	bool isMoCo = false;
	bool isPaletteColor = false;
	bool isInterpolated = false;
	bool isIconImageSequence = false;
	bool isDeidentificationMethodCodeSequence = false;
	int sqDepthIcon = -1;
	bool isSwitchToImplicitVR = false;
	bool isSwitchToBigEndian = false;
	bool isAtFirstPatientPosition = false; // for 3d and 4d files: flag is true for slices at same position as first slice
	bool isMosaic = false;
	bool isGEfieldMap = false; // issue501
	int patientPositionNum = 0;
	float B0Philips = -1.0;
	float vRLPhilips = 0.0;
	float vAPPhilips = 0.0;
	float vFHPhilips = 0.0;
	// float TRPhilips = -1.0;
	double acquisitionTimePhilips = -1.0;
	bool isPhase = false;
	bool isReal = false;
	bool isImaginary = false;
	bool isMagnitude = false;
	d.seriesNum = -1;
	// start issue 372:
	vec3 sliceV; // cross-product of kOrientation 0020,0037
	sliceV.v[0] = NAN;
	float sliceMM[kMaxSlice2D];
	int nSliceMM = 0;
	float minSliceMM = INFINITY;
	float maxSliceMM = -INFINITY;
	float minDynamicScanBeginTime = INFINITY;
	float maxDynamicScanBeginTime = -INFINITY;
	float minPatientPosition[4] = {NAN, NAN, NAN, NAN};
	float maxPatientPosition[4] = {NAN, NAN, NAN, NAN};
	// end issue 372
	// float frameAcquisitionDuration = 0.0; //issue369
	float patientPositionPrivate[4] = {NAN, NAN, NAN, NAN};
	float patientPosition[4] = {NAN, NAN, NAN, NAN}; // used to compute slice direction for Philips 4D
	// float patientPositionPublic[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D
	float patientPositionEndPhilips[4] = {NAN, NAN, NAN, NAN};
	float patientPositionStartPhilips[4] = {NAN, NAN, NAN, NAN};
	// struct TDTI philDTI[kMaxDTI4D];
	// for (int i = 0; i < kMaxDTI4D; i++)
	//	philDTI[i].V[0] = -1;
	// array for storing DimensionIndexValues
	int numDimensionIndexValues = 0;
	bool isSiemensXA = false;
	//don't use stack! TDCMdim dcmDim[kMaxSlice2D];
	TDCMdim *dcmDim = (TDCMdim *)malloc(kMaxSlice2D * sizeof(TDCMdim));
	for (int i = 0; i < kMaxSlice2D; i++) {
		dcmDim[i].diskPos = i;
		for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++)
			dcmDim[i].dimIdx[j] = 0;
	}
// http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html
// The array nestPos tracks explicit lengths for Data Element Tag of Value (FFFE,E000)
// a delimiter (fffe,e000) can have an explicit length, in which case there is no delimiter (fffe,e00d)
//  fffe,e000 can provide explicit lengths, to demonstrate ./dcmconv +ti ex.DCM im.DCM
#define kMaxNestPost 128
	int nNestPos = 0;
	size_t nestPos[kMaxNestPost];
	while ((d.imageStart == 0) && ((lPos + 8 + lFileOffset) < fileLen)) {
#ifndef myLoadWholeFileToReadHeader				  // read one segment at a time
		if ((size_t)(lPos + 128) > MaxBufferSz) { // avoid overreading the file
			lFileOffset = lFileOffset + lPos;
			if ((lFileOffset + MaxBufferSz) > (size_t)fileLen)
				MaxBufferSz = fileLen - lFileOffset;
			fseek(file, lFileOffset, SEEK_SET);
			size_t sz = fread(buffer, 1, MaxBufferSz, file);
			if (sz < MaxBufferSz) {
				printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname);
				fclose(file);
				#ifndef USING_R
				free(dcmDim);
				#endif
				return d;
			}
			lPos = 0;
		}
#endif
		if (d.isLittleEndian)
			groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
		else
			groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24);
		if ((isSwitchToBigEndian) && ((groupElement & 0xFFFF) != 2)) {
			isSwitchToBigEndian = false;
			d.isLittleEndian = false;
			groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24);
		} // transfer syntax requests switching endian after group 0002
		if ((isSwitchToImplicitVR) && ((groupElement & 0xFFFF) != 2)) {
			isSwitchToImplicitVR = false;
			d.isExplicitVR = false;
		} // transfer syntax requests switching VR after group 0001
		// uint32_t group = (groupElement & 0xFFFF);
		lPos += 4;
		uint32_t realGroupElement = groupElement;
		// issue409 - icons can have their own sub-sections... keep reading until we get to the icon image?
		// if ((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) isIconImageSequence = false;
		// if (groupElement == kItemTag) sqDepth++;
		bool unNest = false;
		while ((nNestPos > 0) && (nestPos[nNestPos] <= (lFileOffset + lPos))) {
			nNestPos--;
			sqDepth--;
			unNest = true;
			if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { // issue415
				sqDepthIcon = -1;
				isIconImageSequence = false;
			}
		}
		if (groupElement == kItemDelimitationTag) { // end of item with undefined length
			sqDepth--;
			unNest = true;
			if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { // issue415
				sqDepthIcon = -1;
				isIconImageSequence = false;
			}
		}
		if (unNest) {
			if ((groupElement != kItemTag) && (sqDepth <= sqDepth04000561)) {
				sqDepth04000561 = -1; // unlatch
			}
			is2005140FSQ = false;
			is00089092SQ = false;
			if (sqDepth < 0)
				sqDepth = 0; // should not happen, but protect for faulty anonymization
			// if we leave the folder MREchoSequence 0018,9114
			if ((nDimIndxVal > 0) && ((d.manufacturer == kMANUFACTURER_UNKNOWN) || (d.manufacturer == kMANUFACTURER_MEDISO) || (d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_BRUKER) || (d.manufacturer == kMANUFACTURER_PHILIPS)) && (sqDepth00189114 >= sqDepth)) {
				sqDepth00189114 = -1; // triggered
				//  d.aslFlags = kASL_FLAG_PHILIPS_LABEL; kASL_FLAG_PHILIPS_LABEL
				//printf("issue809 %d %d %d\n", inStackPositionNumber, philMRImageDiffBValueNumber, gradientOrientationNumberPhilips);
				bool isKludge = (swVers > 10) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (nDimIndxVal > 1) && (inStackPositionNumber > 0);
				if (isKludge) {
					isKludgeIssue809 = true;
					int phase = d.phaseNumber;
					if (d.phaseNumber < 0)
						phase = 0;
					int aslFlag = d.aslFlags == kASL_FLAG_PHILIPS_LABEL;
					int imageType = 0;
					if (isReal) imageType = 1;
					if (isImaginary) imageType = 2;
					if (isPhase) imageType = 3;
					int bvalNum = philMRImageDiffBValueNumber > 0 ? philMRImageDiffBValueNumber : 0;
					int gradNum = gradientOrientationNumberPhilips > 0 ? gradientOrientationNumberPhilips : 0;
					int volume = volumeNumber > 0 ? volumeNumber : 0;
					int d2 = d.dimensionIndexValues[2];
					int d3 = d.dimensionIndexValues[3];
					if (d.aslFlags == kASL_FLAG_NONE) {
						aslFlag = d2;
					}
					for (int i = 0; i < nDimIndxVal; i++)
						d.dimensionIndexValues[i] = 0;
					d.dimensionIndexValues[0] = inStackPositionNumber;				   // dim[3] slice changes fastest
					d.dimensionIndexValues[1] = phase;
					d.dimensionIndexValues[2] = aslFlag;
					d.dimensionIndexValues[3] = imageType;
					d.dimensionIndexValues[4] = bvalNum;
					d.dimensionIndexValues[5] = gradNum;
					d.dimensionIndexValues[6] = d3;
					d.dimensionIndexValues[7] = volume;
					nDimIndxVal = 8;
				}
				if ((volumeNumber == 1) && (acquisitionTimePhilips >= 0.0) && (inStackPositionNumber > 0)) {
					d.CSA.sliceTiming[inStackPositionNumber - 1] = acquisitionTimePhilips;
					// printf("%d\t%f\n", inStackPositionNumber, acquisitionTimePhilips);
					acquisitionTimePhilips = -1.0;
				}
				int ndim = nDimIndxVal;
				// issue 839
				patientPosition1[numDimensionIndexValues] = patientPosition[1];
				patientPosition2[numDimensionIndexValues] = patientPosition[2];
				patientPosition3[numDimensionIndexValues] = patientPosition[3];
				if (inStackPositionNumber > 0) {
					// for images without SliceNumberMrPhilips (2001,100A)
					int sliceNumber = inStackPositionNumber;
					// printf("slice %d \n", sliceNumber);
					if ((!isIssue839) && (sliceNumber == 1) && (!isnan(patientPositionStartPhilips[1]))) {
						float dx = sqrt(
							pow(patientPositionStartPhilips[1] - patientPosition[1], 2) +
							pow(patientPositionStartPhilips[2] - patientPosition[2], 2) +
							pow(patientPositionStartPhilips[3] - patientPosition[3], 2));
						if (!isSameFloatGE(dx, 0.0)) {
							isIssue839 = true;
							printWarning("repeats of first inStackPositionNumber are at different locations (dx = %gmm; issue 839)\n", dx);
						}
					}
					if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) {
						for (int k = 0; k < 4; k++)
							patientPositionStartPhilips[k] = patientPosition[k];
					} else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) {
						for (int k = 0; k < 4; k++)
							patientPositionStartPhilips[k] = patientPositionPrivate[k];
					}
					if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPosition[1]))) {
						for (int k = 0; k < 4; k++)
							patientPositionEndPhilips[k] = patientPosition[k];
					} else if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPositionPrivate[1]))) {
						for (int k = 0; k < 4; k++)
							patientPositionEndPhilips[k] = patientPositionPrivate[k];
					}
					patientPosition[1] = NAN;
					patientPositionPrivate[1] = NAN;
				}
				inStackPositionNumber = 0;
				if (numDimensionIndexValues >= kMaxSlice2D) {
					printError("Too many slices to track dimensions. Only up to %d are supported\n", kMaxSlice2D);
					break;
				}
				uint32_t dimensionIndexOrder[MAX_NUMBER_OF_DIMENSIONS];
				for (int i = 0; i < nDimIndxVal; i++)
					dimensionIndexOrder[i] = i;
				// Bruker Enhanced MR IOD: reorder dimensions to ensure InStackPositionNumber corresponds to the first one
				// This will ensure correct ordering of slices in 4D datasets
				// Canon and Bruker reverse dimensionIndexItem order relative to Philips: new versions introduce compareTDCMdimRev
				// printf("%d: %d %d %d %d\n", ndim, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2], d.dimensionIndexValues[3]);
				if ((!isKludgeIssue809) && (philMRImageDiffVolumeNumber > 0) && (sliceNumberMrPhilips > 0)) { // issue546: 2005,1596 provides temporal order
					dcmDim[numDimensionIndexValues].dimIdx[0] = 1;
					dcmDim[numDimensionIndexValues].dimIdx[1] = sliceNumberMrPhilips;
					dcmDim[numDimensionIndexValues].dimIdx[2] = philMRImageDiffVolumeNumber;
				} else {
					for (int i = 0; i < ndim; i++)
						dcmDim[numDimensionIndexValues].dimIdx[i] = d.dimensionIndexValues[dimensionIndexOrder[i]];
				}
				dcmDim[numDimensionIndexValues].TE = d.TE;
				dcmDim[numDimensionIndexValues].TR = d.TR;
				dcmDim[numDimensionIndexValues].intenScale = d.intenScale;
				dcmDim[numDimensionIndexValues].intenIntercept = d.intenIntercept;
				dcmDim[numDimensionIndexValues].isPhase = isPhase;
				dcmDim[numDimensionIndexValues].isReal = isReal;
				dcmDim[numDimensionIndexValues].isImaginary = isImaginary;
				dcmDim[numDimensionIndexValues].intenScalePhilips = d.intenScalePhilips;
				dcmDim[numDimensionIndexValues].RWVScale = d.RWVScale;
				dcmDim[numDimensionIndexValues].RWVIntercept = d.RWVIntercept;
				// printf("%d %d %g????\n", isTriggerSynced, isProspectiveSynced, d.triggerDelayTime);
				// TODO533: isKludgeIssue809 alias Philips ASL as FrameDuration?
				// if ((d.triggerDelayTime > 0.0) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.aslFlags != kASL_FLAG_NONE))
				// printf(">>>%g\n", d.triggerDelayTime);
				// if ((isASL) || (d.aslFlags != kASL_FLAG_NONE)) d.triggerDelayTime = 0.0; //see dcm_qa_philips_asl
				// if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408
				//	d.triggerDelayTime = 0.0;
				if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime))
					dcmDim[numDimensionIndexValues].triggerDelayTime = 0.0; // issue395
				else
					dcmDim[numDimensionIndexValues].triggerDelayTime = d.triggerDelayTime;
				dcmDim[numDimensionIndexValues].V[0] = -1.0;
#ifdef MY_DEBUG
				if (numDimensionIndexValues < 19) {
					printMessage("dimensionIndexValues0020x9157[%d] = [", numDimensionIndexValues);
					for (int i = 0; i < ndim; i++)
						printMessage("%d ", d.dimensionIndexValues[i]);
					printMessage("]\n");
					// printMessage("B0= %g num=%d\n", B0Philips, gradNum);
				} else {
					#ifndef USING_R
						free(dcmDim);
					#endif
					return d;
				}
#endif
				// next: add diffusion if reported
				if (B0Philips >= 0.0) { // diffusion parameters
					// Philips does not always provide 2005,1413 (MRImageGradientOrientationNumber) and sometimes after dimensionIndexValues
					/*int gradNum = 0;
				for (int i = 0; i < ndim; i++)
					if (d.dimensionIndexValues[i] > 0) gradNum = d.dimensionIndexValues[i];
				if (gradNum <= 0) break;
				With Philips 51.0 both ADC and B=0 are saved as same direction, though they vary in another dimension
				(0018,9075) CS [ISOTROPIC]
				(0020,9157) UL 1\2\1\33 << ADC MAP
				(0018,9075) CS [NONE]
				(0020,9157) UL 1\1\2\33
				next two lines attempt to skip ADC maps
				we could also increment gradNum for ADC if we wanted...
				*/
					if (isPhilipsDerived) {
						// gradNum ++;
						B0Philips = 2000.0;
						vRLPhilips = 0.0;
						vAPPhilips = 0.0;
						vFHPhilips = 0.0;
					}
					if (B0Philips == 0.0) {
						// printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips);
						vRLPhilips = 0.0;
						vAPPhilips = 0.0;
						vFHPhilips = 0.0;
					}
					// if ((MRImageGradientOrientationNumber > 0) && ((gradNum != MRImageGradientOrientationNumber)) break;
					/*if (gradNum < minGradNum) minGradNum = gradNum;
				if (gradNum >= maxGradNum) maxGradNum = gradNum;
				if (gradNum >= kMaxDTI4D) {
						printError("Number of DTI gradients exceeds 'kMaxDTI4D (%d).\n", kMaxDTI4D);
				} else {
					gradNum = gradNum - 1; //index from 0
					philDTI[gradNum].V[0] = B0Philips;
					philDTI[gradNum].V[1] = vRLPhilips;
					philDTI[gradNum].V[2] = vAPPhilips;
					philDTI[gradNum].V[3] = vFHPhilips;
				}*/
					dcmDim[numDimensionIndexValues].V[0] = B0Philips;
					dcmDim[numDimensionIndexValues].V[1] = vRLPhilips;
					dcmDim[numDimensionIndexValues].V[2] = vAPPhilips;
					dcmDim[numDimensionIndexValues].V[3] = vFHPhilips;
					isPhilipsDerived = false;
					// printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips);
					//!!! 16032018 : next line as well as definition of B0Philips may need to be set to zero if Philips omits DiffusionBValue tag for B=0
					B0Philips = -1.0; // Philips may skip reporting B-values for B=0 volumes, so zero these
					vRLPhilips = 0.0;
					vAPPhilips = 0.0;
					vFHPhilips = 0.0;
					// MRImageGradientOrientationNumber = 0;
				} // diffusion parameters
				numDimensionIndexValues++;
				nDimIndxVal = -1; // we need DimensionIndexValues
			} // record dimensionIndexValues slice information
		} // groupElement == kItemDelimitationTag : delimit item exits folder
		uint32_t itemTagLength = 0;
		if (groupElement == kItemTag) {
			itemTagLength = dcmInt(4, &buffer[lPos], d.isLittleEndian);
			uint32_t kUndefinedLen = 0xFFFFFFFF;
			if (itemTagLength != kUndefinedLen) {
				nNestPos++;
				if (nNestPos >= kMaxNestPost)
					nNestPos = kMaxNestPost - 1;
				nestPos[nNestPos] = itemTagLength + lFileOffset + lPos;
			}
			lLength = 4;
			sqDepth++;
		} else if (((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) && (!isEncapsulatedData)) {
			vr[0] = 'N';
			vr[1] = 'A';
			lLength = 4;
		} else if (d.isExplicitVR) {
			vr[0] = buffer[lPos];
			vr[1] = buffer[lPos + 1];
			if (buffer[lPos + 1] < 'A') { // implicit vr with 32-bit length
				if (d.isLittleEndian)
					lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
				else
					lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24);
				lPos += 4;
			} else if (((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'N')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'C')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'R')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'T')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'V')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'B')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'D')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'F')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'L')) | ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'V')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'W')) || ((buffer[lPos] == 'S') && (buffer[lPos + 1] == 'V'))) { // VR= UN, OB, OW, SQ || ((buffer[lPos] == 'S') && (buffer[lPos+1] == 'Q'))
				// for example of UC/UR/UV/OD/OF/OL/OV/SV see VR conformance test https://www.aliza-dicom-viewer.com/download/datasets
				lPos = lPos + 4; // skip 2 byte VR string and 2 reserved bytes = 4 bytes
				if (d.isLittleEndian)
					lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
				else
					lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24);
				lPos = lPos + 4; // skip 4 byte length
			} else if ((buffer[lPos] == 'S') && (buffer[lPos + 1] == 'Q')) {
				lLength = 8; // Sequence Tag
							 // printMessage(" !!!SQ\t%04x,%04x\n", groupElement & 65535,groupElement>>16);
			} else {		 // explicit VR with 16-bit length
				if ((d.isLittleEndian))
					lLength = buffer[lPos + 2] | (buffer[lPos + 3] << 8);
				else
					lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8);
				lPos += 4; // skip 2 byte VR string and 2 length bytes = 4 bytes
			}
		} else { // implicit VR
			vr[0] = 'U';
			vr[1] = 'N';
			if (d.isLittleEndian)
				lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
			else
				lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24);
			lPos += 4;																	   // we have loaded the 32-bit length
			if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isSQ(groupElement, true))) { // https://github.com/rordenlab/dcm2niix/issues/144
				vr[0] = 'S';
				vr[1] = 'Q';
				lLength = 0; // Do not skip kItemTag - required to determine nesting of Philips Enhanced
			}
			if ((d.manufacturer != kMANUFACTURER_PHILIPS) && (isSQ(groupElement, false))) { // https://github.com/rordenlab/dcm2niix/issues/144
				vr[0] = 'S';
				vr[1] = 'Q';
				lLength = 0; // Do not skip kItemTag - required to determine nesting of Philips Enhanced
			}
			if (groupElement == kDiffusionGradientDirectionSQ) { // https://github.com/rordenlab/dcm2niix/issues/144
				vr[0] = 'S';
				vr[1] = 'Q';
				lLength = 0; // Do not skip kItemTag - required to determine nesting of Philips Enhanced
			}
		} // if explicit else implicit VR
		if ((lLength == 0xFFFFFFFF) && (vr[0] == 'O') && (vr[1] == 'B') && (isIconImageSequence)) {
			lLength = 0; // encapuslated data of unspecified length
		} // issue713
		if (lLength == 0xFFFFFFFF) {
			lLength = 8; // SQ (Sequences) use 0xFFFFFFFF [4294967295] to denote unknown length
						 // 09032018 - do not count these as SQs: Horos does not count even groups
						 // uint32_t special = dcmInt(4,&buffer[lPos],d.isLittleEndian);
			// http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html
			// if (special != ksqDelim) {
			vr[0] = 'S';
			vr[1] = 'Q';
			//}
		}
		if ((groupElement == kItemTag) && (vr[0] == 'O') && (vr[1] == 'B') && (isIconImageSequence))
			lLength += itemTagLength; // issue713
		// printf("issue713::%c%c %04x,%04x %d@%lu\n", vr[0], vr[1], groupElement & 65535, groupElement >> 16, lLength, lPos);
		if ((groupElement == kItemTag) && (isEncapsulatedData)) { // use this to find image fragment for compressed datasets, e.g. JPEG transfer syntax
			lLength = dcmInt(4, &buffer[lPos], d.isLittleEndian);
			lPos = lPos + 4;
			// https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_8.2.html
			// detect and skip  Basic Offset Table
			bool  isBasicOffsetTable = (d.imageBytes < 1) && ((lLength == 4 * numberOfFrames) || (lLength <= 0));
			if (lLength == 0xFFFFFFFF) {
				// printf("Fragment has undefined length\n");
				// to do: see pixelmed microscopy https://www.aliza-dicom-viewer.com/download/datasets
				lLength = d.imageBytes;
				d.imageStart = (int)lPos + (int)lFileOffset;
				isBasicOffsetTable = true;
			}
			if (!isBasicOffsetTable) {
				d.imageBytes = lLength;
				if (d.offsetTableItems < kMaxSlice2D)
					dti4D->offsetTable[d.offsetTableItems] = (int)lPos + (int)lFileOffset;
				d.offsetTableItems ++;
				if (d.imageBytes <= 0)
					goto skipRemap;
				if (d.imageBytes > 24) {
					/*if (encapsulatedDataFragments < kMaxDTI4D) {
						dti4D->fragmentOffset[encapsulatedDataFragments] = (int)lPos + (int)lFileOffset;
						dti4D->fragmentLength[encapsulatedDataFragments] = lLength;
					}*/
					encapsulatedDataFragments++;
					if (encapsulatedDataFragmentStart == 0)
						encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset;
				}
			} // not isBasicOffsetTable
		}
		if ((sqDepth04000561 >= 0) || (is00089092SQ))
			groupElement = kUnused; // ignore Original Attributes
		if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028))
			groupElement = kUnused; // ignore icon dimensions
#ifdef salvageAgfa					// issue435
		// Handle remapping using integers, and slower but simpler approach is with strings:
		//  https://github.com/pydicom/pydicom/blob/master/pydicom/_private_dict.py
		if (((groupElement & 65535) % 2) == 0)
			goto skipRemap; // remap odd (private) groups
		// printf("tag %04x,%04x\n", groupElement & 65535, groupElement >> 16);
		if (((groupElement >> 16) >= 0x10) && ((groupElement >> 16) <= 0xFF)) { // tags (gggg,0010-00FF) may define new remapping
			// if remapping tag
			// first: see if this remapping overwrites existing tag
			uint32_t privateCreatorMask = 0;  // 0 -> none
			uint32_t privateCreatorRemap = 0; // 0 -> none
			privateCreatorMask = (groupElement & 65535) + ((groupElement & 0xFFFF0000) << 8);
			if (nRemaps > 0) {
				int j = 0;
				for (int i = 0; i < nRemaps; i++) // remove duplicate remapping
					// copy all remaps except exact match
					if (privateCreatorMasks[i] != privateCreatorMask) {
						privateCreatorMasks[j] = privateCreatorMasks[i];
						privateCreatorRemaps[j] = privateCreatorRemaps[i];
						j++;
					}
				nRemaps = j;
			}
			// see if this is known private vendor tag
			privateCreatorRemap = 0;
			char privateCreator[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], privateCreator);
			//
			int s_len = strlen(privateCreator);
			for (int i = 0; i < s_len; i++)
				privateCreator[i] = toupper(privateCreator[i]);
			// next lines determine remapping, append as needed
			// Siemens https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl
			if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL)
				privateCreatorRemap = 0x0019 + (0x1000 << 16);
			if (strstr(privateCreator, "SIEMENS MR SDS 01") != NULL)
				privateCreatorRemap = 0x0021 + (0x1000 << 16);
			if (strstr(privateCreator, "SIEMENS MR SDI 02") != NULL)
				privateCreatorRemap = 0x0021 + (0x1100 << 16);
			if (strstr(privateCreator, "SIEMENS CSA HEADER") != NULL)
				privateCreatorRemap = 0x0029 + (0x1000 << 16);
			if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL)
				privateCreatorRemap = 0x0051 + (0x1000 << 16);
			// GE https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/gems.tpl
			if (strstr(privateCreator, "GEMS_ACQU_01") != NULL)
				privateCreatorRemap = 0x0019 + (0x1000 << 16);
			if (strstr(privateCreator, "GEMS_RELA_01") != NULL)
				privateCreatorRemap = 0x0021 + (0x1000 << 16);
			if (strstr(privateCreator, "GEMS_SERS_01") != NULL)
				privateCreatorRemap = 0x0025 + (0x1000 << 16);
			if (strstr(privateCreator, "GEMS_PARM_01") != NULL)
				privateCreatorRemap = 0x0043 + (0x1000 << 16);
			// ELSCINT https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/elscint.tpl
			int grp = (groupElement & 65535);
			if ((grp == 0x07a1) && (strstr(privateCreator, "ELSCINT1") != NULL))
				privateCreatorRemap = 0x07a1 + (0x1000 << 16);
			if ((grp == 0x07a3) && (strstr(privateCreator, "ELSCINT1") != NULL))
				privateCreatorRemap = 0x07a3 + (0x1000 << 16);
			// Philips https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/philips.tpl
			if (strstr(privateCreator, "PHILIPS IMAGING") != NULL) {
				if (strstr(privateCreator, "DD 001") != NULL)
					privateCreatorRemap = 0x2001 + (0x1000 << 16);
				if (strstr(privateCreator, "DD 002") != NULL)
					privateCreatorRemap = 0x2001 + (0x1100 << 16);
				if (strstr(privateCreator, "DD 003") != NULL)
					privateCreatorRemap = 0x2001 + (0x1200 << 16);
				if (strstr(privateCreator, "DD 004") != NULL)
					privateCreatorRemap = 0x2001 + (0x1300 << 16);
				if (strstr(privateCreator, "DD 005") != NULL)
					privateCreatorRemap = 0x2001 + (0x1400 << 16);
			}
			if (strstr(privateCreator, "PHILIPS MR IMAGING") != NULL) {
				if (strstr(privateCreator, "DD 001") != NULL)
					privateCreatorRemap = 0x2005 + (0x1000 << 16);
				if (strstr(privateCreator, "DD 002") != NULL)
					privateCreatorRemap = 0x2005 + (0x1100 << 16);
				if (strstr(privateCreator, "DD 003") != NULL)
					privateCreatorRemap = 0x2005 + (0x1200 << 16);
				if (strstr(privateCreator, "DD 004") != NULL)
					privateCreatorRemap = 0x2005 + (0x1300 << 16);
				if (strstr(privateCreator, "DD 005") != NULL)
					privateCreatorRemap = 0x2005 + (0x1400 << 16);
			}
			// UIH https://github.com/neurolabusc/dcm_qa_uih
			if (strstr(privateCreator, "Image Private Header") != NULL)
				privateCreatorRemap = 0x0065 + (0x1000 << 16);
			// sanity check: group should match
			if (grp != (int)(privateCreatorRemap & 65535))
				privateCreatorRemap = 0;
			if (privateCreatorRemap == 0)
				goto skipRemap; // this is not a known private group
			if (privateCreatorRemap == privateCreatorMask)
				goto skipRemap; // the remapping and mask are identical 2005,1000 -> 2005,1000
			if ((nRemaps + 1) >= kMaxRemaps)
				goto skipRemap; // all slots full (should never happen)
			// add new remapping
			privateCreatorMasks[nRemaps] = privateCreatorMask;
			privateCreatorRemaps[nRemaps] = privateCreatorRemap;
			// printf("new remapping %04x,%04x -> %04x,%04x\n", privateCreatorMask & 65535, privateCreatorMask >> 16, privateCreatorRemap & 65535, privateCreatorRemap >> 16);
			if (isVerbose > 1)
				printMessage("new remapping (%d) %04x,%02xxy -> %04x,%02xxy\n", nRemaps, privateCreatorMask & 65535, privateCreatorMask >> 24, privateCreatorRemap & 65535, privateCreatorRemap >> 24);
			nRemaps += 1;
			// for (int i = 0; i < nRemaps; i++)
			//	printf(" %d = %04x,%02xxy -> %04x,%02xxy\n", i, privateCreatorMasks[i] & 65535, privateCreatorMasks[i] >> 24, privateCreatorRemaps[i] & 65535, privateCreatorRemaps[i] >> 24);
			goto skipRemap;
		}
		if (nRemaps < 1)
			goto skipRemap;
		{
			uint32_t remappedGroupElement = 0;
			for (int i = 0; i < nRemaps; i++)
				if ((groupElement & 0xFF00FFFF) == (privateCreatorMasks[i] & 0xFF00FFFF))
					remappedGroupElement = privateCreatorRemaps[i] + (groupElement & 0x00FF0000);
			if (remappedGroupElement == 0)
				goto skipRemap;
			if (isVerbose > 1)
				printMessage("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16);
			groupElement = remappedGroupElement;
		}
#endif // salvageAgfa
	skipRemap:
		if ((lLength % 2) != 0) { // https://www.nitrc.org/forum/forum.php?thread_id=11827&forum_id=4703
			printMessage("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535, groupElement >> 16, lLength, fname);
			// proper to return here, but we can carry on as a hail mary
			//  d.isValid = false;
		}
		if (lLength > 0) // issue695: skip empty tags, "gdcmanon --dumb --empty 0018,0089 good.dcm bad.dcm"
			if (sqDepth < 1 && isDeidentificationMethodCodeSequence && groupElement != kItemDelimitationTag && groupElement != kItemTag)
				isDeidentificationMethodCodeSequence = false;
		switch (groupElement) {
		case kMediaStorageSOPClassUID: {
			char mediaUID[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], mediaUID);
			// Philips "XX_" files
			// see https://github.com/rordenlab/dcm2niix/issues/328
			if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66.4") != NULL) // Segmentation Storage
				d.isDerived = true;										  // Segmentation IOD, issue 572
			else if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66") != NULL)
				d.isRawDataStorage = true; // e.g. Raw Data IOD, https://dicom.nema.org/dicom/2013/output/chtml/part04/sect_i.4.html
			if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.77.1.6") != NULL)
				d.isMicroscopy = true;
			if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.1") != NULL)
				d.isRawDataStorage = true; // Private MR Spectrum Storage
			if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.2") != NULL)
				d.isRawDataStorage = true; // Private MR Series Data Storage
			if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.4") != NULL)
				d.isRawDataStorage = true; // Private MR Examcard Storage
			if (d.isRawDataStorage)
				d.isDerived = true;
			if (d.isRawDataStorage)
				printMessage("Skipping non-image DICOM: %s\n", fname);
			// Philips "PS_" files
			if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.11.1") != NULL)
				d.isGrayscaleSoftcopyPresentationState = true;
			if (d.isGrayscaleSoftcopyPresentationState)
				d.isDerived = true;
			break;
		}
		case kMediaStorageSOPInstanceUID: { // 0002, 0003
			// char SOPInstanceUID[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], d.instanceUID);
			// printMessage(">>%s\n", d.seriesInstanceUID);
			d.instanceUidCrc = mz_crc32X((unsigned char *)&d.instanceUID, strlen(d.instanceUID));
			break;
		}
		case kTransferSyntax: {
			// if (sqDepth04000561 >= 0) break;
			char transferSyntax[kDICOMStr];
			strcpy(transferSyntax, "");
			dcmStr(lLength, &buffer[lPos], transferSyntax);
			strcpy(d.transferSyntax, transferSyntax);
			if (strcmp(transferSyntax, "1.2.840.10008.1.2.1") == 0)
				; // default isExplicitVR=true; //d.isLittleEndian=true
			else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.50") == 0) {
				d.compressionScheme = kCompress50;
				// printMessage("Lossy JPEG: please decompress with Osirix or dcmdjpg. %s\n", transferSyntax);
				// d.imageStart = 1; //abort as invalid (imageStart MUST be >128)
			} else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.51") == 0) {
				d.compressionScheme = kCompress50;
			} else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.57") == 0) {
				// d.isCompressed = true;
				// https://www.medicalconnections.co.uk/kb/Transfer_Syntax should be SOF = 0xC3
				d.compressionScheme = kCompressC3;
				// printMessage("Ancient JPEG-lossless (SOF type 0xc3): please check conversion\n");
			} else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.70") == 0) {
				d.compressionScheme = kCompressC3;
			} else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.4.80") == 0) || (strcmp(transferSyntax, "1.2.840.10008.1.2.4.81") == 0)) {
#if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1)
				d.compressionScheme = kCompressJPEGLS;
#else
					printWarning("Unsupported transfer syntax '%s' (decode with 'dcmdjpls jpg.dcm raw.dcm' or 'gdcmconv -w jpg.dcm raw.dcm', or recompile dcm2niix with JPEGLS support)\n", transferSyntax);
					d.imageStart = 1; // abort as invalid (imageStart MUST be >128)
#endif
			} else if (strcmp(transferSyntax, "1.3.46.670589.33.1.4.1") == 0) {
				d.compressionScheme = kCompressPMSCT_RLE1;
				// printMessage("Unsupported transfer syntax '%s' (decode with rle2img)\n",transferSyntax);
				// d.imageStart = 1; //abort as invalid (imageStart MUST be >128)
			} else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.90") == 0)) {
				d.compressionScheme = kCompressYes;
				// printMessage("JPEG2000 Lossless support is new: please validate conversion\n");
			} else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.201") == 0)) {
				d.compressionScheme = kCompressYes; //High-Throughput JPEG 2000 issue 897
			} else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.203") == 0)) {
				d.compressionScheme = kCompressYes; //High-Throughput JPEG 2000 issue 897
			} else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.1.99") == 0)) {
				// n.b. Deflate compression applied applies to the encoding of the **entire** DICOM Data Set, not just image data
				//  see https://www.medicalconnections.co.uk/kb/Transfer-Syntax/
				// #ifndef myDisableZLib
				// d.compressionScheme = kCompressDeflate;
				// #else
				printWarning("Unsupported transfer syntax '%s' (inflate files with 'dcmconv +te gz.dcm raw.dcm' or 'gdcmconv -w gz.dcm raw.dcm)'\n", transferSyntax);
				d.imageStart = 1; // abort as invalid (imageStart MUST be >128)
								  // #endif
			} else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.91") == 0)) {
				d.compressionScheme = kCompressYes;
				// printMessage("JPEG2000 support is new: please validate conversion\n");
			} else if (strcmp(transferSyntax, "1.2.840.10008.1.2.5") == 0)
				d.compressionScheme = kCompressRLE; // run length
			else if (strcmp(transferSyntax, "1.2.840.10008.1.2.2") == 0)
				isSwitchToBigEndian = true; // isExplicitVR=true;
			else if (strcmp(transferSyntax, "1.2.840.10008.1.2") == 0)
				isSwitchToImplicitVR = true; // d.isLittleEndian=true
			else {
				if (lLength < 1) //"1.2.840.10008.1.2"
					printWarning("Missing transfer syntax: assuming default (1.2.840.10008.1.2)\n");
				else {
					printWarning("Unsupported transfer syntax '%s' (see www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage)\n", transferSyntax);
					d.imageStart = 1; // abort as invalid (imageStart MUST be >128)
				}
			}
			break;
		} //{} provide scope for variable 'transferSyntax
		case kImplementationVersionName: {
			char impTxt[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], impTxt);
			int slen = (int)strlen(impTxt);
			if ((slen > 5) && (strstr(impTxt, "MATLAB") != NULL))
				isMATLAB = true;
			if ((slen < 5) || (strstr(impTxt, "XA10A") == NULL))
				break;
			d.isXA10A = true;
			break;
		}
		case kSourceApplicationEntityTitle: {
			char saeTxt[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], saeTxt);
			int slen = (int)strlen(saeTxt);
			if ((slen < 5) || (strstr(saeTxt, "oasis") == NULL))
				break;
			d.isSegamiOasis = true;
			break;
		}
		/* case kSpecificCharacterSet : {
			char charTxt[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], charTxt);
			//issue878 dcm_qa_uih is ISO_IR 192 aka  UTF-8 !
			if (strcmp(charTxt, "ISO_IR 100") != 0) {
				printWarning("Issue878: Specific Character Set (0008,0005) is not ISO-IR 100. Non-latin characters may be mangled.\n");
			}
		} */
		case kDirectoryRecordSequence: {
			d.isRawDataStorage = true;
			break;
		}
		case kImageTypeTag: {
			bool is1st = strlen(d.imageType) == 0;
			dcmStr(lLength, &buffer[lPos], d.imageType, false); //<-distinguish spaces from pathdelim: [ORIGINAL\PHASE MAP\FFE] should return "PHASE MAP" not "PHASE_MAP"
			int slen;
			slen = (int)strlen(d.imageType);
			if (slen > 1) {
				for (int i = 0; i < slen; i++)
					if (d.imageType[i] == '\\')
						d.imageType[i] = '_';
			}
			if (is1st)
				strcpy(imageType1st, d.imageType);
			if ((slen > 5) && strstr(d.imageType, "_MOCO_")) {
				// d.isDerived = true; //this would have 'i- y' skip MoCo images
				isMoCo = true;
			}
			if ((slen > 5) && strstr(d.imageType, "B0") && strstr(d.imageType, "MAP"))
				d.isRealIsPhaseMapHz = true;
			if ((slen > 5) && strstr(d.imageType, "_ADC_"))
				d.isDerived = true;
			if ((slen > 5) && strstr(d.imageType, "_TRACEW_"))
				d.isDerived = true;
			if ((slen > 5) && strstr(d.imageType, "_TRACE_"))
				d.isDerived = true;
			if ((slen > 5) && strstr(d.imageType, "_FA_"))
				d.isDerived = true;
			if ((slen > 12) && strstr(d.imageType, "_DIFFUSION_"))
				d.isDiffusion = true;
			// if (strcmp(transferSyntax, "ORIGINAL_PRIMARY_M_ND_MOSAIC") == 0)
			if ((slen > 5) && !strcmp(d.imageType + slen - 6, "MOSAIC"))
				isMosaic = true;
			// const char* prefix = "MOSAIC";
			const char *pos = strstr(d.imageType, "MOSAIC");
			// const char p = (const char *) d.imageType;
			// p = (const char) strstr(d.imageType, "MOSAIC");
			// const char* p = strstr(d.imageType, "MOSAIC");
			if (pos != NULL)
				isMosaic = true;
			// isNonImage 0008,0008 = DERIVED,CSAPARALLEL,POSDISP
			//  sometime ComplexImageComponent 0008,9208 is missing - see ADNI data
			//  attempt to detect non-images, see https://github.com/scitran/data/blob/a516fdc39d75a6e4ac75d0e179e18f3a5fc3c0af/scitran/data/medimg/dcm/mr/siemens.py
			// For Philips combinations see Table 3-28 Table 3-28: Valid combinations of Image Type applied values
			//  http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/5144982/DICOM_Conformance_Statement_Intera_R7%2c_R8_and_R9.pdf%3fnodeid%3d5147977%26vernum%3d-2
			if ((slen > 3) && (strstr(d.imageType, "_R_") != NULL)) {
				d.isHasReal = true;
				isReal = true;
			}
			if ((slen > 3) && (strstr(d.imageType, "_M_") != NULL)) {
				d.isHasMagnitude = true;
				isMagnitude = true;
			}
			if ((slen > 3) && (strstr(d.imageType, "_I_") != NULL)) {
				d.isHasImaginary = true;
				isImaginary = true;
			}
			if ((slen > 3) && (strstr(d.imageType, "_P_") != NULL)) {
				d.isHasPhase = true;
				isPhase = true;
			}
			if ((slen > 6) && (strstr(d.imageType, "_REAL_") != NULL)) {
				d.isHasReal = true;
				isReal = true;
			}
			if ((slen > 11) && (strstr(d.imageType, "_MAGNITUDE_") != NULL)) {
				d.isHasMagnitude = true;
				isMagnitude = true;
			}
			if ((slen > 11) && (strstr(d.imageType, "_IMAGINARY_") != NULL)) {
				d.isHasImaginary = true;
				isImaginary = true;
			}
			if ((slen > 6) && (strstr(d.imageType, "PHASE") != NULL)) {
				d.isHasPhase = true;
				isPhase = true;
			}
			if ((slen > 6) && (strstr(d.imageType, "DERIVED") != NULL))
				d.isDerived = true;
			// if((slen > 4) && (strstr(typestr, "DIS2D") != NULL) )
			//	d.isNonImage = true;
			// not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image
			break;
		}
		case kAcquisitionDate:
			char acquisitionDateTxt[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], acquisitionDateTxt);
			d.acquisitionDate = atof(acquisitionDateTxt);
			break;
		case kAcquisitionDateTime:
			// char acquisitionDateTimeTxt[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], acquisitionDateTimeTxt);
			// printMessage("%s\n",acquisitionDateTimeTxt);
			break;
			/* Failed attempt to infer slice timing for Philips MRI
			https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6

					case kSOPInstanceUID: {
						if (d.manufacturer != kMANUFACTURER_PHILIPS) break;
						char uid[kDICOMStrLarge];
						dcmStr(lLength, &buffer[lPos], uid, true);
						char *timeStr = strrchr(uid, '.');
						//nb Manufacturer (0008,0070) comes AFTER (0008,0018) SOPInstanceUID.
						//format of (0008,0018) UI
						//[1.23.4.2019051416101221842
						//       .YYYYMMDDHHmmssxxxxx
						timeStr++; //skip "."
						if ((strlen(timeStr) != 19) || (strlen(d.studyDate) < 8)) break;
						bool sameDay = true;
						for (int z = 0; z < 8; z++)
							if (timeStr[z] != d.studyDate[z]) sameDay = false;
						if (!sameDay)
							printf("SOPInstanceUID does not match StudyDate: assuming study cross midnight\n");
						char *hourStr = timeStr + 8; //Skip 8 charactersYear,Month,Day YYYYMMDD
						acquisitionTimePhilips = (double) atof(hourStr) * (double) 0.00001;
						//printf("   %s  %s  %f\n", timeStr, hourStr, acquisitionTimePhilips);

						break;
					}
			*/
		case kStudyDate:
			dcmStr(lLength, &buffer[lPos], d.studyDate);
			if (((int)strlen(d.studyDate) > 7) && (strstr(d.studyDate, "19000101") != NULL))
				isNeologica = true;
			break;
		case kModality:
			if (lLength < 2)
				break;
			if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'R'))
				d.modality = kMODALITY_CR;
			else if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'T'))
				d.modality = kMODALITY_CT;
			if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'R'))
				d.modality = kMODALITY_MR;
			if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'T'))
				d.modality = kMODALITY_PT;
			if ((buffer[lPos] == 'U') && (toupper(buffer[lPos + 1]) == 'S'))
				d.modality = kMODALITY_US;
			break;
		case kManufacturer:
			if (d.manufacturer == kMANUFACTURER_UNKNOWN)
				d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]);
			volDiffusion.manufacturer = d.manufacturer;
			break;
		case kInstitutionName:
			dcmStr(lLength, &buffer[lPos], d.institutionName);
			break;
		case kInstitutionAddress: // VR is "ST": 1024 chars maximum
			dcmStr(lLength, &buffer[lPos], d.institutionAddress, true);
			break;
		case kReferringPhysicianName:
			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:
			if (is2005140FSQ)
				break; // see Maastricht DICOM data for magnitude data with this field set as REAL! https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging
			if (lLength < 2)
				break;
			// issue 256: Philips files report real ComplexImageComponent but Magnitude ImageType https://github.com/rordenlab/dcm2niix/issues/256
			isPhase = false;
			isReal = false;
			isImaginary = false;
			isMagnitude = false;
			// see Table C.8-85 http://dicom.nema.org/medical/Dicom/2017c/output/chtml/part03/sect_C.8.13.3.html
			if ((buffer[lPos] == 'R') && (toupper(buffer[lPos + 1]) == 'E'))
				isReal = true;
			if ((buffer[lPos] == 'I') && (toupper(buffer[lPos + 1]) == 'M'))
				isImaginary = true;
			if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'H'))
				isPhase = true;
			if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'A'))
				isMagnitude = true;
			// not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image
			if (isPhase)
				d.isHasPhase = true;
			if (isReal)
				d.isHasReal = true;
			if (isImaginary)
				d.isHasImaginary = true;
			if (isMagnitude)
				d.isHasMagnitude = true;
			break;
		case kAcquisitionContrast:
			char acqContrast[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], acqContrast);
			if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "DIFFUSION") != NULL))
				d.isDiffusion = true;
			// if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "PERFUSION") != NULL))
			//	isASL = true; //see series 301 of dcm_qa_philips_asl
			break;
		case kAcquisitionTime: {
			char acquisitionTimeTxt[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], acquisitionTimeTxt);
			d.acquisitionTime = atof(acquisitionTimeTxt);
			if (d.manufacturer != kMANUFACTURER_UIH)
				break;
			// UIH slice timing- do not use for Siemens as Siemens de-identification can corrupt this field https://github.com/rordenlab/dcm2niix/issues/236
			d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.acquisitionTime;
			acquisitionTimesGE_UIH++;
			break;
		}
		/*case kContentTime: {
			char contentTimeTxt[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], contentTimeTxt);
			d.contentTime = atof(contentTimeTxt);
		}*/
		case kSeriesTime:
			dcmStr(lLength, &buffer[lPos], seriesTimeTxt);
			break;
		case kStudyTime:
			if (strlen(d.studyTime) < 2)
				dcmStr(lLength, &buffer[lPos], d.studyTime);
			break;
		case kInjectedVolumeGE:
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.injectedVolume = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kReconFilterSizeGE:
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.reconFilterSize = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kIconSQ: { // issue760 GEIIS icon strikes again
			if ((vr[0] != 'S') || (vr[1] != 'Q') || (lLength != 8))
				break; // only risk kludge for explicit VR with length
			isIconImageSequence = true;
			if (sqDepthIcon < 0)
				sqDepthIcon = sqDepth;
			break;
		}
		case kPatientName:
			dcmStr(lLength, &buffer[lPos], d.patientName);
			break;
		case kAnatomicalOrientationType: {
			char aotTxt[kDICOMStr]; // ftp://dicom.nema.org/MEDICAL/dicom/2015b/output/chtml/part03/sect_C.7.6.2.html#sect_C.7.6.2.1.1
			dcmStr(lLength, &buffer[lPos], aotTxt);
			int slen = (int)strlen(aotTxt);
			if ((slen < 9) || (strstr(aotTxt, "QUADRUPED") == NULL))
				break;
			d.isQuadruped = true;
			// printError("Anatomical Orientation Type (0010,2210) is QUADRUPED: rotate coordinates accordingly\n");
			break;
		}
		case kDeidentificationMethod: { // issue 383
			dcmStr(lLength, &buffer[lPos], d.deidentificationMethod);
			int slen = (int)strlen(d.deidentificationMethod);
			if ((slen < 10) || (strstr(d.deidentificationMethod, "DICOMANON") == NULL))
				break;
			isDICOMANON = true;
			printWarning("Matlab DICOMANON can scramble SeriesInstanceUID (0020,000e) and remove crucial data (see issue 383). \n");
			break;
		}
		case kDeidentificationMethodCodeSequence: {
			isDeidentificationMethodCodeSequence = true;
			break;
		}
		case kCodeValue: {
			if (isDeidentificationMethodCodeSequence && d.deID_CS_n < MAX_DEID_CS)
				dcmStr(lLength, &buffer[lPos], dti4D->deID_CS[d.deID_CS_n].CodeValue);
			break;
		}
		case kCodingSchemeDesignator: {
			if (isDeidentificationMethodCodeSequence && d.deID_CS_n < MAX_DEID_CS)
				dcmStr(lLength, &buffer[lPos], dti4D->deID_CS[d.deID_CS_n].CodingSchemeDesignator);
			break;
		}
		case kCodingSchemeVersion: {
			if (isDeidentificationMethodCodeSequence && d.deID_CS_n < MAX_DEID_CS)
				dcmStr(lLength, &buffer[lPos], dti4D->deID_CS[d.deID_CS_n].CodingSchemeVersion);
			break;
		}
		case kCodeMeaning: {
			if (isDeidentificationMethodCodeSequence && d.deID_CS_n < MAX_DEID_CS) {
				dcmStr(lLength, &buffer[lPos], dti4D->deID_CS[d.deID_CS_n].CodeMeaning);
				d.deID_CS_n++;
			}
			break;
		}
		case kPatientID:
			if (strlen(d.patientID) > 1)
				break;
			dcmStr(lLength, &buffer[lPos], d.patientID);
			break;
		case kAccessionNumber:
			dcmStr(lLength, &buffer[lPos], d.accessionNumber);
			break;
		case kPatientBirthDate:
			dcmStr(lLength, &buffer[lPos], d.patientBirthDate);
			break;
		case kPatientSex: {
			// must be M,F,O: http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.2.html
			char patientSex = toupper(buffer[lPos]);
			if ((patientSex == 'M') || (patientSex == 'F') || (patientSex == 'O'))
				d.patientSex = patientSex;
			break;
		}
		case kPatientAge:
			dcmStr(lLength, &buffer[lPos], d.patientAge);
			break;
		case kPatientSize:
			d.patientSize = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kPatientWeight:
			d.patientWeight = dcmStrFloat(lLength, &buffer[lPos]);
			break;
			/* n.b. check GeneralElectricAdvance-NIMH example dataset: need to specify SQ
				(0018,0031) LO [FDG -- fluorodeoxyglucose ]                   # 26,1 Radiopharmaceutical
				(0018,1071) DS [0 ]                                           # 2,1 Radiopharmaceutical Volume
				(0018,1072) TM [092345.00 ]                                   # 10,1 Radiopharmaceutical Start Time
				(0018,1074) DS [      75850000]                               # 14,1 Radionuclide Total Dose
				(0018,1075) DS [6588]                                         # 4,1 Radionuclide Half Life
				(0018,1076) DS [0.97000002861023]                             # 16,1 Radionuclide Positron Fraction
				(0054,0300) SQ (Sequence with undefined length)               # u/l,1 Radionuclide Code Sequence
				  (fffe,e000) na (Item with defined length)
					(0008,0100) SH [C-111A1 ]                                 # 8,1 Code Value
					(0008,0102) SH [99SDM ]                                   # 6,1 Coding Scheme Designator
					(0008,0104) LO [18F ]                                     # 4,1 Code Meaning
				(fffe,e0dd)
				(0054,0304) SQ (Sequence with undefined length)               # u/l,1 Radiopharmaceutical Code Sequence
				  (fffe,e000) na (Item with defined length)
					(0008,0100) SH [Y-X1743 ]                                 # 8,1 Code Value
					(0008,0102) SH [99SDM ]                                   # 6,1 Coding Scheme Designator
					(0008,0104) LO [FDG -- fluorodeoxyglucose ]               # 26,1 Code Meaning
				(fffe,e0dd)
					case kTracerRadionuclide1:
					case kTracerRadionuclide2:
						dcmStr(lLength, &buffer[lPos], d.tracerRadionuclide);
						break;*/
		case kStationName:
			dcmStr(lLength, &buffer[lPos], d.stationName);
			break;
		case kStudyDescription:
			dcmStr(lLength, &buffer[lPos], d.studyDescription);
			break;
		case kSeriesDescription:
			dcmStr(lLength, &buffer[lPos], d.seriesDescription);
#ifdef USING_DCM2NIIXFSWRAPPER
			remove_specialchars(d.seriesDescription);
#endif
			break;
		case kInstitutionalDepartmentName:
			dcmStr(lLength, &buffer[lPos], d.institutionalDepartmentName);
			break;
		case kFrameNum:
			d.frameNum = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kManufacturersModelName:
			dcmStr(lLength, &buffer[lPos], d.manufacturersModelName);
			break;
		case kDerivationDescription: {
			// strcmp(transferSyntax, "1.2.840.10008.1.2")
			char derivationDescription[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], derivationDescription); // strcasecmp, strcmp
			if (strcasecmp(derivationDescription, "MEDCOM_RESAMPLED") == 0)
				d.isResampled = true;
			break;
		}
		case kDeviceSerialNumber: {
			dcmStr(lLength, &buffer[lPos], d.deviceSerialNumber);
			break;
		}
		case kSoftwareVersions: {
			dcmStr(lLength, &buffer[lPos], d.softwareVersions);
			int slen = (int)strlen(d.softwareVersions);
			swVers = dcmStrInt(lLength, &buffer[lPos]);
			if ((slen > 4) && (strstr(d.softwareVersions, "XA10") != NULL))
				d.isXA10A = true;
			if ((slen > 4) && (strstr(d.softwareVersions, "XA11") != NULL))
				d.isXA10A = true;
			if ((slen > 4) && (strstr(d.softwareVersions, "XA20") != NULL))
				d.isXA10A = true;
			if ((slen > 4) && (strstr(d.softwareVersions, "XA30") != NULL))
				d.isXA10A = true;
			if ((slen > 4) && (strstr(d.softwareVersions, "XA31") != NULL))
				d.isXA10A = true;
			//isXA10A is designed to catch early Siemens bugs, while isSiemensXA also detect modern XA
			if (d.isXA10A)
				isSiemensXA = true;
			if ((slen > 4) && (strstr(d.softwareVersions, "XA5") != NULL))
				isSiemensXA = true; //XA50/XA51
			if ((slen > 4) && (strstr(d.softwareVersions, "XA6") != NULL))
				isSiemensXA = true; //XA60
			if ((slen > 4) && (strstr(d.softwareVersions, "XA7") != NULL))
				isSiemensXA = true; //XA70
			break;
		}
		case kProtocolName: {
			dcmStr(lLength, &buffer[lPos], d.protocolName);
			break;
		}
		case kPatientOrient:
			dcmStr(lLength, &buffer[lPos], d.patientOrient);
			break;
		case kPulseSequenceName:
			dcmStr(lLength, &buffer[lPos], d.pulseSequenceName);
			break;
		case kInversionRecovery: // CS [YES],[NO]
			if (lLength < 2)
				break;
			if (toupper(buffer[lPos]) == 'Y')
				d.isIR = true;
			break;
		case kSpoiling: // CS 0018,9016
			if (lLength < 2)
				break;
			char sTxt[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], sTxt);
			if ((strstr(sTxt, "RF") != NULL) && (strstr(sTxt, "RF") != NULL))
				d.spoiling = kSPOILING_RF_AND_GRADIENT;
			else if (strstr(sTxt, "RF") != NULL)
				d.spoiling = kSPOILING_RF;
			else if (strstr(sTxt, "GRADIENT") != NULL)
				d.spoiling = kSPOILING_GRADIENT;
			else if (strstr(sTxt, "NONE") != NULL)
				d.spoiling = kSPOILING_NONE;
			break;
		case kEchoPlanarPulseSequence: // CS [YES],[NO]
			if (lLength < 2)
				break;
			if (toupper(buffer[lPos]) == 'Y')
				d.isEPI = true;
			break;
		case kMagnetizationTransferAttribute: //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE'
			if (lLength < 2)
				break;																  // https://github.com/bids-standard/bids-specification/pull/681
			if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos + 1]) == 'F')) // OFF_RESONANCE
				d.mtState = 1;														  // TRUE
			if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos + 1]) == 'N')) // ON_RESONANCE and NONE
				d.mtState = 0;														  // FALSE
			if ((toupper(buffer[lPos]) == 'N') && (toupper(buffer[lPos + 1]) == 'O')) // ON_RESONANCE and NONE
				d.mtState = 0;														  // FALSE
			break;
		case kRectilinearPhaseEncodeReordering: { //'CS' [REVERSE_LINEAR],[LINEAR],[CENTRIC],[REVERSE_CENTRIC]
			if (d.manufacturer != kMANUFACTURER_GE)
				break; // only found in GE software beginning with RX27
			if (lLength < 2)
				break;
			if (toupper(buffer[lPos]) == 'L')
				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; // issue674, (0018, 9034) LINEAR-->FLIPPED
			if (toupper(buffer[lPos]) == 'R')
				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; // issue674, R(0018, 9034) REVERSE_LINEAR-->UNFLIPPED
			break;
		}
		case kPartialFourierDirection: { //'CS' PHASE FREQUENCY SLICE_SELECT COMBINATION
			if (lLength < 2)
				break;
			if (toupper(buffer[lPos]) == 'P')
				d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_PHASE;
			if (toupper(buffer[lPos]) == 'F')
				d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_FREQUENCY;
			if (toupper(buffer[lPos]) == 'S')
				d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT;
			if (toupper(buffer[lPos]) == 'C')
				d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION;
			break;
		}
		/*case kCardiacSynchronizationTechnique:
			if (toupper(buffer[lPos]) == 'P')
				isProspectiveSynced = true;
			break;*/
		case kParallelReductionFactorInPlane:
			accelFactPE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			if (d.manufacturer == kMANUFACTURER_SIEMENS)
				break;
			d.accelFactPE = accelFactPE;
			break;
		case kAcquisitionDuration:
			if (!isSameFloatGE(d.acquisitionDuration, 0.0))
				break; // issue 808: give precedence to more precise measures, e.g kAcquisitionDurationGE (0019,105a)
			// 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);
			double dTime = DateTimeToTime(dateTime);
			printf("%s\t FrameAcquisitionDateTime %0.4f \n", dateTime, dTime);
			//d.triggerDelayTime = dTime;
		}*/
		case kAcquisitionDurationGE: // issue 808
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.acquisitionDuration = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			d.acquisitionDuration /= 1000000.0; // convert microsec to sec
			break;
		case kDiffusionDirectionality: { // 0018, 9075
			set_directionality0018_9075(&volDiffusion, (&buffer[lPos]));
			if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 10))
				break;
			char dir[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], dir);
			if (strcmp(dir, "ISOTROPIC") == 0)
				isPhilipsDerived = true;
			break;
		}
		case kParallelAcquisitionTechnique: // CS
			dcmStr(lLength, &buffer[lPos], d.parallelAcquisitionTechnique);
			break;
		case kInversionTimes: { // issue 380
			if ((lLength < 8) || ((lLength % 8) != 0))
				break;
			d.TI = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		}
		case kPartialFourier: //(0018,9081) CS [YES],[NO]
			if (lLength < 2)
				break;
			if (toupper(buffer[lPos]) == 'Y')
				d.isPartialFourier = true;
			break;
		case kMREchoSequence:
			if (d.manufacturer != kMANUFACTURER_BRUKER)
				break;
			if (sqDepth == 0)
				sqDepth = 1; // should not happen, in case faulty anonymization
			sqDepth00189114 = sqDepth - 1;
			break;
		case kMRAcquisitionPhaseEncodingStepsInPlane:
			d.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: {
			int etl = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			if (etl < 2) // issue 717
				break;
			d.echoTrainLength = etl;
			break;
		}
		case kNumberOfImagesInMosaic:
			if (d.manufacturer == kMANUFACTURER_SIEMENS)
				numberOfImagesInMosaic = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kSeriesPlaneGE: // SS 2=Axi, 4=Sag, 8=Cor, 16=Obl, 256=3plane
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			if (dcmInt(lLength, &buffer[lPos], d.isLittleEndian) == 256)
				d.isLocalizer = true;
			break;
		case kDwellTime:
			d.dwellTime = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kDiffusion_bValueSiemens:
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			// issue409
			B0Philips = dcmStrInt(lLength, &buffer[lPos]);
			set_bVal(&volDiffusion, B0Philips);
			break;
		case kSliceTimeSiemens: { // Array of FD (64-bit double)
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			if ((lLength < 8) || ((lLength % 8) != 0))
				break;
			int nSlicesTimes = lLength / 8;
			if (nSlicesTimes > kMaxEPI3D)
				break;
			d.CSA.mosaicSlices = nSlicesTimes;
			// printf(">>>> %d\n", nSlicesTimes);
			// issue 296: for images de-identified to remove readCSAImageHeader
			for (int z = 0; z < nSlicesTimes; z++)
				d.CSA.sliceTiming[z] = dcmFloatDouble(8, &buffer[lPos + (z * 8)], d.isLittleEndian);
			// for (int z = 0; z < nSlicesTimes; z++)
			//	printf("%d>>>%g\n", z+1, d.CSA.sliceTiming[z]);
			checkSliceTimes(&d.CSA, nSlicesTimes, isVerbose, d.is3DAcq);
			// d.CSA.dtiV[0] = dcmStrInt(lLength, &buffer[lPos]);
			// d.CSA.numDti = 1;
			break;
		}
		case kDiffusionGradientDirectionSiemens: {
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			float v[4];
			dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian);
			// dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v);
			// printf(">>>%g %g %g\n", v[0], v[1], v[2]);
			d.CSA.dtiV[1] = v[0];
			d.CSA.dtiV[2] = v[1];
			d.CSA.dtiV[3] = v[2];
			break;
		}
		case kImaRelTablePosition: {
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &d.CSA.tablePos[0]); // slice position
			d.CSA.tablePos[3] = -d.CSA.tablePos[3];								  // reverse Z polarity, issue 726
			d.CSA.tablePos[0] = 1.0;											  // set
			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;
			float f = dcmStrFloat(lLength, &buffer[lPos]);
			d.numberOfDiffusionDirectionGE = round(f);
			break;
		}
		case kVelocityEncodeScaleGE: {
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.velocityEncodeScaleGE = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		}
		case kLastScanLoc:
			d.lastScanLoc = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		// GE bug: multiple echos can create identical instance numbers
		//  in theory, one could detect as kRawDataRunNumberGE varies
		//  sliceN of echoE will have the same value for all timepoints
		//  this value does not appear indexed
		//  different echoes record same echo time.
		//  use multiEchoSortGEDICOM.py to salvage
		case kRawDataRunNumberGE:
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.rawDataRunNumber = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kMaxEchoNumGE:
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.maxEchoNumGE = round(dcmStrFloat(lLength, &buffer[lPos]));
			break;
		case 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);
			break;
		case kDiffusionDirectionGEY:
			if (d.manufacturer == kMANUFACTURER_GE)
				set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 1);
			break;
		case kDiffusionDirectionGEZ:
			if (d.manufacturer == kMANUFACTURER_GE)
				set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 2);
			break;
		case kPulseSequenceNameGE: { // LO 'epi'/'epiRT'
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			dcmStr(lLength, &buffer[lPos], d.pulseSequenceName);
			if (strstr(d.pulseSequenceName, "epi_pepolar") != NULL) {
				// if ((strstr( d.pulseSequenceName, "epi_pepolar") != NULL) || (strstr( d.pulseSequenceName, "epi2_pepolar") != NULL)){
				d.epiVersionGE = kGE_EPI_PEPOLAR_FWD; // n.b. combine with 0019,10B3
			} else if (strstr(d.pulseSequenceName, "epi2") != NULL) {
				d.epiVersionGE = kGE_EPI_EPI2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2
			} else if (strstr(d.pulseSequenceName, "epiRT") != NULL) {
				d.epiVersionGE = kGE_EPI_EPIRT; //-1 = not epi, 0 = epi, 1 = epiRT
			} else if (strstr(d.pulseSequenceName, "epi") != NULL) {
				d.epiVersionGE = kGE_EPI_EPI; //-1 = not epi, 0 = epi, 1 = epiRT
			}
			if (strstr(d.pulseSequenceName, "b0map")) {
				isGEfieldMap = true; // issue501
			}
			break;
		}
		case kInternalPulseSequenceNameGE: { // LO 'EPI'(gradient echo)/'EPI2'(spin echo):
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			char epiStr[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], epiStr);
			strcat(d.phaseEncodingDirectionDisplayedUIH, epiStr); // overload
			if ((d.epiVersionGE < kGE_EPI_PEPOLAR_FWD) && (strcmp(epiStr, "EPI") == 0)) {
				d.internalepiVersionGE = 1; //-1 = not EPI, 1 = EPI, 2 = EPI2
				if (d.epiVersionGE != 1) {	// 1 = epiRT by kEpiRTGroupDelayGE or kPulseSequenceNameGE
					d.epiVersionGE = 0;		// 0 = epi (multi-phase epi)
				}
			}
			if (strcmp(epiStr, "EPI2") == 0) {
				d.internalepiVersionGE = 2; //-1 = not epi, 1 = EPI, 2 = EPI2
			}
			if (strcmp(epiStr, "B0map") == 0) {
				isGEfieldMap = true; // issue501
			}
			break;
		}
		case kBandwidthPerPixelPhaseEncode:
			d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kStudyInstanceUID: // 0020,000D
			dcmStr(lLength, &buffer[lPos], d.studyInstanceUID);
			break;
		case kSeriesInstanceUID: // 0020,000E
			if (sqDepth > seriesInstanceUIDsqDepth)
				break; // issue655
			seriesInstanceUIDsqDepth = sqDepth;
			dcmStr(lLength, &buffer[lPos], d.seriesInstanceUID);
			d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID));
			break;
		case kImagePositionPatient: {
			if (is2005140FSQ) {
				dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPositionPrivate[0]);
				break;
			}
			patientPositionNum++;
			isAtFirstPatientPosition = true;
			// char dx[kDICOMStr];
			// dcmStr(lLength, &buffer[lPos], dx);
			// printMessage("*%s*", dx);
			dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPosition[0]); // slice position
			if (isnan(d.patientPosition[1])) {
				// dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPosition[0]); //slice position
				for (int k = 0; k < 4; k++)
					d.patientPosition[k] = patientPosition[k];
			} else {
				// dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPositionLast[0]); //slice direction for 4D
				for (int k = 0; k < 4; k++)
					d.patientPositionLast[k] = patientPosition[k];
				if ((isFloatDiff(d.patientPositionLast[1], d.patientPosition[1])) ||
					(isFloatDiff(d.patientPositionLast[2], d.patientPosition[2])) ||
					(isFloatDiff(d.patientPositionLast[3], d.patientPosition[3]))) {
					isAtFirstPatientPosition = false; // this slice is not at position of 1st slice
													  // if (d.patientPositionSequentialRepeats == 0) //this is the first slice with different position
													  //	d.patientPositionSequentialRepeats = patientPositionNum-1;
				} // if different position from 1st slice in file
			} // if not first slice in file
			set_isAtFirstPatientPosition_tvd(&volDiffusion, isAtFirstPatientPosition);
			// if (isAtFirstPatientPosition) numFirstPatientPosition++;
			if (isVerbose > 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);
				if (sliceMM[nSliceMM] < minSliceMM) {
					minSliceMM = sliceMM[nSliceMM];
					for (int k = 0; k < 4; k++)
						minPatientPosition[k] = patientPosition[k];
				}
				if (sliceMM[nSliceMM] > maxSliceMM) {
					maxSliceMM = sliceMM[nSliceMM];
					for (int k = 0; k < 4; k++)
						maxPatientPosition[k] = patientPosition[k];
				}
				nSliceMM++;
			}
			break;
		}
		case kInPlanePhaseEncodingDirection:
			d.phaseEncodingRC = toupper(buffer[lPos]); // first character is either 'R'ow or 'C'ol
			break;
		case kSAR:
			d.SAR = dcmStrFloat(lLength, &buffer[lPos]);
			maxSAR = fmax(maxSAR, d.SAR);
			break;
		case kStudyID:
			dcmStr(lLength, &buffer[lPos], d.studyID);
			break;
		case kSeriesNum:
			d.seriesNum = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kAcquNum:
			d.acquNum = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kImageNum:
			// Enhanced Philips also uses this in once per file SQ 0008,1111
			// Enhanced Philips also uses this once per slice in SQ 2005,140f
			has00200013 = true;
			if (d.imageNum < 1)
				d.imageNum = dcmStrInt(lLength, &buffer[lPos]); // Philips renames each image again in 2001,9000, which can lead to duplicates
			break;
		case kInStackPositionNumber:
			if ((d.manufacturer != kMANUFACTURER_CANON) && (d.manufacturer != kMANUFACTURER_HITACHI) && (d.manufacturer != kMANUFACTURER_UNKNOWN) && (d.manufacturer != kMANUFACTURER_PHILIPS) && (d.manufacturer != kMANUFACTURER_BRUKER))
				break;
			inStackPositionNumber = dcmInt(4, &buffer[lPos], d.isLittleEndian);
			// if (inStackPositionNumber == 1) numInStackPositionNumber1 ++;
			// printf("<%d>\n",inStackPositionNumber);
			if (inStackPositionNumber > maxInStackPositionNumber)
				maxInStackPositionNumber = inStackPositionNumber;
			break;
		case kTemporalPositionIndex:
			temporalPositionIndex = dcmInt(4, &buffer[lPos], d.isLittleEndian);
			if (temporalPositionIndex > maxTemporalPositionIndex)
				maxTemporalPositionIndex = temporalPositionIndex;
			if (temporalPositionIndex < minTemporalPositionIndex)
				minTemporalPositionIndex = temporalPositionIndex;
			break;
		case kDimensionIndexPointer:
			if (dimensionIndexPointerCounter >= MAX_NUMBER_OF_DIMENSIONS)
				break;
			dimensionIndexPointer[dimensionIndexPointerCounter++] = dcmAttributeTag(&buffer[lPos], d.isLittleEndian);
			break;
		case kFrameContentSequence:
			// if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //see https://github.com/rordenlab/dcm2niix/issues/241
			if (sqDepth == 0)
				sqDepth = 1; // should not happen, in case faulty anonymization
			sqDepth00189114 = sqDepth - 1;
			break;
		case kTriggerDelayTime: { // 0x0020+uint32_t(0x9153<< 16 ) //FD
			if (prefs->isIgnoreTriggerTimes)
				break; // issue499
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			// if (isVerbose < 2) break;
			double trigger = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			d.triggerDelayTime = trigger;
			if (isSameFloatGE(d.triggerDelayTime, 0.0))
				d.triggerDelayTime = 0.0; // double to single
			break;
		}
		case kDimensionIndexValues: { // kImageNum is not enough for 4D series from Philips 5.*.
			if (lLength < 4)
				break;
			nDimIndxVal = lLength / 4;
			if (nDimIndxVal > MAX_NUMBER_OF_DIMENSIONS) {
				printError("%d is too many dimensions.  Only up to %d are supported\n", nDimIndxVal, MAX_NUMBER_OF_DIMENSIONS);
				nDimIndxVal = MAX_NUMBER_OF_DIMENSIONS; // Truncate
			}
			dcmMultiLongs(4 * nDimIndxVal, &buffer[lPos], nDimIndxVal, d.dimensionIndexValues, d.isLittleEndian);
			break;
		}
		case kPhotometricInterpretation: {
			char interp[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], interp);
			if (strcmp(interp, "PALETTE_COLOR") == 0)
				isPaletteColor = true;
			if (strncmp(interp, "YBR_FULL", 8) == 0)
				d.isYBRfull = true;
			// printf("interp '%s' %d\n", interp, d.isYBRfull);
			break;
		}
		case kPlanarRGB:
			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;
		case kSamplesPerPixel:
			d.samplesPerPixel = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kDim2:
			d.xyzDim[2] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kDim1:
			d.xyzDim[1] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		// order is Row,Column e.g. YX
		case kXYSpacing: {
			float yx[3];
			dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, yx);
			d.xyzMM[1] = yx[2];
			d.xyzMM[2] = yx[1];
			break;
		}
		case kImageComments:
			dcmStr(lLength, &buffer[lPos], d.imageComments, true);
			break;
		// group 21: siemens
		case kScanOptionsSiemens:
			dcmStr(lLength, &buffer[lPos], scanOptionsSiemens, true);
			break;
		case kFrameNumberInSeries: // issue837
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			frameNumberInSeries = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kPATModeText: { // e.g. Siemens iPAT x2 listed as "p2"
			char accelStr[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], accelStr);
			char *ptr;
			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);
			dcmStrDigitsDotOnlyKey('s', accelStr); // e.g. if "p2s4" return "4", if "p2" return ""
			multiBandFactor = (int)strtol(accelStr, &ptr, 10);
			if (*ptr != '\0')
				multiBandFactor = 0.0;
			// printMessage("p%gs%d\n", d.accelFactPE, multiBandFactor);
			break;
		}
			//		case kCSASeriesHeaderInfoXA2:
			//			printf("do something profound\n");
		case kCSASeriesHeaderInfoXA:
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			if ((lPos + lLength) > fileLen)
				break;
			d.CSA.SeriesHeader_offset = (int)lPos;
			d.CSA.SeriesHeader_length = lLength;
			break;
		case kTimeAfterStart:
			// 0021,1104 see https://github.com/rordenlab/dcm2niix/issues/303
			//  0021,1104 6@159630 DS 4.635
			//  0021,1104 2@161164 DS 0
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			if (acquisitionTimesGE_UIH >= kMaxEPI3D)
				break;
			d.CSA.sliceTiming[acquisitionTimesGE_UIH] = dcmStrFloat(lLength, &buffer[lPos]);
			d.CSA.sliceTiming[acquisitionTimesGE_UIH] *= 1000.0; // convert sec to msec
			// printf("\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]
			if ((d.manufacturer != kMANUFACTURER_SIEMENS) || (d.echoNum > 1))
				break;
			char iceStr[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], iceStr);
			dcmStrDigitsOnly(iceStr);
			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;
			break;
		}
		case kICE_dimsLong: { // issue568: LO (0021,1106) [X_4_1_1_1_1_160_1_1_1_1_1_277]
			if ((d.manufacturer != kMANUFACTURER_SIEMENS) || (numberOfFramesICEdims > 0))
				break;
			char iceStr[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], iceStr);
			int idx = 0;
			char *pch = strtok(iceStr, "_");
			char *end;
			while (pch != NULL) {
				if (idx == 20)
					numberOfFramesICEdims = (int)strtol(pch, &end, 10);
				idx++;
				pch = strtok(NULL, "_");
			}
			break;
		}
		case kPhaseEncodingDirectionPositiveSiemens: {
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			int ph = dcmStrInt(lLength, &buffer[lPos]);
			if (ph == 0)
				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
			if (ph == 1)
				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
			break;
		}
		case kRealDwellTime: // https://github.com/rordenlab/dcm2niix/issues/240
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			d.dwellTime = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kBandwidthPerPixelPhaseEncode21:
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kCoilElements:
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			dcmStr(lLength, &buffer[lPos], d.coilElements);
			break;
		// group 21: GE
		case kLocationsInAcquisitionGE:
			locationsInAcquisitionGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kRTIA_timer:
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			// see dicm2nii slice timing from 0021,105E DS RTIA_timer
			d.rtia_timerGE = dcmStrFloat(lLength, &buffer[lPos]); // RefAcqTimes = t/10; end % in ms
			// printf("%s\t%g\n", fname, d.rtia_timerGE);
			break;
		case kProtocolDataBlockGE:
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.protocolBlockLengthGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			d.protocolBlockStartGE = (int)lPos + (int)lFileOffset + 4;
			// printError("ProtocolDataBlockGE %d @ %d\n", d.protocolBlockLengthGE, d.protocolBlockStartGE);
			break;
		case kNumberOfExcitations: // FL
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.numberOfExcitations = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kNumberOfArms: // FL
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.numberOfArms = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kNumberOfPointsPerArm: // FL
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.numberOfPointsPerArm = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kDoseCalibrationFactor:
			d.doseCalibrationFactor = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kPETImageIndex:
			PETImageIndex = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kReferencedSegmentNumber: { // US issue706
			int segn = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			if (segn < minReferencedSegmentNumber)
				minReferencedSegmentNumber = segn;
			if (segn > maxReferencedSegmentNumber)
				maxReferencedSegmentNumber = segn;
			break;
		}
		case kPEDirectionDisplayedUIH:
			if (d.manufacturer != kMANUFACTURER_UIH)
				break;
			dcmStr(lLength, &buffer[lPos], d.phaseEncodingDirectionDisplayedUIH);
			break;
		case kDiffusion_bValueUIH: {
			if (d.manufacturer != kMANUFACTURER_UIH)
				break;
			float v[4];
			dcmMultiFloatDouble(lLength, &buffer[lPos], 1, v, d.isLittleEndian);
			B0Philips = v[0];
			set_bVal(&volDiffusion, v[0]);
			break;
		}
		case kParallelInformationUIH: { // SENSE factor (0065,100d) SH [F:2S]
			if (d.manufacturer != kMANUFACTURER_UIH)
				break;
			char accelStr[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], accelStr);
			// char *ptr;
			dcmStrDigitsDotOnlyKey(':', accelStr); // e.g. if "p2s4" return "2", if "s4" return ""
			d.accelFactPE = atof(accelStr);
			break;
		}
		case kNumberOfImagesInGridUIH:
			if (d.manufacturer != kMANUFACTURER_UIH)
				break;
			d.numberOfImagesInGridUIH = dcmStrFloat(lLength, &buffer[lPos]);
			d.CSA.mosaicSlices = d.numberOfImagesInGridUIH;
			break;
		case kPhaseEncodingDirectionPositiveUIH: {
			if (d.manufacturer != kMANUFACTURER_UIH)
				break;
			int ph = dcmStrInt(lLength, &buffer[lPos]);
			if (ph == 1)
				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
			if (ph == 0)
				d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
			break;
		}
		case kDiffusionGradientDirectionUIH: {
			if (d.manufacturer != kMANUFACTURER_UIH)
				break;
			float v[4];
			isDWI_UIH = true; // issue836
			dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian);
			// dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v);
			// printf(">>>%g %g %g\n", v[0], v[1], v[2]);
			d.CSA.dtiV[1] = v[0];
			d.CSA.dtiV[2] = v[1];
			d.CSA.dtiV[3] = v[2];
			// vRLPhilips = v[0];
			// vAPPhilips = v[1];
			// vFHPhilips = v[2];
			break;
		}
		case kBitsAllocated:
			d.bitsAllocated = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kBitsStored:
			d.bitsStored = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kHighBit:
			highBit = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kIsSigned: // http://dicomiseasy.blogspot.com/2012/08/chapter-12-pixel-data.html
			d.isSigned = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kPixelPaddingValue:
			// According to the DICOM standard, this can be either unsigned (US) or signed (SS). Currently this
			// is used only in nii_saveNII3Dtilt() which only allows DT_INT16, so treat it as signed.
			d.pixelPaddingValue = (float)(short)dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kFloatPixelPaddingValue:
			d.pixelPaddingValue = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kTR:
			d.TR = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kTE:
			TE = dcmStrFloat(lLength, &buffer[lPos]);
			// 20240229 if (d.TE <= 0.0)
			d.TE = TE;
			break;
		case kNumberOfAverages:
			d.numberOfAverages = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kImagingFrequency:
			d.imagingFrequency = dcmStrDouble(lLength, &buffer[lPos]);
			break;
		case kTriggerTime: {
			if (prefs->isIgnoreTriggerTimes)
				break; // issue499
			// untested method to detect slice timing for GE PSD “epi” with multiphase option
			//  will not work for current PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics)
			if ((d.manufacturer != kMANUFACTURER_GE) && (d.manufacturer != kMANUFACTURER_PHILIPS))
				break;												  // issue384
			d.triggerDelayTime = dcmStrFloat(lLength, &buffer[lPos]); //???? issue 336
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.triggerDelayTime;
			// printf("%g\n", d.CSA.sliceTiming[acquisitionTimesGE_UIH]);
			acquisitionTimesGE_UIH++;
			break;
		}
		case kRadionuclideTotalDose:
			d.radionuclideTotalDose = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kEffectiveTE: {
			TE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			if (d.TE <= 0.0)
				d.TE = TE;
			break;
		}
		case kTI:
			d.TI = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kEchoNum:
			d.echoNum = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kMagneticFieldStrength:
			d.fieldStrength = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kZSpacing:
			d.zSpacing = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kPhaseEncodingSteps:
			d.phaseEncodingSteps = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kEchoTrainLength:
			d.echoTrainLength = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kPercentSampling:
			d.percentSampling = dcmStrFloat(lLength, &buffer[lPos]);
		case kPhaseFieldofView:
			d.phaseFieldofView = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kPixelBandwidth:
			d.pixelBandwidth = dcmStrFloat(lLength, &buffer[lPos]);
			// printWarning(" PixelBandwidth (0018,0095)====> %g @%d\n", d.pixelBandwidth, lPos);
			break;
		case kAcquisitionMatrix:
			if (lLength == 8) {
				uint16_t acquisitionMatrix[4];
				dcmMultiShorts(lLength, &buffer[lPos], 4, &acquisitionMatrix[0], d.isLittleEndian); // slice position
				// phaseEncodingLines stored in either image columns or rows
				if (acquisitionMatrix[3] > 0)
					d.phaseEncodingLines = acquisitionMatrix[3];
				if (acquisitionMatrix[2] > 0)
					d.phaseEncodingLines = acquisitionMatrix[2];
				if (acquisitionMatrix[1] > 0)
					frequencyRows = acquisitionMatrix[1];
				if (acquisitionMatrix[0] > 0)
					frequencyRows = acquisitionMatrix[0];
			}
			break;
		case kFlipAngle:
			d.flipAngle = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case 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;
		case kRadionuclidePositronFraction:
			d.radionuclidePositronFraction = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kGantryTilt:
			d.gantryTilt = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kXRayTimeMS: // IS
			d.exposureTimeMs = dcmStrInt(lLength, &buffer[lPos]);
			;
			break;
		case kXRayTubeCurrent: // IS
			d.xRayTubeCurrent = dcmStrInt(lLength, &buffer[lPos]);
			;
			break;
		case kXRayExposure:	 // CTs do not have echo times, we use this field to detect different exposures: https://github.com/neurolabusc/dcm2niix/pull/48
			if (d.TE == 0) { // for CT we will use exposure (0018,1152) whereas for MR we use echo time (0018,0081)
				d.isXRay = true;
				d.TE = dcmStrFloat(lLength, &buffer[lPos]);
			}
			break;
		case kConvolutionKernel: // CS
			dcmStr(lLength, &buffer[lPos], d.convolutionKernel);
			break;
		case kFrameDuration:
			d.frameDuration = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kReceiveCoilName:
			dcmStr(lLength, &buffer[lPos], d.coilName);
			if (strlen(d.coilName) < 1)
				break;
			d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName));
			break;
		case kSlope:
			d.intenScale = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		// case kSpectroscopyDataPointColumns :
		//	d.xyzDim[4] = dcmInt(4,&buffer[lPos],d.isLittleEndian);
		//	break;
		case kPhilipsSlope:
			if ((lLength == 4) && (d.manufacturer == kMANUFACTURER_PHILIPS))
				d.intenScalePhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kMRImageDynamicScanBeginTime: { // FL
			if (lLength != 4)
				break;
			MRImageDynamicScanBeginTime = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			if (MRImageDynamicScanBeginTime < minDynamicScanBeginTime)
				minDynamicScanBeginTime = MRImageDynamicScanBeginTime;
			if (MRImageDynamicScanBeginTime > maxDynamicScanBeginTime)
				maxDynamicScanBeginTime = MRImageDynamicScanBeginTime;
			break;
		}
		case kIntercept:
			d.intenIntercept = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kRadiopharmaceutical:
			dcmStr(lLength, &buffer[lPos], d.radiopharmaceutical);
			break;
		case kZThick:
			d.xyzMM[3] = dcmStrFloat(lLength, &buffer[lPos]);
			d.zThick = d.xyzMM[3];
			break;
		case kImageTypeText: {
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			dcmStr(lLength, &buffer[lPos], d.imageTypeText);
			int slen = (int)strlen(d.imageTypeText);
			if (slen > 1) {
				for (int i = 0; i < slen; i++)
					if (d.imageTypeText[i] == '\\')
						d.imageTypeText[i] = '_';
			}
			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: {
			if (d.manufacturer == kMANUFACTURER_SIEMENS) {
				char matStr[kDICOMStr];
				dcmStr(lLength, &buffer[lPos], matStr);
				char *pPosition = strchr(matStr, 'I');
				if (pPosition != NULL)
					isInterpolated = true;
			}
			break;
		}
		case kImageOrientationText: // issue522
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			dcmStr(lLength, &buffer[lPos], d.imageOrientationText);
			break;
		case kCoilSiemens: {
			if (d.manufacturer == kMANUFACTURER_SIEMENS) {
				// see if image from single coil "H12" or an array "HEA;HEP"
				// char coilStr[kDICOMStr];
				// int coilNum;
				dcmStr(lLength, &buffer[lPos], d.coilName);
				if (strlen(d.coilName) < 1)
					break;
				// printf("-->%s\n", coilStr);
				// d.coilName = coilStr;
				// if (coilStr[0] == 'C') break; //kludge as Nova 32-channel defaults to "C:A32" https://github.com/rordenlab/dcm2niix/issues/187
				// char *ptr;
				// dcmStrDigitsOnly(coilStr);
				// coilNum = (int)strtol(coilStr, &ptr, 10);
				d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName));
				// printf("%d:%s\n", d.coilNum, coilStr);
				// if (*ptr != '\0')
				//  d.coilNum = 0;
			}
			break;
		}
		case kImaPATModeText: { // e.g. Siemens iPAT x2 listed as "p2"
			char accelStr[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], accelStr);
			char *ptr;
			dcmStrDigitsOnlyKey('p', accelStr); // e.g. if "p2s4" return "2", if "s4" return ""
			d.accelFactPE = (float)strtof(accelStr, &ptr);
			if (*ptr != '\0')
				d.accelFactPE = 0.0;
			// between slice accel
			dcmStr(lLength, &buffer[lPos], accelStr);
			dcmStrDigitsOnlyKey('s', accelStr); // e.g. if "p2s4" return "4", if "p2" return ""
			multiBandFactor = (int)strtol(accelStr, &ptr, 10);
			if (*ptr != '\0')
				multiBandFactor = 0.0;
			break;
		}
		case kLocationsInAcquisition:
			d.locationsInAcquisition = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kUnitsPT: // CS
			dcmStr(lLength, &buffer[lPos], d.unitsPT);
			break;
		case kAttenuationCorrectionMethod: // LO
			dcmStr(lLength, &buffer[lPos], d.attenuationCorrectionMethod);
			break;
		case kDecayCorrection: // CS
			dcmStr(lLength, &buffer[lPos], d.decayCorrection);
			break;
		case kReconstructionMethod: // LO
			dcmStr(lLength, &buffer[lPos], d.reconstructionMethod);
			break;
		case kFrameReferenceTime:
			d.frameReferenceTime = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kDecayFactor:
			d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kScatterFraction:
			d.scatterFraction = dcmStrFloat(lLength, &buffer[lPos]);
			// printf("SF%g\n", d.scatterFraction); //for each slice?
			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;
			break;
		// case kMRSeriesAcquisitionNumber: // 0x2001+(0x107B << 16 ) //IS
		//	mRSeriesAcquisitionNumber = dcmStrInt(lLength, &buffer[lPos]);
		//	break;
		case kNumberOfDynamicScans:
			//~d.numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]);
			numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]);
			break;
		/*case 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');
			if (lLength > 1)
				d.is3DAcq = (buffer[lPos] == '3') && (toupper(buffer[lPos + 1]) == 'D');
			// dcmStr(lLength, &buffer[lPos], d.mrAcquisitionType);
			break;
		case kBodyPartExamined: {
			dcmStr(lLength, &buffer[lPos], d.bodyPartExamined);
			break;
		}
		case kScanningSequence: {
			dcmStr(lLength, &buffer[lPos], d.scanningSequence);
			// According to the DICOM standard 0018,9018 is REQUIRED for EPI raw data
			//  http://dicom.nema.org/MEDICAL/Dicom/2015c/output/chtml/part03/sect_C.8.13.4.html
			// In practice, this is not the case for all vendors
			// Fortunately, the combination of 0018,0020 and 0018,9018 appears to reliably detect EPI data
			// Siemens (pre-XA) omits 0018,9018, but reports [EP] for 0018,0020 (regardless of SE/GR)
			// Siemens (XA) reports 0018,9018 but omits 0018,0020
			// Canon/Toshiba omits 0018,9018, but reports [SE\EP];[GR\EP] for 0018,0020
			// GE omits 0018,9018, but reports [EP\GR];[EP\SE] for 0018,0020
			// Philips reports 0018,9018, but reports [SE];[GR] for 0018,0020
			if ((lLength > 1) && (strstr(d.scanningSequence, "IR") != NULL))
				d.isIR = true;
			if ((lLength > 1) && (strstr(d.scanningSequence, "EP") != NULL))
				d.isEPI = true;
			break; // warp
		}
		case kRelTablePosition: {
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break;
			dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &d.CSA.tablePos[0]); // slice position
			d.CSA.tablePos[3] = -d.CSA.tablePos[3];								  // reverse Z polarity, issue 726
			d.CSA.tablePos[0] = 1.0;											  // set
			break;
		}
		case kScanningSequenceSiemens:
			if (d.manufacturer == kMANUFACTURER_SIEMENS)
				dcmStr(lLength, &buffer[lPos], scanningSequenceSiemens);
			if (d.manufacturer == kMANUFACTURER_GE) // issue690, series-level 16=DFAXDTI
				seriesdiffusionDirectionTypeGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kSequenceVariant21:
			if (d.manufacturer != kMANUFACTURER_SIEMENS)
				break; // n.b. for GE 0021,105B' TaggingFlipAngle
					   // fall through...
		case kSequenceVariant: {
			dcmStr(lLength, &buffer[lPos], d.sequenceVariant);
			break;
		}
		case kScanOptions:
			dcmStr(lLength, &buffer[lPos], d.scanOptions, true);
			if ((lLength > 1) && (strstr(d.scanOptions, "PFF") != NULL))
				d.isPartialFourier = true; // e.g. GE does not populate (0018,9081)
			break;
		case kSequenceName: {
			dcmStr(lLength, &buffer[lPos], d.sequenceName);
			break;
		}
		case kTRArray: {
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			// support multiple TRs: issue 743
			const int kMax = 4;
			float v[kMax] = {0.0};
			int nFloat32 = (lLength / 4);
			if (nFloat32 < 2)
				break;
			if (nFloat32 > kMax)
				nFloat32 = kMax;
			dcmMultiFloatSingle(nFloat32 * 4, &buffer[lPos], nFloat32, v, d.isLittleEndian);
			d.numberOfTR = 0;
			// count number of TRs > 0, see issue 749
			for (int i = 0; i < nFloat32; i++)
				if (v[i] > 0.0)
					d.numberOfTR++;
		}
		case kMRfMRIStatusIndicationPhilips: { // fmri volume number
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			if (volumeNumber > 0)
				break; // issue567
			int i = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			if (i > 0) // only if positive value, see Magdeburg_2014 sample data from dcm_qa_philips Philips MR 51.0
				volumeNumber = i;
			break;
		}
		case kMRAcquisitionTypePhilips: // kMRAcquisitionType
			if (lLength > 1)
				d.is3DAcq = (buffer[lPos] == '3') && (toupper(buffer[lPos + 1]) == 'D');
			break;
		case kAngulationRL:
			d.angulation[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kAngulationAP:
			d.angulation[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kAngulationFH:
			d.angulation[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kMRStackOffcentreRL:
			d.stackOffcentre[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kMRStackOffcentreAP:
			d.stackOffcentre[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kMRStackOffcentreFH:
			d.stackOffcentre[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kSliceOrient: {
			char orientStr[kDICOMStr];
			orientStr[0] = 'X'; // avoid compiler warning: orientStr filled by dcmStr
			dcmStr(lLength, &buffer[lPos], orientStr);
			if (toupper(orientStr[0]) == 'S')
				d.sliceOrient = kSliceOrientSag; // sagittal
			else if (toupper(orientStr[0]) == 'C')
				d.sliceOrient = kSliceOrientCor; // coronal
			else
				d.sliceOrient = kSliceOrientTra; // transverse (axial)
			break;
		}
		case kElscintIcon:
			printWarning("Assuming icon SQ 07a3,10ce.\n");
			isIconImageSequence = true;
			if (sqDepthIcon < 0)
				sqDepthIcon = sqDepth;
			break;
		case kPMSCT_RLE1:
			// https://groups.google.com/forum/#!topic/comp.protocols.dicom/8HuP_aNy9Pc
			// https://discourse.slicer.org/t/fail-to-load-pet-ct-gemini/8158/3
			//  d.compressionScheme = kCompressPMSCT_RLE1; //force RLE
			if (d.compressionScheme != kCompressPMSCT_RLE1)
				break;
			d.imageStart = (int)lPos + (int)lFileOffset;
			d.imageBytes = lLength;
			break;
		case kPrivateCreator: {
			if (d.manufacturer != kMANUFACTURER_UNKNOWN)
				break;
			d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]);
			volDiffusion.manufacturer = d.manufacturer;
			break;
		}
		case kDiffusion_bValuePhilips:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			B0Philips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			set_bVal(&volDiffusion, B0Philips);
			break;
		case kPhaseNumber: // IS issue529
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			d.phaseNumber = dcmStrInt(lLength, &buffer[lPos]); // see dcm_qa_philips_asl
			break;
		/*case kCardiacSync: //CS [TRIGGERED],[NO]
			if (lLength < 2)
				break;
			if (toupper(buffer[lPos]) != 'N')
				isTriggerSynced = true;
			break;*/
		case kDiffusion_bValue: // 0018,9087
			if (d.manufacturer == kMANUFACTURER_UNKNOWN) {
				d.manufacturer = kMANUFACTURER_PHILIPS;
				printWarning("Found 0018,9087 but manufacturer (0008,0070) unknown: assuming Philips.\n");
			}
			// Note that this is ahead of kImagePositionPatient (0020,0032), so
			// isAtFirstPatientPosition is not necessarily set yet.
			// Philips uses this tag too, at least as of 5.1, but they also
			// use kDiffusionBFactor (see above), and we do not want to
			// double count. More importantly, with Philips this tag
			// (sometimes?) gets repeated in a nested sequence with the
			// value *unset*!
			// GE started using this tag in 27, and annoyingly, NOT including
			// the b value if it is 0 for the slice.
			// if((d.manufacturer != kMANUFACTURER_PHILIPS) || !is2005140FSQ){
			// d.CSA.numDti++;
			// if (d.CSA.numDti == 2) { //First time we know that this is a 4D DTI dataset
			// dti4D->S[0].V[0] = d.CSA.dtiV[0];
			// dti4D->S[0].V[1] = d.CSA.dtiV[1];
			// dti4D->S[0].V[2] = d.CSA.dtiV[2];
			// dti4D->S[0].V[3] = d.CSA.dtiV[3];
			//}
			B0Philips = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			// d.CSA.dtiV[0] = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			set_bVal(&volDiffusion, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian));
			// if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D))
			//  dti4D->S[d.CSA.numDti-1].V[0] = d.CSA.dtiV[0];
			//}
			break;
		case kDiffusionOrientation: // 0018, 9089
			// Note that this is ahead of kImagePositionPatient (0020,0032), so
			// isAtFirstPatientPosition is not necessarily set yet.
			// Philips uses this tag too, at least as of 5.1, but they also
			// use kDiffusionDirectionRL, etc., and we do not want to double
			// count. More importantly, with Philips this tag (sometimes?)
			// gets repeated in a nested sequence with the value *unset*!
			// if (((d.manufacturer == kMANUFACTURER_SIEMENS) ||
			//  ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) &&
			//  (isAtFirstPatientPosition || isnan(d.patientPosition[1])))
			// if((d.manufacturer == kMANUFACTURER_SIEMENS) || ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ))
			if (!isDWI_UIH) { // issue836
				// 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);
				// dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian);
				dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian);
				vRLPhilips = v[0];
				vAPPhilips = v[1];
				vFHPhilips = v[2];
				// printMessage("><>< 0018,9089:\t%g\t%g\t%g\n", v[0], v[1], v[2]);
				// https://github.com/rordenlab/dcm2niix/issues/256
				// d.CSA.dtiV[1] = v[0];
				// d.CSA.dtiV[2] = v[1];
				// d.CSA.dtiV[3] = v[2];
				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 kImagingFrequencyFD:
			d.imagingFrequency = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kParallelReductionFactorOutOfPlane:
			if (d.manufacturer == kMANUFACTURER_SIEMENS)
				break;
			d.accelFactOOP = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case 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;
		case kArterialSpinLabelingContrast: { // CS
			char st[kDICOMStr];
			// aslFlags
			dcmStr(lLength, &buffer[lPos], st);
			if (strstr(st, "PSEUDOCONTINUOUS") != NULL)
				d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PSEUDOCONTINUOUS);
			else if (strstr(st, "CONTINUOUS") != NULL)
				d.aslFlags = (d.aslFlags | kASL_FLAG_GE_CONTINUOUS);
			else if (strstr(st, "PULSED") != NULL)
				d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PULSED);
			break;
		}
		case kASLPulseTrainDuration: {
			if (vr[0] == 'S' && vr[1] == 'H') { // issue919
				d.postLabelDelay = dcmStrInt(lLength, &buffer[lPos]);
			} else {
				d.postLabelDelay = dcmInt(4, &buffer[lPos], d.isLittleEndian);
			}
			break;
		}
		case kDiffusionBValueXX: {
			if (!(d.manufacturer == kMANUFACTURER_BRUKER))
				break; // other manufacturers provide bvec directly, rather than bmatrix
			double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			set_bMatrix(&volDiffusion, bMat, 0);
			isHasBMatrix = true;
			break;
		}
		case kSliceNumberMrPhilips: {
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			sliceNumberMrPhilips = dcmStrInt(lLength, &buffer[lPos]);
			int sliceNumber = sliceNumberMrPhilips;
			// use public patientPosition if it exists - fall back to private patient position
			if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) {
				for (int k = 0; k < 4; k++)
					patientPositionStartPhilips[k] = patientPosition[k];
			} else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) {
				for (int k = 0; k < 4; k++)
					patientPositionStartPhilips[k] = patientPositionPrivate[k];
			}
			if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPosition[1]))) {
				for (int k = 0; k < 4; k++)
					patientPositionEndPhilips[k] = patientPosition[k];
			} else if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPositionPrivate[1]))) {
				for (int k = 0; k < 4; k++)
					patientPositionEndPhilips[k] = patientPositionPrivate[k];
			}
			break;
		}
		case kEPIFactorPhilips:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			echoTrainLengthPhil = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kPrepulseDelay: // FL
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			d.TI = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kPrepulseType: // CS [INV]
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			if (lLength < 3)
				break;
			if ((toupper(buffer[lPos]) != 'I') && (toupper(buffer[lPos + 1]) != 'N') && (toupper(buffer[lPos + 2]) != 'V'))
				d.isIR = true;
			break;
		/*case kRespirationSync: //CS [TRIGGERED],[NO]
			if (lLength < 2)
				break;
			if (toupper(buffer[lPos]) != 'N')
				isTriggerSynced = true;
			break;*/
		case kNumberOfSlicesMrPhilips:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			locationsInAcquisitionPhilips = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			// printMessage("====> locationsInAcquisitionPhilips\t%d\n", locationsInAcquisitionPhilips);
			break;
		case kPartialMatrixScannedPhilips:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			if (lLength < 2)
				break;
			if (toupper(buffer[lPos]) == 'Y')
				d.isPartialFourier = true;
			break;
		case kWaterFatShiftPhilips:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			d.waterFatShift = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kDiffusionDirectionRL:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			vRLPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			set_diffusion_directionPhilips(&volDiffusion, vRLPhilips, 0);
			break;
		case kDiffusionDirectionAP:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			vAPPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			set_diffusion_directionPhilips(&volDiffusion, vAPPhilips, 1);
			break;
		case kDiffusionDirectionFH:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			vFHPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			set_diffusion_directionPhilips(&volDiffusion, vFHPhilips, 2);
			break;
		case 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;
			// if ((vr[0] == 'S') && (vr[1] == 'Q')) break;
			// if (!is2005140FSQwarned)
			//	printWarning("expected VR of 2005,140F to be 'SQ' (prior DICOM->DICOM conversion error?)\n");
			is2005140FSQ = true;
			// is2005140FSQwarned = true;
			break;
		case kMRImageGradientOrientationNumber:
			if (d.manufacturer == kMANUFACTURER_PHILIPS) {
				//n.b. historically VR of 2005,1413 is IS, but with R11 is can be SL
				// this will cause havoc if Philips data is saved on a PACS with implicit vr
				if (vr[0] == 'S' && vr[1] == 'L') {
					gradientOrientationNumberPhilips = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
				} else {
					gradientOrientationNumberPhilips = dcmStrInt(lLength, &buffer[lPos]);
				}
			}
			break;
		case kMRImageLabelType: // CS ??? LBL CTL
			if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 2))
				break;
			// TODO529: issue529 for ASL LBL/CTL  "LABEL"
			// if (toupper(buffer[lPos]) == 'L') isLabel = true;
			if (toupper(buffer[lPos]) == 'L')
				d.aslFlags = kASL_FLAG_PHILIPS_LABEL;
			if (toupper(buffer[lPos]) == 'C')
				d.aslFlags = kASL_FLAG_PHILIPS_CONTROL;
			break;
		case kMRStackTablePosLong: // FL
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			d.CSA.tablePos[3] = -dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			d.CSA.tablePos[0] = 1.0;
			break;
		case kMRImageDiffBValueNumber:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			//n.b. historically VR of 2005,1412 is IS, but with R11 is can be SL
			// this will cause havoc if Philips data is saved on a PACS with implicit vr
			if (vr[0] == 'S' && vr[1] == 'L') {
				philMRImageDiffBValueNumber = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			} else {
				philMRImageDiffBValueNumber = dcmStrInt(lLength, &buffer[lPos]);
			}
			break;
		case kMRImageDiffVolumeNumber:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			philMRImageDiffVolumeNumber = dcmStrInt(lLength, &buffer[lPos]);
			break;
		case kOriginalAttributesSq:
			if (lLength > 8)
				break; // issue639: we will skip entire icon if there is an explicit length
			sqDepth04000561 = sqDepth;
			break;
		case kWaveformSq:
			d.imageStart = 1; // abort!!!
			printMessage("Skipping DICOM (audio not image) '%s'\n", fname);
			break;
		case kSpectroscopyData: // kSpectroscopyDataPointColumns
			printMessage("Skipping Spectroscopy DICOM '%s'\n", fname);
			d.xyzDim[1] = 0; // issue606
			d.imageStart = (int)lPos + (int)lFileOffset;
			break;
		case kCSAImageHeaderInfo:
			if ((lPos + lLength) > fileLen)
				break;
			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)
				break;
			d.CSA.SeriesHeader_offset = (int)lPos;
			d.CSA.SeriesHeader_length = lLength;
			break;
		case kRealWorldIntercept:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			d.RWVIntercept = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			if (isSameFloat(0.0, d.intenIntercept)) // give precedence to standard value
				d.intenIntercept = d.RWVIntercept;
			break;
		case kRealWorldSlope:
			if (d.manufacturer != kMANUFACTURER_PHILIPS)
				break;
			d.RWVScale = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
			if (d.RWVScale > 1.0E38)
				d.RWVScale = 0.0;
			else if (isSameFloat(1.0, d.intenScale)) // give precedence to standard value
				d.intenScale = d.RWVScale;
			break;
		case kShimGradientX:
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.shimGradientX = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kShimGradientY:
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.shimGradientY = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kShimGradientZ:
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.shimGradientZ = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kVasCollapseFlagGE: // SS issue 690 image-level 16=DiffusionDtiDicomValue or 14=DiffusionT2DicomValue (initial b0)
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			diffusionDirectionTypeGE = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kPrescanReuseString: // LO
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			dcmStr(lLength, &buffer[lPos], d.prescanReuseString);
			break;
		case kUserDefineDataGE: { // 0043,102A
			if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 128))
				break;
#define MY_DEBUG_GE // <- uncomment this to use following code to infer GE phase encoding direction
#ifdef MY_DEBUG_GE
			int isVerboseX = isVerbose; // for debugging only - in standard release we will enable user defined "isVerbose"
			// int isVerboseX = 2;
			if (isVerboseX > 1)
				printMessage(" UserDefineDataGE file offset/length %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;
			}
			// debug code to export binary data
			/*
				char str[kDICOMStr];
				snprintf(str, kDICOMStr, "%s_ge.bin",fname);
				FILE *pFile = fopen(str, "wb");
				fwrite(&buffer[lPos], 1, lLength, pFile);
				fclose (pFile);
			*/
			if ((size_t)(lPos + lLength) > MaxBufferSz) {
				// we could re-read the buffer in this case, however in practice GE headers are concise so we never see this issue
				printMessage(" GE header overflows buffer\n");
				break;
			}
			size_t hdr_offset = dcmInt(2, &buffer[lPos + 24], true);
			if (isVerboseX > 1)
				printMessage(" header offset: %zu\n", hdr_offset);
			if (lLength < (hdr_offset + 916)) { // minimum size is hdr_offset=0, read 0x0394
				printMessage(" GE header too small to be valid  (B)\n");
				break;
			}
			// size_t hdr = lPos+hdr_offset;
			float version = dcmFloat(4, &buffer[lPos + hdr_offset], true);
			if (isVerboseX > 1)
				printMessage(" version %g\n", version);
			if (version < 5.0 || version > 40.0) {
				// printMessage(" GE header file format incorrect %g\n", version);
				break;
			}
			// char const *hdr = &buffer[lPos + hdr_offset];
			char *hdr = (char *)&buffer[lPos + hdr_offset];
			int epi_chk_off = 0x003a;
			int pepolar_off = 0x0030;
			int kydir_off = 0x0394;
			if (version >= 25.002) {
				hdr += 0x004c;
				kydir_off -= 0x008c;
			}
			// int seqOrInter =dcmInt(2,(unsigned char*)(hdr + pepolar_off-638),true);
			// int seqOrInter2 =dcmInt(2,(unsigned char*)(hdr + kydir_off-638),true);
			// printf("%d %d<<<\n", seqOrInter,seqOrInter2);
			// check if EPI
			if (true) {
				// int check = *(short const *)(hdr + epi_chk_off) & 0x800;
				int check = dcmInt(2, (unsigned char *)hdr + epi_chk_off, true) & 0x800;
				if (check == 0) {
					if (isVerboseX > 1)
						printMessage("%s: Warning: Data is not EPI\n", fname);
					break;
				}
			}
			// Check for PE polarity
			//  int flag1 = *(short const *)(hdr + pepolar_off) & 0x0004;
			// Check for ky direction (view order)
			//  int flag2 = *(int const *)(hdr + kydir_off);
			int phasePolarityFlag = dcmInt(2, (unsigned char *)hdr + pepolar_off, true) & 0x0004;
			// Check for ky direction (view order)
			int sliceOrderFlag = dcmInt(2, (unsigned char *)hdr + kydir_off, true);
			if (isVerboseX > 1)
				printMessage(" GE phasePolarity/sliceOrder flags %d %d\n", phasePolarityFlag, sliceOrderFlag);
			if (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;
				if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED)
					d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
				if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) {
					// https://cfmriweb.ucsd.edu/Howto/3T/operatingtips.html
					if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED)
						d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
					else
						d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
				}
			}
// if (sliceOrderFlag == kGE_SLICE_ORDER_TOP_DOWN)
//	d.sliceOrderGE = kGE_SLICE_ORDER_TOP_DOWN;
// if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP)
//	d.sliceOrderGE = kGE_SLICE_ORDER_BOTTOM_UP;
#endif
			break;
		}
		case kEffectiveEchoSpacingGE:
			if (d.manufacturer == kMANUFACTURER_GE)
				d.effectiveEchoSpacingGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			break;
		case kImageTypeGE: { // 0/1/2/3 for magnitude/phase/real/imaginary
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			int dt = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
			if (dt == 0)
				d.isHasMagnitude = true;
			if (dt == 1)
				d.isHasPhase = true;
			if (dt == 2)
				d.isHasReal = true;
			if (dt == 3)
				d.isHasImaginary = true;
			break;
		}
		case kDiffusion_bValueGE:
			if (d.manufacturer == kMANUFACTURER_GE) {
				d.CSA.dtiV[0] = (float)set_bValGE(&volDiffusion, lLength, &buffer[lPos]);
				d.CSA.numDti = 1;
				d.isDiffusion = true;
			}
			break;
		case kEpiRTGroupDelayGE: // FL
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.groupDelay = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
			d.groupDelay *= 1000.0; // sec -> ms
			// If kEpiRTGroupDelayGE (0043,107C) exists, epiRT
			d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT
			break;
		case kAssetRFactorsGE: { // DS issue427GE
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			float PhaseSlice[3];
			dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, PhaseSlice);
			if (PhaseSlice[1] > 0.0)
				d.accelFactPE = 1.0f / PhaseSlice[1];
			if (PhaseSlice[2] > 0.0)
				d.accelFactOOP = 1.0f / PhaseSlice[2];
			break;
		}
		case kASLContrastTechniqueGE: { // CS
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			char st[kDICOMStr];
			// aslFlags
			dcmStr(lLength, &buffer[lPos], st);
			if (strstr(st, "PSEUDOCONTINUOUS") != NULL)
				d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PSEUDOCONTINUOUS);
			else if (strstr(st, "CONTINUOUS") != NULL)
				d.aslFlags = (d.aslFlags | kASL_FLAG_GE_CONTINUOUS);
			break;
		}
		case kASLLabelingTechniqueGE: { // LO issue427GE
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			char st[kDICOMStr];
			dcmStr(lLength, &buffer[lPos], st);
			if (strstr(st, "3D continuous") != NULL)
				d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DCASL);
			if (strstr(st, "3D pulsed continuous") != NULL)
				d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DPCASL);
			break;
		}
		case kDurationLabelPulseGE: { // IS
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			d.durationLabelPulseGE = dcmStrInt(lLength, &buffer[lPos]);
			break;
		}
		case kTableDeltaGE: { // DS issue726
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			tableDeltaGE = dcmStrFloat(lLength, &buffer[lPos]);
		}
		case kMRTablePositionInformation: { // LO issue726
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			// LO array of floats stored in LONG STRING!
			//  [960.5\400\17.9108\0\-9999\-9999]
			// we want 3rd value, e.g. 17.9:
			float v[6];
			dcmMultiFloat(lLength, (char *)&buffer[lPos], 5, v);
			d.CSA.tablePos[3] = v[3] - tableDeltaGE;
			d.CSA.tablePos[0] = 1.0;
			break;
		}
		case kMultiBandGE: { // LO issue427GE
			if (d.manufacturer != kMANUFACTURER_GE)
				break;
			// LO array: Value 1 = Multiband factor, Value 2 = Slice FOV Shift Factor, Value 3 = Calibration method
			int mb = dcmStrInt(lLength, &buffer[lPos]);
			if (mb > 1)
				d.CSA.multiBandFactor = mb;
			break;
		}
		case 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
				printWarning("GEIIS violates the DICOM standard. Inspect results and admonish your vendor.\n");
				isIconImageSequence = true;
				if (sqDepthIcon < 0)
					sqDepthIcon = sqDepth;
				// geiisBug = true; //compressed thumbnails do not follow transfer syntax! GE should not reuse pulbic tags for these proprietary images http://sonca.kasshin.net/gdcm/Doc/GE_ImageThumbnails
			}
			break;
		case kStudyComments: {
			// char commentStr[kDICOMStr];
			// dcmStr(lLength, &buffer[lPos], commentStr);
			// printf(">> %s\n", commentStr);
			break;
		}
		case kProcedureStepDescription:
			dcmStr(lLength, &buffer[lPos], d.procedureStepDescription);
			break;
		case kOrientationACR: // use in emergency if kOrientation is not present!
			if (!isOrient)
				dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient);
			break;
		case kOrientation: {
			if (isOrient) { // already read orient - read for this slice to see if it varies (localizer)
				float orient[7];
				dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, orient);
				if ((!isSameFloatGE(d.orient[1], orient[1]) || !isSameFloatGE(d.orient[2], orient[2]) || !isSameFloatGE(d.orient[3], orient[3]) ||
					 !isSameFloatGE(d.orient[4], orient[4]) || !isSameFloatGE(d.orient[5], orient[5]) || !isSameFloatGE(d.orient[6], orient[6]))) {
					if (!d.isLocalizer)
						printError("DICOM incompatible with NIfTI slice orientation varies (issue 894, localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n",
									 d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6],
									 orient[1], orient[2], orient[3], orient[4], orient[5], orient[6]);
					d.isLocalizer = true;
					isSliceOrientVaries = true;
				}
			}
			dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient);
			vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]);
			vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]);
			sliceV = crossProduct(readV, phaseV);
			// printf("sliceV %g %g %g\n", sliceV.v[0], sliceV.v[1], sliceV.v[2]);
			isOrient = true;
			break;
		}
		case kTemporalPosition: // fall through, both kSliceNumberMrPhilips (2001,100A) and kTemporalPosition are is
			volumeNumber = dcmStrInt(lLength, &buffer[lPos]);
			// temporalPositionIdentifier = volumeNumber;
			break;
		case kTemporalResolution:
			temporalResolutionMS = dcmStrFloat(lLength, &buffer[lPos]);
			break;
		case kImagesInAcquisition:
			imagesInAcquisition = dcmStrInt(lLength, &buffer[lPos]);
			break;
		// case kSliceLocation : //optional so useless, infer from image position patient (0020,0032) and image orientation (0020,0037)
		//	sliceLocation = dcmStrFloat(lLength, &buffer[lPos]);
		//	break;
		case kImageStart:
			// if ((!geiisBug) && (!isIconImageSequence)) //do not exit for proprietary thumbnails
			if (isIconImageSequence) {
				// 20200116 see example from Tashrif Bilah that saves GEIIS thumbnails uncompressed
				//  therefore, the next couple lines are not a perfect detection for GEIIS thumbnail icons
				// int imgBytes = (d.xyzDim[1] * d.xyzDim[2] * int(d.bitsAllocated / 8));
				// if (imgBytes == lLength)
				//	isIconImageSequence = false;
				if ((isIconImageSequence) && (sqDepth < 1))
					printWarning("Assuming 7FE0,0010 refers to an icon not the main image\n");
			}
			if ((d.compressionScheme == kCompressNone) && (!isIconImageSequence)) // do not exit for proprietary thumbnails
				d.imageStart = (int)lPos + (int)lFileOffset;
			// geiisBug = false;
			// http://www.dclunie.com/medical-image-faq/html/part6.html
			// unlike raw data, Encapsulated data is stored as Fragments contained in Items that are the Value field of Pixel Data
			if ((d.compressionScheme != kCompressNone) && (!isIconImageSequence)) {
				isEncapsulatedData = true;
				encapsulatedDataImageStart = (int)lPos + (int)lFileOffset;
				lLength = 0;
			}
			isIconImageSequence = false;
			break;
		case kImageStartFloat:
			d.isFloat = true;
			if (!isIconImageSequence) // do not exit for proprietary thumbnails
				d.imageStart = (int)lPos + (int)lFileOffset;
			isIconImageSequence = false;
			break;
		case kImageStartDouble:
			printWarning("Double-precision DICOM conversion untested: please provide samples to developer\n");
			d.isFloat = true;
			if (!isIconImageSequence) // do not exit for proprietary thumbnails
				d.imageStart = (int)lPos + (int)lFileOffset;
			isIconImageSequence = false;
			break;
		} // switch/case for groupElement
		if ((((groupElement >> 8) & 0xFF) == 0x60) && (groupElement % 2 == 0) && ((groupElement & 0xFF) < 0x1E)) { // Group 60xx: OverlayGroup http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.9.html
			// even group numbers 0x6000..0x601E
			int overlayN = ((groupElement & 0xFF) >> 1);
			// printf("%08x %d %d\n", groupElement, (groupElement & 0xFF), overlayN);
			int element = groupElement >> 16;
			switch (element) {
			case 0x0010: // US OverlayRows
				overlayRows = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
				break;
			case 0x0011: // US OverlayColumns
				overlayCols = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
				break;
			case 0x0050: { // SSx2! OverlayOrigin
				if (lLength != 4)
					break;
				int row = dcmInt(2, &buffer[lPos], d.isLittleEndian);
				int col = dcmInt(2, &buffer[lPos + 2], d.isLittleEndian);
				if ((row == 1) && (col == 1))
					break;
				printMessage("Unsupported overlay origin %d/%d\n", row, col);
				overlayOK = false;
				break;
			}
			case 0x0100: { // US OverlayBitsAllocated
				int bits = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
				if (bits == 1)
					break;
				// old style Burned-In
				printMessage("Illegal/Obsolete DICOM (%s): Overlay Bits Allocated must be 1, not %d\n", fname, bits);
				overlayOK = false;
				break;
			}
			case 0x0102: { // US OverlayBitPosition
				int pos = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
				if (pos == 0)
					break;
				// old style Burned-In
				printMessage("Illegal/Obsolete DICOM (%s): Overlay Bit Position shall be 0, not %d\n", fname, pos);
				overlayOK = false;
				break;
			}
			case 0x3000: {
				d.overlayStart[overlayN] = (int)lPos + (int)lFileOffset;
				d.isHasOverlay = true;
				break;
			}
			}
		} // Group 60xx even values 0x6000..0x601E https://www.medicalconnections.co.uk/kb/Number-Of-Overlays-In-Image/
#ifndef USING_R
		if (isVerbose > 1) {
			// dcm2niix i fast because it does not use a dictionary.
			//  this is a very incomplete DICOM header report, and not a substitute for tools like dcmdump
			//  the purpose is to see how dcm2niix has parsed the image for diagnostics
			//  this section will report very little for implicit data
			// if (d.isHasReal) printf("r");else printf("m");
			char str[kDICOMStr];
			char realCh = ' ';
			if (realGroupElement != groupElement)
				realCh = '~'; // indicate we are ignoring this tag (e.g. wrapped in 0400,0561)
			snprintf(str, kDICOMStr, "%*c%04x,%04x%c %u@%zu ", sqDepth + 1, ' ', realGroupElement & 65535, realGroupElement >> 16, realCh, lLength, lFileOffset + lPos);
			bool isStr = false;
			if ((d.isExplicitVR) && (realGroupElement != kItemTag)) {
				// 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, " ");
				// snprintf(str, kDICOMStr, "%s%c%c ", str2, vr[0], vr[1]);
				char str2[kDICOMStr] = "";
				if ((vr[0] == 'F') && (vr[1] == 'D'))
					snprintf(str2, kDICOMStr, "%g ", dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian));
				if ((vr[0] == 'F') && (vr[1] == 'L'))
					snprintf(str2, kDICOMStr, "%g ", dcmFloat(lLength, &buffer[lPos], d.isLittleEndian));
				if ((vr[0] == 'S') && (vr[1] == 'S'))
					snprintf(str2, kDICOMStr, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
				if ((vr[0] == 'S') && (vr[1] == 'L'))
					snprintf(str2, kDICOMStr, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
				if ((vr[0] == 'U') && (vr[1] == 'S'))
					snprintf(str2, kDICOMStr, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
				if ((vr[0] == 'U') && (vr[1] == 'L'))
					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'))
					isStr = true;
				if ((vr[0] == 'C') && (vr[1] == 'S'))
					isStr = true;
				if ((vr[0] == 'D') && (vr[1] == 'A'))
					isStr = true;
				if ((vr[0] == 'D') && (vr[1] == 'S'))
					isStr = true;
				if ((vr[0] == 'D') && (vr[1] == 'T'))
					isStr = true;
				if ((vr[0] == 'I') && (vr[1] == 'S'))
					isStr = true;
				if ((vr[0] == 'L') && (vr[1] == 'O'))
					isStr = true;
				if ((vr[0] == 'L') && (vr[1] == 'T'))
					isStr = true;
				if ((vr[0] == 'P') && (vr[1] == 'N'))
					isStr = true;
				if ((vr[0] == 'S') && (vr[1] == 'H'))
					isStr = true;
				if ((vr[0] == 'S') && (vr[1] == 'T'))
					isStr = true;
				if ((vr[0] == 'T') && (vr[1] == 'M'))
					isStr = true;
				if ((vr[0] == 'U') && (vr[1] == 'I'))
					isStr = true;
				if ((vr[0] == 'U') && (vr[1] == 'T'))
					isStr = true;
				strncat(str, str2, kDICOMStr - 4);
				str[kDICOMStr - 1] = '\0';
			} else
				isStr = (lLength > 12); // implicit encoding: not always true as binary vectors may exceed 12 bytes, but often true
			if (lLength > 128) {
				printMessage("%s<%d bytes>\n", str, lLength);
			} else if (isStr) { // if length is greater than 8 bytes (+4 hdr) the MIGHT be a string
				char tagStr[kDICOMStr];
				// tagStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr
				strcpy(tagStr, "");
				if (lLength > 0)
					dcmStr(lLength, &buffer[lPos], tagStr);
				if (strlen(tagStr) > 1) {
					for (size_t pos = 0; pos < strlen(tagStr); pos++)
						if ((tagStr[pos] == '<') || (tagStr[pos] == '>') || (tagStr[pos] == ':') || (tagStr[pos] == '"') || (tagStr[pos] == '\\') || (tagStr[pos] == '/') || (tagStr[pos] < 32) // issue398
																																																//|| (tagStr[pos] == '^') || (tagStr[pos] < 33)
							|| (tagStr[pos] == '*') || (tagStr[pos] == '|') || (tagStr[pos] == '?'))
							tagStr[pos] = '_';
				}
				printMessage("%s %s\n", str, tagStr);
			} else
				printMessage("%s\n", str);
			// if (d.isExplicitVR) printMessage(" VR=%c%c\n", vr[0], vr[1]);
		} // printMessage(" tag=%04x,%04x length=%u pos=%ld %c%c nest=%d\n", groupElement & 65535,groupElement>>16, lLength, lPos,vr[0], vr[1], nest);
#endif
		lPos = lPos + (lLength);
	} // while d.imageStart == 0
	free(buffer);
	if (d.bitsStored < 0)
		d.isValid = false;
	// printf("%d bval=%g bvec=%g %g %g<<<\n", d.CSA.numDti, d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]);
	// printMessage("><>< DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]);
	if (encapsulatedDataFragmentStart > 0) {
		if ((encapsulatedDataFragments > 1) && (encapsulatedDataFragments == numberOfFrames) && (encapsulatedDataFragments < kMaxDTI4D)) {
			printWarning("Compressed image stored as %d fragments: if conversion fails decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname);
			d.imageStart = encapsulatedDataFragmentStart;
		} else if (encapsulatedDataFragments > 1) {
			printError("Compressed image with %d frames stored as %d fragments: decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", numberOfFrames, encapsulatedDataFragments, fname);
		} else {
			d.imageStart = encapsulatedDataFragmentStart;
			// dti4D->fragmentOffset[0] = -1;
		}
	} else if ((isEncapsulatedData) && (d.imageStart < 128)) {
		// http://www.dclunie.com/medical-image-faq/html/part6.html
		// Uncompressed data (unencapsulated) is sent in DICOM as a series of raw bytes or words (little or big endian) in the Value field of the Pixel Data element (7FE0,0010). Encapsulated data on the other hand is sent not as raw bytes or words but as Fragments contained in Items that are the Value field of Pixel Data
		printWarning("DICOM violation (contact vendor): compressed image without image fragments, assuming image offset defined by 0x7FE0,x0010: %s\n", fname);
		d.imageStart = encapsulatedDataImageStart;
	} 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)
	if ((d.modality == kMODALITY_PT) && (PETImageIndex > 0)) {
		d.imageNum = PETImageIndex; // https://github.com/rordenlab/dcm2niix/issues/184
									// printWarning("PET scan using 0054,1330 for image number %d\n", PETImageIndex);
	}
	if (d.isHasOverlay) {
		if ((overlayCols > 0) && (d.xyzDim[1] != overlayCols))
			overlayOK = false;
		if ((overlayRows > 0) && (d.xyzDim[2] != overlayRows))
			overlayOK = false;
		if (!overlayOK)
			d.isHasOverlay = false;
	}
	// Recent Philips images include DateTime (0008,002A) but not separate date and time (0008,0022 and 0008,0032)
	if ((strlen(acquisitionDateTimeTxt) > (kYYYYMMDDlen + 5)) && (!isFloatDiff(d.acquisitionTime, 0.0f)) && (!isFloatDiff(d.acquisitionDate, 0.0f))) {
		// 20161117131643.80000 -> date 20161117 time 131643.80000
		// printMessage("acquisitionDateTime %s\n",acquisitionDateTimeTxt);
		char acquisitionDateTxt[kDICOMStr];
		memcpy(acquisitionDateTxt, acquisitionDateTimeTxt, kYYYYMMDDlen);
		acquisitionDateTxt[kYYYYMMDDlen] = '\0'; // IMPORTANT!
		d.acquisitionDate = atof(acquisitionDateTxt);
		char acquisitionTimeTxt[kDICOMStr];
		int timeLen = (int)strlen(acquisitionDateTimeTxt) - kYYYYMMDDlen;
		strncpy(acquisitionTimeTxt, &acquisitionDateTimeTxt[kYYYYMMDDlen], timeLen);
		acquisitionTimeTxt[timeLen] = '\0'; // IMPORTANT!
		d.acquisitionTime = atof(acquisitionTimeTxt);
	}
	d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime);
	// printMessage("slices in Acq %d %d %g %g\n",locationsInAcquisitionGE, d.locationsInAcquisition, d.xyzMM[3], d.zSpacing);
	if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.locationsInAcquisition == 0))
		d.locationsInAcquisition = locationsInAcquisitionPhilips;
	if ((d.manufacturer == kMANUFACTURER_GE) && (imagesInAcquisition > 0))
		d.locationsInAcquisition = imagesInAcquisition; // e.g. if 72 slices acquired but interpolated as 144
	if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition > 0) && (locationsInAcquisitionGE > 0) && (d.locationsInAcquisition != locationsInAcquisitionGE)) {
		if (isVerbose)
			printMessage("Check number of slices, discrepancy between tags (0020,1002; 0021,104F; 0054,0081) (%d vs %d) %s\n", locationsInAcquisitionGE, d.locationsInAcquisition, fname);
		/* SAH.start: Fix for ZIP2 */
		int zipFactor = (int)roundf(d.xyzMM[3] / d.zSpacing);
		if (zipFactor > 1) {
			d.interp3D = zipFactor;
			// printMessage("Issue 373: Check for ZIP2 Factor: %d SliceThickness+SliceGap: %f, SpacingBetweenSlices: %f \n", zipFactor, d.xyzMM[3], d.zSpacing);
			locationsInAcquisitionGE *= zipFactor; // Multiply number of slices by ZIP factor. Do this prior to checking for conflict below (?).
		}
		if (isGEfieldMap) { // issue501 : to do check zip factor
			// Volume 1) derived phase field map [Hz] and 2) magnitude volume.
			// issue 777 while a fieldmap is technically derived, do not exclude with -i y
			bool isDerived = (d.imageNum <= locationsInAcquisitionGE); // first volume
			d.isRealIsPhaseMapHz = isDerived;
			d.isHasReal = isDerived;
		}
		/* SAH.end */
		if (locationsInAcquisitionGE < d.locationsInAcquisition) {
			d.locationsInAcquisitionConflict = d.locationsInAcquisition;
			d.locationsInAcquisition = locationsInAcquisitionGE;
		} else
			d.locationsInAcquisitionConflict = locationsInAcquisitionGE;
	}
	if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition == 0))
		d.locationsInAcquisition = locationsInAcquisitionGE;
	if (d.zSpacing > 0.0)
		d.xyzMM[3] = d.zSpacing; // use zSpacing if provided: depending on vendor, kZThick may or may not include a slice gap
	// printMessage("patientPositions = %d XYZT = %d slicePerVol = %d numberOfDynamicScans %d\n",patientPositionNum,d.xyzDim[3], d.locationsInAcquisition, d.numberOfDynamicScans);
	if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (patientPositionNum > d.xyzDim[3])) {
		d.CSA.numDti = d.xyzDim[3];																																			// issue506
		printMessage("Please check slice thicknesses: Philips R3.2.2 bug can disrupt estimation (%d positions reported for %d slices)\n", patientPositionNum, d.xyzDim[3]); // Philips reported different positions for each slice!
	}
	if ((d.imageStart > 144) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1))
		d.isValid = true;
	// if ((d.imageStart > 144) && (d.xyzDim[1] >= 1) && (d.xyzDim[2] >= 1) && (d.xyzDim[4] > 1)) //Spectroscopy
	//	d.isValid = true;
	if ((d.xyzMM[1] > FLT_EPSILON) && (d.xyzMM[2] < FLT_EPSILON)) {
		printMessage("Please check voxel size\n");
		d.xyzMM[2] = d.xyzMM[1];
	}
	if ((d.xyzMM[2] > FLT_EPSILON) && (d.xyzMM[1] < FLT_EPSILON)) {
		printMessage("Please check voxel size\n");
		d.xyzMM[1] = d.xyzMM[2];
	}
	if (d.isMicroscopy && (d.xyzMM[3] < FLT_EPSILON)) {
		// for multiplanar display, make stack of all slices have same size as largest in plane field of view
		float fov1 = d.xyzMM[1] * d.xyzDim[1];
		float fov2 = d.xyzMM[2] * d.xyzDim[2];
		d.xyzMM[3] = fmaxf(fov1, fov2) / d.xyzDim[3];
	}
	if (d.xyzMM[3] < FLT_EPSILON) {
		if (d.xyzDim[3] > 1)
			printMessage("Unable to determine slice thickness: please check voxel size\n");
		d.xyzMM[3] = 1.0;
	}
	// printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3]);
	// printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\tStart\t%d\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3], d.imageStart);
	//  printMessage("ser %ld\n", d.seriesNum);
	// int kEchoMult = 100; //For Siemens/GE Series 1,2,3... save 2nd echo as 201, 3rd as 301, etc
	// if (d.seriesNum > 100)
	//  kEchoMult = 10; //For Philips data Saved as Series 101,201,301... save 2nd echo as 111, 3rd as 121, etc
	// if (coilNum > 0) //segment images with multiple coils
	//  d.seriesNum = d.seriesNum + (100*coilNum);
	// if (d.echoNum > 1) //segment images with multiple echoes
	//  d.seriesNum = d.seriesNum + (kEchoMult*d.echoNum);
	if (isPaletteColor) {
		d.isValid = false;
		d.isDerived = true; // to my knowledge, palette images always derived
		printWarning("Photometric Interpretation 'PALETTE COLOR' not supported\n");
	}
	if ((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);
		d.isValid = false;
	}
	if ((isMosaic) && (d.CSA.mosaicSlices < 1) && (numberOfImagesInMosaic < 1) && (!isInterpolated) && (d.phaseEncodingLines > 0) && (frequencyRows > 0) && ((d.xyzDim[1] % frequencyRows) == 0) && ((d.xyzDim[1] / frequencyRows) > 2) && ((d.xyzDim[2] % d.phaseEncodingLines) == 0) && ((d.xyzDim[2] / d.phaseEncodingLines) > 2)) {
		// n.b. in future check if frequency is in row or column direction (and same with phase)
		//  >2 avoids detecting interpolated as mosaic, in future perhaps check "isInterpolated"
		numberOfImagesInMosaic = (d.xyzDim[1] / frequencyRows) * (d.xyzDim[2] / d.phaseEncodingLines);
		printWarning("Guessing this is a mosaic up to %d slices (issue 337).\n", numberOfImagesInMosaic);
	}
	if ((numberOfImagesInMosaic > 1) && (d.CSA.mosaicSlices < 1))
		d.CSA.mosaicSlices = numberOfImagesInMosaic;
	if (d.isXA10A)
		d.manufacturer = kMANUFACTURER_SIEMENS; // XA10A mosaics omit Manufacturer 0008,0070!
	if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (isMosaic) && (d.CSA.mosaicSlices < 1) && (d.phaseEncodingSteps > 0) && ((d.xyzDim[1] % d.phaseEncodingSteps) == 0) && ((d.xyzDim[2] % d.phaseEncodingSteps) == 0)) {
		d.CSA.mosaicSlices = (d.xyzDim[1] / d.phaseEncodingSteps) * (d.xyzDim[2] / d.phaseEncodingSteps);
		printWarning("Mosaic inferred without CSA header (check number of slices and spatial orientation)\n");
	}
	if ((d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1))
		d.CSA.mosaicSlices = -1;								// mark as bogus DICOM
	if ((!d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) // See Erlangen Vida dataset - never reports "XA10" but mosaics have no attributes
		printWarning("0008,0008=MOSAIC but number of slices not specified: %s\n", fname);
	if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.dtiV[1] < -1.0) && (d.CSA.dtiV[2] < -1.0) && (d.CSA.dtiV[3] < -1.0))
		d.CSA.dtiV[0] = 0; // SiemensTrio-Syngo2004A reports B=0 images as having impossible b-vectors.
	if ((strlen(d.scanningSequence) < 1) && (strlen(scanningSequenceSiemens) > 1))
		strcpy(d.scanningSequence, scanningSequenceSiemens);
	if ((strlen(d.scanOptions) < 1) && (strlen(scanOptionsSiemens) > 1))
		strcpy(d.scanOptions, scanOptionsSiemens);
	if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1))
		strcpy(d.protocolName, d.seriesDescription);
	if ((strlen(d.protocolName) > 1) && (isMoCo))
		strcat(d.protocolName, "_MoCo"); // disambiguate MoCo https://github.com/neurolabusc/MRIcroGL/issues/31
	if ((strlen(d.protocolName) < 1) && (strlen(d.sequenceName) > 1) && (d.manufacturer != kMANUFACTURER_SIEMENS))
		strcpy(d.protocolName, d.sequenceName); // protocolName (0018,1030) optional, sequence name (0018,0024) is not a good substitute for Siemens as it can vary per volume: *ep_b0 *ep_b1000#1, *ep_b1000#2, etc https://www.nitrc.org/forum/forum.php?thread_id=8771&forum_id=4703
	if (numberOfFrames == 0)
		numberOfFrames = d.xyzDim[3];
	if ((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;
	} else if ((numberOfDynamicScans > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % numberOfDynamicScans) == 0)) {
		d.xyzDim[3] = d.xyzDim[3] / numberOfDynamicScans;
		d.xyzDim[4] = numberOfDynamicScans;
	}
	int nSegment = maxReferencedSegmentNumber - minReferencedSegmentNumber + 1;
	if ((nSegment > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % nSegment) == 0)) { // issue706
		d.xyzDim[3] = d.xyzDim[3] / nSegment;
		d.xyzDim[4] = nSegment;
	}
	if ((maxInStackPositionNumber > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % maxInStackPositionNumber) == 0)) {
		d.xyzDim[4] = d.xyzDim[3] / maxInStackPositionNumber;
		d.xyzDim[3] = maxInStackPositionNumber;
	}
	if ((!isnan(patientPositionStartPhilips[1])) && (!isnan(patientPositionEndPhilips[1]))) {
		for (int k = 0; k < 4; k++) {
			d.patientPosition[k] = patientPositionStartPhilips[k];
			d.patientPositionLast[k] = patientPositionEndPhilips[k];
		}
	}
	if ((numberOfFrames > 1) && (locationsInAcquisitionPhilips > 0) && ((numberOfFrames % locationsInAcquisitionPhilips) != 0)) { // issue515
		printWarning("Number of frames (%d) not divisible by locations in acquisition (2001,1018) %d (issue 515)\n", numberOfFrames, locationsInAcquisitionPhilips);
		d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips;
		d.xyzDim[3] = locationsInAcquisitionPhilips;
	}
	if ((B0Philips >= 0) && (d.CSA.numDti == 0)) {
		d.CSA.dtiV[0] = B0Philips;
		d.CSA.numDti = 1;
	} // issue409 Siemens XA saved as classic 2D not enhanced
	if (!isnan(patientPositionStartPhilips[1])) // for Philips data without
		for (int k = 0; k < 4; k++)
			d.patientPosition[k] = patientPositionStartPhilips[k];
	if (isVerbose) {
		printMessage("DICOM file: %s\n", fname);
		printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1], d.patientPosition[2], d.patientPosition[3]);
		if (!isnan(patientPositionEndPhilips[1]))
			printMessage(" patient position end (0020,0032)\t%g\t%g\t%g\n", patientPositionEndPhilips[1], patientPositionEndPhilips[2], patientPositionEndPhilips[3]);
		printMessage(" orient (0020,0037)\t%g\t%g\t%g\t%g\t%g\t%g\n", d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6]);
		printMessage(" acq %d img %d ser %ld dim %dx%dx%dx%d mm %gx%gx%g offset %d loc %d valid %d ph %d mag %d nDTI %d 3d %d bits %d littleEndian %d echo %d coilCRC %d TE %g TR %g\n", d.acquNum, d.imageNum, d.seriesNum, d.xyzDim[1], d.xyzDim[2], d.xyzDim[3], d.xyzDim[4], d.xyzMM[1], d.xyzMM[2], d.xyzMM[3], d.imageStart, d.locationsInAcquisition, d.isValid, d.isHasPhase, d.isHasMagnitude, d.CSA.numDti, d.is3DAcq, d.bitsAllocated, d.isLittleEndian, d.echoNum, d.coilCrc, d.TE, d.TR);
		if (d.CSA.dtiV[0] > 0)
			printMessage(" DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]);
	}
	if ((d.isValid) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1) && (d.imageStart < 132) && (!d.isRawDataStorage)) {
		// 20190524: Philips MR 55.1 creates non-image files that report kDim1/kDim2 - we can detect them since 0008,0016 reports "RawDataStorage"
		// see https://neurostars.org/t/dcm2niix-error-from-philips-dicom-qsm-data-can-this-be-skipped/4883
		printError("Conversion aborted due to corrupt file: %s %dx%d %d\n", fname, d.xyzDim[1], d.xyzDim[2], d.imageStart);
#ifdef USING_R
		Rf_error("Irrecoverable error during conversion");
#else
		exit(kEXIT_CORRUPT_FILE_FOUND);
#endif
	}
	if ((numberOfFrames > 1) && (numDimensionIndexValues == 0) && (numberOfFrames == nSliceMM)) { // issue 372
		fidx *objects = (fidx *)malloc(sizeof(struct fidx) * numberOfFrames);
		for (int i = 0; i < numberOfFrames; i++) {
			objects[i].value = sliceMM[i];
			objects[i].index = i;
		}
		qsort(objects, numberOfFrames, sizeof(struct fidx), fcmp);
		numDimensionIndexValues = numberOfFrames;
		for (int i = 0; i < numberOfFrames; i++) {
			// printf("%d > %g\n", objects[i].index, objects[i].value);
			dcmDim[objects[i].index].dimIdx[0] = i;
		}
		for (int i = 0; i < 4; i++) {
			d.patientPosition[i] = minPatientPosition[i];
			d.patientPositionLast[i] = maxPatientPosition[i];
		}
		// printf("%g -> %g\n", objects[0].value, objects[numberOfFrames-1].value);
		// printf("%g %g %g -> %g %g %g\n", d.patientPosition[1], d.patientPosition[2], d.patientPosition[3], 	d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3]);
		free(objects);
	} // issue 372
	if ((d.echoTrainLength == 0) && (echoTrainLengthPhil))
		d.echoTrainLength = echoTrainLengthPhil;																														   //+1 ?? to convert "EPI factor" to echo train length, see issue 377
	if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (minDynamicScanBeginTime < maxDynamicScanBeginTime)) { // issue369
		float TR = 1000.0 * ((maxDynamicScanBeginTime - minDynamicScanBeginTime) / (d.xyzDim[4] - 1));																	   //-1 : fence post problem
		if (fabs(TR - d.TR) > 0.001) {
			printWarning("Assuming TR = %gms, not 0018,0080 = %gms (see issue 369)\n", TR, d.TR);
			d.TR = TR;
		}
	}
	//	printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]);
	// printf("%g %g\n", minDynamicScanBeginTime, maxDynamicScanBeginTime);
	// if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (frameAcquisitionDuration > 0.0)) //issue369
	//	printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]);
	if (numDimensionIndexValues > 1)
		strcpy(d.imageType, imageType1st); // for multi-frame datasets, return name of book, not name of last chapter
	if (((d.isValid)) && (isIssue839) && (numDimensionIndexValues > 2)) {
		// rewrite
		for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) {
			for (int i = 0; i < numDimensionIndexValues; i++) {
				dcmDim[i].dimIdx[j] = 0;
			}
		}
		// we need a scalar value for sorting
		// first pass: find slice that is furthest from isocenter, n.b. arbitrary if tied
		float mxDx = 0.0;
		int mxIdx = 0; //
		for (int i = 0; i < numDimensionIndexValues; i++) {
			float dx = sqrt(pow(patientPosition1[i], 2) + pow(patientPosition2[i], 2) + pow(patientPosition3[i], 2));
			if (dx > mxDx) {
				mxDx = dx;
				mxIdx = i;
			}
			if (isVerbose > 1)
				printMessage("slice %d is %gmm from isocenter\n", i, dx);
		}
		if (isVerbose > 1)
			printMessage("slice %d is furthest (%gmm) from isocenter\n", mxIdx, mxDx);
		// second pass: measure each slices scalar distance from furthest slice
		// ensure their are no repeated slice positions
		fidx *objects = (fidx *)malloc(sizeof(struct fidx) * numDimensionIndexValues);
		bool isSamePosition = false;
		for (int i = 0; i < numDimensionIndexValues; i++) {
			float dx = sqrt(pow(patientPosition1[i] - patientPosition1[mxIdx], 2) + pow(patientPosition2[i] - patientPosition2[mxIdx], 2) + pow(patientPosition3[i] - patientPosition3[mxIdx], 2));
			if (isVerbose > 1)
				printMessage("slice %d is %gmm from furthest slice\n", i, dx);
			objects[i].value = dx;
			// sliceMM[i];
			objects[i].index = i;
			if ((i != mxIdx) && (isSameFloatGE(dx, 0.0)))
				isSamePosition = true;
		}
		if (isSamePosition) {
			printError("Multivolume file with invalid DimensionIndexValues (issue839)\n");
			d.isValid = false;
		}
		qsort(objects, numberOfFrames, sizeof(struct fidx), fcmp);
		numDimensionIndexValues = numberOfFrames;
		for (int i = 0; i < numDimensionIndexValues; i++) {
			dcmDim[objects[i].index].dimIdx[0] = i;
		}
		d.xyzDim[4] = 1;
		d.xyzDim[3] = numDimensionIndexValues;
	} // issue839
	if ((numDimensionIndexValues > 1) && (numDimensionIndexValues == numberOfFrames)) {
		// Philips enhanced datasets can have custom slice orders and pack images with different TE, Phase/Magnitude/Etc.
		int maxVariableItem = 0;
		int nVariableItems = 0;
		if (true) { //
			int mn[MAX_NUMBER_OF_DIMENSIONS];
			int mx[MAX_NUMBER_OF_DIMENSIONS];
			for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) {
				mx[j] = dcmDim[0].dimIdx[j];
				mn[j] = mx[j];
				for (int i = 0; i < numDimensionIndexValues; i++) {
					if (mx[j] < (int)dcmDim[i].dimIdx[j])
						mx[j] = dcmDim[i].dimIdx[j];
					if (mn[j] > (int)dcmDim[i].dimIdx[j])
						mn[j] = dcmDim[i].dimIdx[j];
				}
				if (mx[j] != mn[j]) {
					maxVariableItem = j;
					nVariableItems++;
				}
			}
			if (isVerbose > 1) {
				printMessage(" DimensionIndexValues (0020,9157), dimensions with variability:\n");
				for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++)
					if (mn[i] != mx[i])
						printMessage(" Dimension %d Range: %d..%d\n", i, mn[i], mx[i]);
			}
		} // verbose > 1
		// see http://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.8.24.3.3.html
		int stackPositionItem = 0;
		if (dimensionIndexPointerCounter > 0)
			for (size_t i = 0; i < dimensionIndexPointerCounter; i++)
				if ((dimensionIndexPointer[i] == kInStackPositionNumber) || (dimensionIndexPointer[i] == kImagePositionPatient)) // issue706
					stackPositionItem = i;
		if ((d.manufacturer == kMANUFACTURER_CANON) && (nVariableItems == 1) && (d.xyzDim[4] > 1)) {
			// WARNING: Canon CANON V6.0SP2001* (0018,9005) = "AX fMRI" strangely sets TemporalPositionIndex(0020,9128) as 1 for all volumes: (0020,9157) and (0020,9128) are INCORRECT!
			printMessage("Invalid enhanced DICOM created by Canon: Only single dimension in DimensionIndexValues (0020,9157) varies, for 4D file (e.g. BOTH space and time should vary)\n");
			printMessage("%d %d\n", stackPositionItem, maxVariableItem);
			int stackTimeItem = 0;
			if (stackPositionItem == 0) {
				maxVariableItem++;
				stackTimeItem++; // e.g. slot 0 = space, slot 1 = time
			}
			int vol = 0;
			for (int i = 0; i < numDimensionIndexValues; i++) {
				if (1 == dcmDim[i].dimIdx[stackPositionItem])
					vol++;
				dcmDim[i].dimIdx[stackTimeItem] = vol;
				// printf("vol %d slice %d\n", dcmDim[i].dimIdx[stackTimeItem], dcmDim[i].dimIdx[stackPositionItem]);
			}
		} // Kuldge for corrupted CANON 0020,9157
		/* //issue533: this code fragment will replicate dicm2nii (reverse order of volume hierarchy) https://github.com/xiangruili/dicm2nii/blob/f918366731162e6895be6e5ee431444642e0e7f9/dicm2nii.m#L2205
		if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (stackPositionItem == 1) && ( maxVariableItem > 2)) {
			//replicate dicm2nii: s2.DimensionIndexValues([3:end 1])
				uint32_t tmp[MAX_NUMBER_OF_DIMENSIONS];
				for (int i = 0; i < numDimensionIndexValues; i++) {
					for (int j = 2; j <= maxVariableItem; j++)
						tmp[j] = dcmDim[i].dimIdx[j];
					for (int j = 2; j <= maxVariableItem; j++)
						dcmDim[i].dimIdx[j] = tmp[maxVariableItem - j + 2];
				}
		}*/
		// Philips puts spatial position as lower item than temporal position, the reverse is true for Bruker and Canon
		if ((isKludgeIssue809) && (numDimensionIndexValues > 1)) {
			printWarning("Guessing temporal order for Philips enhanced DICOM ASL, DWI and fMRI (issue 533/809).\n");
			//artificially insert stack position in first slot
			// dimensionIndexPointer[0] = kInStackPositionNumber;
			stackPositionItem = 0;
		}
		if (stackPositionItem < maxVariableItem)
			qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdim);
		else
			qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdimRev);
		for (int i = 0; i < numberOfFrames; i++) {
			dti4D->sliceOrder[i] = dcmDim[i].diskPos;
			dti4D->intenScale[i] = dcmDim[i].intenScale;
			dti4D->intenIntercept[i] = dcmDim[i].intenIntercept;
			dti4D->intenScalePhilips[i] = dcmDim[i].intenScalePhilips;
			dti4D->RWVIntercept[i] = dcmDim[i].RWVIntercept;
			dti4D->RWVScale[i] = dcmDim[i].RWVScale;
			if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0])
				d.isScaleVariesEnh = true;
			if (dti4D->intenScale[i] != dti4D->intenScale[0])
				d.isScaleVariesEnh = true;
			if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0])
				d.isScaleVariesEnh = true;
		}
		// Revert Bruker change as new Bruker data uses DimensionIndexValues 0020,9157 for diffusion vectors
		//  https://github.com/rordenlab/dcm2niix/commit/8207877de984dab59e0374906c7ad212756f85a6#diff-120bb215d28dcbddeca8ea56dbb97e237501901ac2dfa1fbe4182282a8dd2b75
		// if (!(d.manufacturer == kMANUFACTURER_BRUKER && d.isDiffusion) && (d.xyzDim[4] > 1) && (d.xyzDim[4] < kMaxDTI4D)) { //record variations in TE
		if ((d.xyzDim[4] > 1) && (d.xyzDim[4] < kMaxDTI4D)) { // record variations in TE
			d.isScaleOrTEVaries = false;
			bool isTEvaries = false;
			bool isScaleVaries = false;
			// setting j = 1 in next few lines is a hack, just in case TE/scale/intercept listed AFTER dimensionIndexValues
			int j = 0;
			for (int i = 0; i < d.xyzDim[4]; i++) {
				int slice = j + (i * d.xyzDim[3]);
				// dti4D->gradDynVol[i] = 0; //only PAR/REC
				dti4D->TE[i] = dcmDim[slice].TE;
				dti4D->TR[i] = dcmDim[slice].TR;
				dti4D->isPhase[i] = dcmDim[slice].isPhase;
				dti4D->isReal[i] = dcmDim[slice].isReal;
				dti4D->isImaginary[i] = dcmDim[slice].isImaginary;
				dti4D->triggerDelayTime[i] = dcmDim[slice].triggerDelayTime;
				dti4D->S[i].V[0] = dcmDim[slice].V[0];
				dti4D->S[i].V[1] = dcmDim[slice].V[1];
				dti4D->S[i].V[2] = dcmDim[slice].V[2];
				dti4D->S[i].V[3] = dcmDim[slice].V[3];
				// printf("te=\t%g\tscl=\t%g\tintercept=\t%g\n",dti4D->TE[i], dti4D->intenScale[i],dti4D->intenIntercept[i]);
				if ((!isSameFloatGE(dti4D->TE[i], 0.0)) && (dti4D->TE[i] != d.TE))
					isTEvaries = true;
				if (dti4D->isPhase[i] != isPhase)
					d.isScaleOrTEVaries = true;
				if (dti4D->triggerDelayTime[i] != d.triggerDelayTime)
					d.isScaleOrTEVaries = true;
				if (dti4D->isReal[i] != isReal)
					d.isScaleOrTEVaries = true;
				if (dti4D->isImaginary[i] != isImaginary)
					d.isScaleOrTEVaries = true;
				/*Philips can vary intensity scalings for separate slices within a volume!
				dti4D->intenScale[i] = dcmDim[slice].intenScale;
				dti4D->intenIntercept[i] = dcmDim[slice].intenIntercept;
				dti4D->intenScalePhilips[i] = dcmDim[slice].intenScalePhilips;
				dti4D->RWVIntercept[i] = dcmDim[slice].RWVIntercept;
				dti4D->RWVScale[i] = dcmDim[slice].RWVScale;
				if (dti4D->intenScale[i] != d.intenScale) isScaleVaries = true;
				if (dti4D->intenIntercept[i] != d.intenIntercept) isScaleVaries = true;*/
			}
			if ((isScaleVaries) || (isTEvaries))
				d.isScaleOrTEVaries = true;
			if (isTEvaries)
				d.isMultiEcho = true;
			// if echoVaries,count number of echoes
			/*int echoNum = 1;
			for (int i = 1; i < d.xyzDim[4]; i++) {
				if (dti4D->TE[i-1] != dti4D->TE[i])
			}*/
			if ((isVerbose) && (d.isScaleOrTEVaries)) {
				printMessage("Parameters vary across 3D volumes packed in single DICOM file:\n");
				for (int i = 0; i < d.xyzDim[4]; i++) {
					int slice = (i * d.xyzDim[3]);
					printMessage(" %d TE=%g Slope=%g Inter=%g PhilipsScale=%g Phase=%d\n", i, dti4D->TE[i], dti4D->intenScale[slice], dti4D->intenIntercept[slice], dti4D->intenScalePhilips[slice], dti4D->isPhase[i]);
				}
			}
		}
		if ((d.xyzDim[3] == maxInStackPositionNumber) && (maxInStackPositionNumber > 1) && (d.zSpacing <= 0.0)) {
			float dx = sqrt(pow(d.patientPosition[1] - d.patientPositionLast[1], 2) +
							pow(d.patientPosition[2] - d.patientPositionLast[2], 2) +
							pow(d.patientPosition[3] - d.patientPositionLast[3], 2));
			dx = dx / (maxInStackPositionNumber - 1);
			if ((dx > 0.0) && (!isSameFloatGE(dx, d.xyzMM[3]))) // patientPosition has some rounding error
				d.xyzMM[3] = dx;
		} // d.zSpacing <= 0.0: Bruker does not populate 0018,0088 https://github.com/rordenlab/dcm2niix/issues/241
	} // if numDimensionIndexValues > 1 : enhanced DICOM
	if (d.CSA.numDti >= kMaxDTI4D) {
		printError("Unable to convert DTI [recompile with increased kMaxDTI4D] detected=%d, max = %d\n", d.CSA.numDti, kMaxDTI4D);
		d.CSA.numDti = 0;
	}
	if ((hasDwiDirectionality) && (d.CSA.numDti < 1))
		d.CSA.numDti = 1;
	if ((d.isValid) && (!has00200013)) { // Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers
		// TYPE 1 for MR image http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,0013)
		//  Only type 2 for some other DICOMs! Therefore, generate warning not error
		printWarning("Instance number (0020,0013) not found: %s\n", fname);
		d.imageNum = abs((int)d.instanceUidCrc) % 2147483647; // INT_MAX;
		if (d.imageNum == 0)
			d.imageNum = 1; // https://github.com/rordenlab/dcm2niix/issues/341
							// d.imageNum = 1; //not set
	}
	if ((numDimensionIndexValues < 1) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.seriesNum > 99999) && (philMRImageDiffBValueNumber > 0)) {
		// Ugly kludge to distinguish Philips classic DICOM dti
		//  images from a single sequence can have identical series number, instance number, gradient number
		//  the ONLY way to distinguish slices is using the private tag MRImageDiffBValueNumber
		//  confusingly, the same sequence can also generate MULTIPLE series numbers!
		//  for examples see https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging
		d.seriesNum += (philMRImageDiffBValueNumber * 1000);
	}
	// if (contentTime != 0.0) && (numDimensionIndexValues < (MAX_NUMBER_OF_DIMENSIONS - 1)){
	//	uint_32t timeCRC = mz_crc32X((unsigned char*) &contentTime, sizeof(double));
	// }
	if (numberOfFramesICEdims < 2) // issue751: icedims[20] frames for EPI only
		numberOfFramesICEdims = 0;
	// Issue 742: Detect *currently* enhanced Siemens XA volumes with fewer
	// than the expected number of slices, and mark them as derived, with
	// SeriesNumber + 1000. However, valid XA series are often unenhanced,
	// likely post-scanner, and can arrive as files with just 1 slice each
	// in d.xyzDim[3] but the total number of z locations for the series in
	// their ICEdims, so they appear to be partial volumes until the
	// dcmList is completed (nii_dicom_batch.cpp). Thus we use the
	// heuristic that d.xyzDim[3] == 1 is probably OK but between 1 and
	// numberOfFramesICEdims (exclusively) is an enhanced partial
	// volume. It would miss the case of a true enhanced partial volume
	// with just 1 slice, but that seems much less likely than unenhanced
	// DICOM with unmodified ICEDims tags.
	if ((numberOfFramesICEdims > 0) && (d.xyzDim[3] > 1) && (d.xyzDim[3] != numberOfFramesICEdims)) {
		printWarning("Series %ld includes partial volume (issue 742): %d slices acquired but ICE dims (0021,118e) specifies %d \n", d.seriesNum, d.xyzDim[3], numberOfFramesICEdims);
		d.seriesNum += 1000;
		d.isDerived = true;
	}
	// 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
		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));
		}
	}
	if (frameNumberInSeries >= 0) // issue837
		d.imageNum = frameNumberInSeries;
	// TODO533: alias Philips ASL PLD as frameDuration? isKludgeIssue809
	// if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408
	//	d.triggerDelayTime = 0.0; 		 //Philips ASL use "(0018,9037) CS [NONE]" but "(2001,1010) CS [TRIGGERED]", a situation not described in issue408
	if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime)) // issue395
		d.triggerDelayTime = 0.0;
	if (d.phaseNumber > 0) // Philips TurboQUASAR set this uniquely for each slice
		d.triggerDelayTime = 0.0;
	// printf("%d\t%g\t%g\t%g\n", d.imageNum, d.acquisitionTime, d.triggerDelayTime, MRImageDynamicScanBeginTime);
	if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strlen(seriesTimeTxt) > 1) && (isSiemensXA) && (d.xyzDim[3] == 1) && (d.xyzDim[4] < 2)) {
		// printWarning(">>Ignoring series number of XA data saved as classic DICOM (issue 394)\n");
		d.isStackableSeries = true;
		d.imageNum += (d.seriesNum * 1000);
		strcpy(d.seriesInstanceUID, seriesTimeTxt); // dest <- src
		d.seriesUidCrc = mz_crc32X((unsigned char *)&seriesTimeTxt, strlen(seriesTimeTxt));
	}
	if (((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON)) && (B0Philips > 0.0)) { // issue 388
		char txt[1024] = {""};
		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);
			strcpy(txt, (char *)&d.imageComments[len]);
			len = strlen(txt);
			for (int i = 0; i <= len; i++) {
				if ((txt[i] >= '0') && (txt[i] <= '9'))
					continue;
				if ((txt[i] == '.') || (txt[i] == '-'))
					continue;
				txt[i] = ' ';
			}
			float v[4];
			dcmMultiFloat(len, (char *)&txt[0], 3, &v[0]);
			d.CSA.dtiV[0] = B0Philips;
#ifdef swizzleCanon // see issue422 and dcm_qa_canon
			d.CSA.dtiV[1] = v[2];
			d.CSA.dtiV[2] = v[1];
			d.CSA.dtiV[3] = -v[3];
#else
			d.CSA.dtiV[1] = v[2];
			d.CSA.dtiV[2] = v[1];
			d.CSA.dtiV[3] = v[3];
			d.manufacturer = kMANUFACTURER_CANON;
#endif
			// d.CSA.dtiV[1] = v[1];
			// d.CSA.dtiV[2] = v[2];
			// d.CSA.dtiV[3] = v[3];
			d.CSA.numDti = 1;
		}
	}
	if ((isDICOMANON) && (isMATLAB)) {
		// issue 383
		strcpy(d.seriesInstanceUID, d.studyDate);
		// This check is unlikely to be important in practice, but it silences a warning from GCC with -Wrestrict
		if (strlen(d.studyDate) < kDICOMStr) {
			strncat(d.seriesInstanceUID, d.studyTime, kDICOMStr - strlen(d.studyDate));
		}
		d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID));
	}
	if (d.manufacturer == kMANUFACTURER_SIEMENS) {
		if (strstr(d.seriesDescription, "AAHScout") != NULL)
			d.isLocalizer = true;
		if (strstr(d.protocolName, "AAHScout") != NULL)
			d.isLocalizer = true;
		if (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 (((strstr(d.manufacturersModelName, "Premier") != NULL) && (isSameFloatGE(userData15GE, 0.72))) ||
				(((strstr(d.manufacturersModelName, "UHP") != NULL) || (strstr(d.manufacturersModelName, "7.0T") != NULL)) && (userData15GE >= 0.5) && (userData15GE <= 1.0))) // issue 796
				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");
	}
	// avoid false positives: non-EPI GE scans can report b-value = 0
	if ((d.isDiffusion) && (d.manufacturer == kMANUFACTURER_GE))
		d.isDiffusion = ((d.internalepiVersionGE == kGE_EPI_EPI2) || (d.epiVersionGE == kGE_EPI_EPI2));
	// detect pepolar https://github.com/nipy/heudiconv/issues/479
	if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 1))
		d.epiVersionGE = kGE_EPI_PEPOLAR_REV;
	if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 2))
		d.epiVersionGE = kGE_EPI_PEPOLAR_REV_FWD;
	if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 3))
		d.epiVersionGE = kGE_EPI_PEPOLAR_FWD_REV;
	if ((d.epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD) && (volumeNumber > 0) && ((volumeNumber % 2) == 1))
		d.epiVersionGE = kGE_EPI_PEPOLAR_REV_FWD_FLIP;
	if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV) && (volumeNumber > 0) && ((volumeNumber % 2) == 0))
		d.epiVersionGE = kGE_EPI_PEPOLAR_FWD_REV_FLIP;
#ifndef myDisableGEPEPolarFlip // e.g. to disable patch for issue 532 "make CFLAGS=-DmyDisableGEPEPolarFlip"
	if ((d.epiVersionGE == kGE_EPI_PEPOLAR_REV) || (d.epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV_FLIP) || (d.epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD_FLIP)) {
		if (d.epiVersionGE != kGE_EPI_PEPOLAR_REV)
			d.seriesNum += 1000;
		if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED)
			d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
		else if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED)
			d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
	}
#endif
	// UIH 3D T1 scans report echo train length, which is interpreted as 3D EPI
	if ((d.manufacturer == kMANUFACTURER_UIH) && (strstr(d.sequenceName, "gre_fsp") != NULL))
		d.echoTrainLength = 0;
	// printf(">>%s\n", d.sequenceName); d.isValid = false;
	//  Andrey Fedorov has requested keeping GE bvaecues, see issue 264
	// if ((d.CSA.numDti > 0) && (d.manufacturer == kMANUFACTURER_GE) && (d.numberOfDiffusionDirectionGE < 1))
	//	d.CSA.numDti = 0; //https://github.com/rordenlab/dcm2niix/issues/264
	if ((!d.isLocalizer) && (isInterpolated) && (d.imageNum <= 1))
		printWarning("interpolated protocol '%s' may be unsuitable for dwidenoise/mrdegibbs. %s\n", d.protocolName, fname);
	if (((numDimensionIndexValues + 3) < MAX_NUMBER_OF_DIMENSIONS) && (d.rawDataRunNumber > 0))
		d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 4] = d.rawDataRunNumber;
	if ((numDimensionIndexValues + 2) < MAX_NUMBER_OF_DIMENSIONS)
		d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 3] = d.instanceUidCrc;
	if ((numDimensionIndexValues + 1) < MAX_NUMBER_OF_DIMENSIONS)
		d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 2] = d.echoNum;
	if (numDimensionIndexValues < MAX_NUMBER_OF_DIMENSIONS) // https://github.com/rordenlab/dcm2niix/issues/221
		d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID));
	if ((d.isValid) && (d.seriesUidCrc == 0)) {
		if (d.seriesNum < 1)
			d.seriesUidCrc = 1; // no series information
		else
			d.seriesUidCrc = d.seriesNum; // file does not have Series UID, use series number instead
	}
	if (d.seriesNum < 1) // https://github.com/rordenlab/dcm2niix/issues/218
		d.seriesNum = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID));
	getFileName(d.imageBaseName, fname);
	if (multiBandFactor > d.CSA.multiBandFactor)
		d.CSA.multiBandFactor = multiBandFactor; // SMS reported in 0051,1011 but not CSA header
#ifndef myLoadWholeFileToReadHeader
	fclose(file);
#endif
	if ((temporalResolutionMS > 0.0) && (isSameFloatGE(d.TR, temporalResolutionMS))) {
		// do something profound
		// in practice 0020,0110 not used
		// https://github.com/bids-standard/bep001/blob/repetitiontime/Proposal_RepetitionTime.md
	}
	// issue690, 777
	//  detect non-DTI for GE
	if ((d.manufacturer == kMANUFACTURER_GE) && (diffusionDirectionTypeGE > 0) && (diffusionDirectionTypeGE != 16) && (diffusionDirectionTypeGE != 14))
		d.numberOfDiffusionDirectionGE = 0;
	if ((d.manufacturer == kMANUFACTURER_GE) && (seriesdiffusionDirectionTypeGE > 0) && (seriesdiffusionDirectionTypeGE != 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");
	// start: issue529 TODO JJJJ
	if ((!isSameFloat(d.CSA.dtiV[0], 0.0f)) && ((isSameFloat(d.CSA.dtiV[1], 0.0f)) && (isSameFloat(d.CSA.dtiV[2], 0.0f)) && (isSameFloat(d.CSA.dtiV[3], 0.0f))))
		gradientOrientationNumberPhilips = kMaxDTI4D + 1; // Philips includes derived Trace/ADC images into raw DTI, these should be removed...
	// if (sliceNumberMrPhilips == 1) printf("instance\t %d\ttime\t%g\tvolume\t%d\tgradient\t%d\tphase\t%d\tisLabel\t%d\n", d.imageNum,  MRImageDynamicScanBeginTime, volumeNumber, gradientOrientationNumberPhilips, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL);
	// if (sliceNumberMrPhilips == 1) printf("ACQtime\t%g\tinstance\t %d\ttime\t%g\tvolume\t%d\tphase\t%d\tisLabel\t%d\n", d.acquisitionTime, d.imageNum,  MRImageDynamicScanBeginTime, volumeNumber, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL);
	// if (sliceNumberMrPhilips == 1) printf("%d\t%d\t%g\t%d\n", philMRImageDiffBValueNumber, gradientOrientationNumberPhilips, d.CSA.dtiV[0], philMRImageDiffVolumeNumber); //issue546
	d.phaseNumber = (d.phaseNumber > philMRImageDiffBValueNumber) ? d.phaseNumber : philMRImageDiffBValueNumber; // we need both BValueNumber(2005,1412) and GradientOrientationNumber(2005,1413) to resolve volumes: issue546
	d.rawDataRunNumber = (d.rawDataRunNumber > volumeNumber) ? d.rawDataRunNumber : volumeNumber;
	d.rawDataRunNumber = (d.rawDataRunNumber > gradientOrientationNumberPhilips) ? d.rawDataRunNumber : gradientOrientationNumberPhilips;
	if ((d.rawDataRunNumber < 0) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (nDimIndxVal > 1) && (d.dimensionIndexValues[nDimIndxVal - 1] > 0))
		d.rawDataRunNumber = d.dimensionIndexValues[nDimIndxVal - 1]; // Philips enhanced scans converted to classic with dcuncat
	if ((philMRImageDiffVolumeNumber > 0) && (swVers < 11)) {							  // use 2005,1596 for Philips DWI >= R5.6; issue809
		d.rawDataRunNumber = philMRImageDiffVolumeNumber;
		d.phaseNumber = 0;
	}
	/*if (gradientOrientationNumberPhilips >= 0) { //issue809
		d.rawDataRunNumber  = gradientOrientationNumberPhilips;
	}*/

	// Phase encoding polarity
	// next conditionals updated: make GE match Siemens
	// it also rescues Siemens XA20 images without CSA header but with 0021,111C
	if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED)
		d.CSA.phaseEncodingDirectionPositive = 1;
	if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED)
		d.CSA.phaseEncodingDirectionPositive = 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 (isSliceOrientVaries)
		d.isValid = false; //issue894
	if (hasDwiDirectionality)
		d.isVectorFromBMatrix = false; // issue 265: Philips/Siemens have both directionality and bmatrix, Bruker only has bmatrix
	// printf("%s\t%s\t%s\t%s\t%s_%s\n",d.patientBirthDate, d.procedureStepDescription,d.patientName, fname, d.studyDate, d.studyTime);
	// d.isValid = false;
	// printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]);
	// printf("%d\t%g\t%g\t%g\t%g\n", d.imageNum, d.rtia_timerGE, d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]);
	// printf("%g\t\t%g\t%g\t%g\t%s\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3], fname);
	// printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz);
	if ((d.bitsStored == 1) && (highBit != 0)) {
		printWarning("1-bit binary with high bit = %d not supported (issue 572)\n", highBit);
		d.isValid = false;
	}
	if (d.offsetTableItems >= kMaxSlice2D) {
		printWarning("DICOM image fragments %d exceed capacity %d\n", d.offsetTableItems, kMaxSlice2D);
		d.isValid = false;
	}
#ifndef SHRT_MAX
#define SHRT_MAX 32767
#endif
	if ((d.xyzDim[1] > SHRT_MAX) || (d.xyzDim[2] > SHRT_MAX) || (d.xyzDim[3] > SHRT_MAX) || (d.xyzDim[4] > SHRT_MAX)) {
		printWarning("DICOM too large for NIfTI1 %d×%d×%d×%d\n", d.xyzDim[1], d.xyzDim[2], d.xyzDim[3], d.xyzDim[4]);
		d.isValid = false;
	}

	// printf("%g\t%g\t%s\n", d.intenIntercept, d.intenScale, fname);
	if ((d.isLocalizer) && (strstr(d.seriesDescription, "b1map"))) // issue751 b1map uses same base as scout
		d.isLocalizer = false;
	free(dcmDim);
	d.isXA = isSiemensXA;
	return d;
} // readDICOMx()

void setDefaultPrefs(struct TDCMprefs *prefs) {
	prefs->isVerbose = false;
	prefs->compressFlag = kCompressSupport;
	prefs->isIgnoreTriggerTimes = false;
}

struct TDICOMdata readDICOMv(char *fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) {
	struct TDCMprefs prefs;
	setDefaultPrefs(&prefs);
	prefs.isVerbose = isVerbose;
	prefs.compressFlag = compressFlag;
	TDICOMdata ret = readDICOMx(fname, &prefs, dti4D);
	return ret;
}

struct TDICOMdata readDICOM(char *fname) {
	struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); // unused
	TDICOMdata ret = readDICOMv(fname, false, kCompressSupport, dti4D);
	free(dti4D);
	return ret;
} // readDICOM()

#ifdef USING_DCM2NIIXFSWRAPPER
// remove spaces, '[', ']' in given buf
// ??? also remove <, >, &
void remove_specialchars(char *buf) {
	if (strchr(buf, ' ') == NULL)
		return;

	int buflen = strlen(buf) + 1;
	char *newbuf = new char[buflen];
	memset(newbuf, '\0', buflen);

	char *ptr_newbuf = &newbuf[0];
	for (int i = 0; i < buflen; i++) {
		// skip spaces, '[', ']'
		if (buf[i] == ' ' || buf[i] == '[' || buf[i] == ']')
			continue;

		*ptr_newbuf = buf[i];
		ptr_newbuf++;
	}

	memcpy(buf, newbuf, ptr_newbuf - newbuf);
	free(newbuf);
}
#endif
