1 //#define MY_DEBUG
2 #if defined(_WIN64) || defined(_WIN32)
3 #include <windows.h> //write to registry
4 #endif
5 #ifdef _MSC_VER
6 #include <direct.h>
7 #define getcwd _getcwd
8 #define chdir _chrdir
9 #include "io.h"
10 #include <math.h>
11 //#define snprintf _snprintf
12 //#define vsnprintf _vsnprintf
13 #define strcasecmp _stricmp
14 #define strncasecmp _strnicmp
15 #ifdef _WIN32
16 #pragma comment(lib, "advapi32")
17 #endif
18 #else
19 #include <unistd.h>
20 #endif
21 //#include <time.h> //clock()
22 #ifndef USING_R
23 #include "nifti1.h"
24 #endif
25 #include "jpg_0XC3.h"
26 #include "nifti1_io_core.h"
27 #include "nii_dicom.h"
28 #include "print.h"
29 #include <ctype.h> //toupper
30 #include <float.h>
31 #include <math.h>
32 #include <stddef.h>
33 #include <stdint.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/stat.h> // discriminate files from folders
38 #include <sys/types.h>
39
40 #ifdef USING_R
41 #undef isnan
42 #define isnan ISNAN
43 #endif
44
45 #ifndef myDisableClassicJPEG
46 #ifdef myTurboJPEG
47 #include <turbojpeg.h>
48 #else
49 #include "ujpeg.h"
50 #endif
51 #endif
52 #ifdef myEnableJasper
53 #include <jasper/jasper.h>
54 #endif
55 #ifndef myDisableOpenJPEG
56 #include "openjpeg.h"
57
58 #ifdef myEnableJasper
59 ERROR : YOU CAN NOT COMPILE WITH myEnableJasper AND NOT myDisableOpenJPEG OPTIONS SET SIMULTANEOUSLY
60 #endif
61
62 unsigned char * imagetoimg(opj_image_t *image) {
63 int numcmpts = image->numcomps;
64 int sgnd = image->comps[0].sgnd;
65 int width = image->comps[0].w;
66 int height = image->comps[0].h;
67 int bpp = (image->comps[0].prec + 7) >> 3; //e.g. 12 bits requires 2 bytes
68 int imgbytes = bpp * width * height * numcmpts;
69 bool isOK = true;
70 if (numcmpts > 1) {
71 for (int comp = 1; comp < numcmpts; comp++) { //check RGB data
72 if (image->comps[0].w != image->comps[comp].w)
73 isOK = false;
74 if (image->comps[0].h != image->comps[comp].h)
75 isOK = false;
76 if (image->comps[0].dx != image->comps[comp].dx)
77 isOK = false;
78 if (image->comps[0].dy != image->comps[comp].dy)
79 isOK = false;
80 if (image->comps[0].prec != image->comps[comp].prec)
81 isOK = false;
82 if (image->comps[0].sgnd != image->comps[comp].sgnd)
83 isOK = false;
84 }
85 if (numcmpts != 3)
86 isOK = false; //we only handle Gray and RedGreenBlue, not GrayAlpha or RedGreenBlueAlpha
87 if (image->comps[0].prec != 8)
88 isOK = false; //only 8-bit for RGB data
89 }
90 if ((image->comps[0].prec < 1) || (image->comps[0].prec > 16))
91 isOK = false; //currently we only handle 1 and 2 byte data
92 if (!isOK) {
93 printMessage("jpeg decode failure w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version());
94 return NULL;
95 }
96 #ifdef MY_DEBUG
97 printMessage("w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version());
98 #endif
99 //extract the data
100 if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) {
101 printError("Catastrophic decompression error\n");
102 return NULL;
103 }
104 unsigned char *img = (unsigned char *)malloc(imgbytes);
105 uint16_t *img16ui = (uint16_t *)img; //unsigned 16-bit
106 int16_t *img16i = (int16_t *)img; //signed 16-bit
107 if (sgnd)
108 bpp = -bpp;
109 if (bpp == -1) {
110 free(img);
111 printError("Signed 8-bit DICOM?\n");
112 return NULL;
113 }
114 //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB
115 int pix = 0; //output pixel
116 for (int cmptno = 0; cmptno < numcmpts; ++cmptno) {
117 int cpix = 0; //component pixel
118 int *v = image->comps[cmptno].data;
119 for (int y = 0; y < height; ++y) {
120 for (int x = 0; x < width; ++x) {
121 switch (bpp) {
122 case 1:
123 img[pix] = (unsigned char)v[cpix];
124 break;
125 case 2:
126 img16ui[pix] = (uint16_t)v[cpix];
127 break;
128 case -2:
129 img16i[pix] = (int16_t)v[cpix];
130 break;
131 }
132 pix++;
133 cpix++;
134 } //for x
135 } //for y
136 } //for each component
137 return img;
138 } // imagetoimg()
139
140 typedef struct bufinfo {
141 unsigned char *buf;
142 unsigned char *cur;
143 size_t len;
144 } BufInfo;
145
my_stream_free(void * p_user_data)146 static void my_stream_free(void *p_user_data) { //do nothing
147 //BufInfo d = (BufInfo) p_user_data;
148 //free(d.buf);
149 } // my_stream_free()
150
opj_read_from_buffer(void * p_buffer,OPJ_UINT32 p_nb_bytes,BufInfo * p_file)151 static OPJ_UINT32 opj_read_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) {
152 OPJ_UINT32 l_nb_read;
153 if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) {
154 l_nb_read = p_nb_bytes;
155 } else {
156 l_nb_read = (OPJ_UINT32)(p_file->buf + p_file->len - p_file->cur);
157 }
158 memcpy(p_buffer, p_file->cur, l_nb_read);
159 p_file->cur += l_nb_read;
160
161 return l_nb_read ? l_nb_read : ((OPJ_UINT32)-1);
162 } //opj_read_from_buffer()
163
opj_write_from_buffer(void * p_buffer,OPJ_UINT32 p_nb_bytes,BufInfo * p_file)164 static OPJ_UINT32 opj_write_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) {
165 memcpy(p_file->cur, p_buffer, p_nb_bytes);
166 p_file->cur += p_nb_bytes;
167 p_file->len += p_nb_bytes;
168 return p_nb_bytes;
169 } // opj_write_from_buffer()
170
opj_skip_from_buffer(OPJ_SIZE_T p_nb_bytes,BufInfo * p_file)171 static OPJ_SIZE_T opj_skip_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) {
172 if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) {
173 p_file->cur += p_nb_bytes;
174 return p_nb_bytes;
175 }
176 p_file->cur = p_file->buf + p_file->len;
177 return (OPJ_SIZE_T)-1;
178 } //opj_skip_from_buffer()
179
180 //fix for https://github.com/neurolabusc/dcm_qa/issues/5
opj_seek_from_buffer(OPJ_SIZE_T p_nb_bytes,BufInfo * p_file)181 static OPJ_BOOL opj_seek_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) {
182 //printf("opj_seek_from_buffer %d + %d -> %d + %d\n", p_file->cur , p_nb_bytes, p_file->buf, p_file->len);
183 if (p_nb_bytes < p_file->len) {
184 p_file->cur = p_file->buf + p_nb_bytes;
185 return OPJ_TRUE;
186 }
187 p_file->cur = p_file->buf + p_file->len;
188 return OPJ_FALSE;
189 } //opj_seek_from_buffer()
190
opj_stream_create_buffer_stream(BufInfo * p_file,OPJ_UINT32 p_size,OPJ_BOOL p_is_read_stream)191 opj_stream_t *opj_stream_create_buffer_stream(BufInfo *p_file, OPJ_UINT32 p_size, OPJ_BOOL p_is_read_stream) {
192 opj_stream_t *l_stream;
193 if (!p_file)
194 return NULL;
195 l_stream = opj_stream_create(p_size, p_is_read_stream);
196 if (!l_stream)
197 return NULL;
198 opj_stream_set_user_data(l_stream, p_file, my_stream_free);
199 opj_stream_set_user_data_length(l_stream, p_file->len);
200 opj_stream_set_read_function(l_stream, (opj_stream_read_fn)opj_read_from_buffer);
201 opj_stream_set_write_function(l_stream, (opj_stream_write_fn)opj_write_from_buffer);
202 opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn)opj_skip_from_buffer);
203 opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn)opj_seek_from_buffer);
204 return l_stream;
205 } //opj_stream_create_buffer_stream()
206
nii_loadImgCoreOpenJPEG(char * imgname,struct nifti_1_header hdr,struct TDICOMdata dcm,int compressFlag)207 unsigned char *nii_loadImgCoreOpenJPEG(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) {
208 //OpenJPEG library is not well documented and has changed between versions
209 //Since the JPEG is embedded in a DICOM we need to skip bytes at the start of the file
210 // In theory we might also want to strip data that exists AFTER the image, see gdcmJPEG2000Codec.c
211 unsigned char *ret = NULL;
212 opj_dparameters_t params;
213 opj_codec_t *codec;
214 opj_image_t *jpx;
215 opj_stream_t *stream;
216 FILE *reader = fopen(imgname, "rb");
217 fseek(reader, 0, SEEK_END);
218 long size = ftell(reader) - dcm.imageStart;
219 if (size <= 8)
220 return NULL;
221 fseek(reader, dcm.imageStart, SEEK_SET);
222 unsigned char *data = (unsigned char *)malloc(size);
223 size_t sz = fread(data, 1, size, reader);
224 fclose(reader);
225 if (sz < size)
226 return NULL;
227 OPJ_CODEC_FORMAT format = OPJ_CODEC_JP2;
228 //DICOM JPEG2k is SUPPOSED to start with codestream, but some vendors include a header
229 if (data[0] == 0xFF && data[1] == 0x4F && data[2] == 0xFF && data[3] == 0x51)
230 format = OPJ_CODEC_J2K;
231 opj_set_default_decoder_parameters(¶ms);
232 BufInfo dx;
233 dx.buf = data;
234 dx.cur = data;
235 dx.len = size;
236 stream = opj_stream_create_buffer_stream(&dx, (OPJ_UINT32)size, true);
237 if (stream == NULL)
238 return NULL;
239 codec = opj_create_decompress(format);
240 // setup the decoder decoding parameters using user parameters
241 if (!opj_setup_decoder(codec, ¶ms))
242 goto cleanup2;
243 // Read the main header of the codestream and if necessary the JP2 boxes
244 if (!opj_read_header(stream, codec, &jpx)) {
245 printError("OpenJPEG failed to read the header %s (offset %d)\n", imgname, dcm.imageStart);
246 //comment these next lines to abort: include these to create zero-padded slice
247 #ifdef MY_ZEROFILLBROKENJPGS
248 //fix broken slices https://github.com/scitran-apps/dcm2niix/issues/4
249 printError("Zero-filled slice created\n");
250 int imgbytes = (hdr.bitpix / 8) * hdr.dim[1] * hdr.dim[2];
251 ret = (unsigned char *)calloc(imgbytes, 1);
252 #endif
253 goto cleanup2;
254 }
255 // Get the decoded image
256 if (!(opj_decode(codec, stream, jpx) && opj_end_decompress(codec, stream))) {
257 printError("OpenJPEG j2k_to_image failed to decode %s\n", imgname);
258 goto cleanup1;
259 }
260 ret = imagetoimg(jpx);
261 cleanup1:
262 opj_image_destroy(jpx);
263 cleanup2:
264 free(dx.buf);
265 opj_stream_destroy(stream);
266 opj_destroy_codec(codec);
267 return ret;
268 }
269 #endif //myDisableOpenJPEG
270
271 #ifndef M_PI
272 #define M_PI 3.14159265358979323846
273 #endif
274
deFuzz(float v)275 float deFuzz(float v) {
276 if (fabs(v) < 0.00001)
277 return 0;
278 else
279 return v;
280 }
281
282 #ifdef MY_DEBUG
reportMat33(char * str,mat33 A)283 void reportMat33(char *str, mat33 A) {
284 printMessage("%s = [%g %g %g ; %g %g %g; %g %g %g ]\n", str,
285 deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]),
286 deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]),
287 deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2]));
288 }
289
reportMat44(char * str,mat44 A)290 void reportMat44(char *str, mat44 A) {
291 //example: reportMat44((char*)"out",*R);
292 printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n", str,
293 deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]), deFuzz(A.m[0][3]),
294 deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]), deFuzz(A.m[1][3]),
295 deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2]), deFuzz(A.m[2][3]));
296 }
297 #endif
298
verify_slice_dir(struct TDICOMdata d,struct TDICOMdata d2,struct nifti_1_header * h,mat44 * R,int isVerbose)299 int verify_slice_dir(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, mat44 *R, int isVerbose) {
300 //returns slice direction: 1=sag,2=coronal,3=axial, -= flipped
301 if (h->dim[3] < 2)
302 return 0; //don't care direction for single slice
303 int iSL = 1; //find Z-slice direction: row with highest magnitude of 3rd column
304 if ((fabs(R->m[1][2]) >= fabs(R->m[0][2])) && (fabs(R->m[1][2]) >= fabs(R->m[2][2])))
305 iSL = 2; //
306 if ((fabs(R->m[2][2]) >= fabs(R->m[0][2])) && (fabs(R->m[2][2]) >= fabs(R->m[1][2])))
307 iSL = 3; //axial acquisition
308 float pos = NAN;
309 if (!isnan(d2.patientPosition[iSL])) { //patient position fields exist
310 pos = d2.patientPosition[iSL];
311 if (isSameFloat(pos, d.patientPosition[iSL]))
312 pos = NAN;
313 #ifdef MY_DEBUG
314 if (!isnan(pos))
315 printMessage("position determined using lastFile %f\n", pos);
316 #endif
317 }
318 if (isnan(pos) && (!isnan(d.patientPositionLast[iSL]))) { //patient position fields exist
319 pos = d.patientPositionLast[iSL];
320 if (isSameFloat(pos, d.patientPosition[iSL]))
321 pos = NAN;
322 #ifdef MY_DEBUG
323 if (!isnan(pos))
324 printMessage("position determined using last (4d) %f\n", pos);
325 #endif
326 }
327 if (isnan(pos) && (!isnan(d.stackOffcentre[iSL])))
328 pos = d.stackOffcentre[iSL];
329 if (isnan(pos) && (!isnan(d.lastScanLoc)))
330 pos = d.lastScanLoc;
331 vec4 x;
332 x.v[0] = 0.0;
333 x.v[1] = 0.0;
334 x.v[2] = (float)(h->dim[3] - 1.0);
335 x.v[3] = 1.0;
336 vec4 pos1v = nifti_vect44mat44_mul(x, *R);
337 float pos1 = pos1v.v[iSL - 1]; //-1 as C indexed from 0
338 bool flip = false;
339 if (!isnan(pos)) // we have real SliceLocation for last slice or volume center
340 flip = (pos > R->m[iSL - 1][3]) != (pos1 > R->m[iSL - 1][3]); // same direction?, note C indices from 0
341 else { // we do some guess work and warn user
342 vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]);
343 vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]);
344 //printMessage("rd %g %g %g\n",readV.v[0],readV.v[1],readV.v[2]);
345 //printMessage("ph %g %g %g\n",phaseV.v[0],phaseV.v[1],phaseV.v[2]);
346 vec3 sliceV = crossProduct(readV, phaseV); //order important: this is our hail mary
347 flip = ((sliceV.v[0] + sliceV.v[1] + sliceV.v[2]) < 0);
348 //printMessage("verify slice dir %g %g %g\n",sliceV.v[0],sliceV.v[1],sliceV.v[2]);
349 if (isVerbose) { //1st pass only
350 if (!d.isDerived) { //do not warn user if image is derived
351 printWarning("Unable to determine slice direction: please check whether slices are flipped\n");
352 } else {
353 printWarning("Unable to determine slice direction: please check whether slices are flipped (derived image)\n");
354 }
355 }
356 }
357 if (flip) {
358 for (int i = 0; i < 4; i++)
359 R->m[i][2] = -R->m[i][2];
360 }
361 if (flip)
362 iSL = -iSL;
363 #ifdef MY_DEBUG
364 printMessage("verify slice dir %d %d %d\n", h->dim[1], h->dim[2], h->dim[3]);
365 //reportMat44((char*)"Rout",*R);
366 printMessage("flip = %d\n", flip);
367 printMessage("sliceDir = %d\n", iSL);
368 printMessage(" pos1 = %f\n", pos1);
369 #endif
370 return iSL;
371 } //verify_slice_dir()
372
noNaN(mat44 Q44,bool isVerbose,bool * isBogus)373 mat44 noNaN(mat44 Q44, bool isVerbose, bool *isBogus) //simplify any headers that have NaN values
374 {
375 mat44 ret = Q44;
376 bool isNaN44 = false;
377 for (int i = 0; i < 4; i++)
378 for (int j = 0; j < 4; j++)
379 if (isnan(ret.m[i][j]))
380 isNaN44 = true;
381 if (isNaN44) {
382 *isBogus = true;
383 if (isVerbose)
384 printWarning("Bogus spatial matrix (perhaps non-spatial image): inspect spatial orientation\n");
385 for (int i = 0; i < 4; i++)
386 for (int j = 0; j < 4; j++)
387 if (i == j)
388 ret.m[i][j] = 1;
389 else
390 ret.m[i][j] = 0;
391 ret.m[1][1] = -1;
392 } //if isNaN detected
393 return ret;
394 }
395
396 #define kSessionOK 0
397 #define kSessionBadMatrix 1
398
setQSForm(struct nifti_1_header * h,mat44 Q44i,bool isVerbose)399 void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose) {
400 bool isBogus = false;
401 mat44 Q44 = noNaN(Q44i, isVerbose, &isBogus);
402 if ((h->session_error == kSessionBadMatrix) || (isBogus)) {
403 h->session_error = kSessionBadMatrix;
404 h->sform_code = NIFTI_XFORM_UNKNOWN;
405 } else
406 h->sform_code = NIFTI_XFORM_SCANNER_ANAT;
407 h->srow_x[0] = Q44.m[0][0];
408 h->srow_x[1] = Q44.m[0][1];
409 h->srow_x[2] = Q44.m[0][2];
410 h->srow_x[3] = Q44.m[0][3];
411 h->srow_y[0] = Q44.m[1][0];
412 h->srow_y[1] = Q44.m[1][1];
413 h->srow_y[2] = Q44.m[1][2];
414 h->srow_y[3] = Q44.m[1][3];
415 h->srow_z[0] = Q44.m[2][0];
416 h->srow_z[1] = Q44.m[2][1];
417 h->srow_z[2] = Q44.m[2][2];
418 h->srow_z[3] = Q44.m[2][3];
419 float dumdx, dumdy, dumdz;
420 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]);
421 h->qform_code = h->sform_code;
422 } //setQSForm()
423
424 #ifdef my_unused
425
maxCol(mat33 R)426 ivec3 maxCol(mat33 R) {
427 //return index of maximum column in 3x3 matrix, e.g. [1 0 0; 0 1 0; 0 0 1] -> 1,2,3
428 ivec3 ixyz;
429 mat33 foo;
430 for (int i = 0; i < 3; i++)
431 for (int j = 0; j < 3; j++)
432 foo.m[i][j] = fabs(R.m[i][j]);
433 //ixyz.v[0] : row with largest value in column 1
434 ixyz.v[0] = 1;
435 if ((foo.m[1][0] > foo.m[0][0]) && (foo.m[1][0] >= foo.m[2][0]))
436 ixyz.v[0] = 2; //2nd column largest column
437 else if ((foo.m[2][0] > foo.m[0][0]) && (foo.m[2][0] > foo.m[1][0]))
438 ixyz.v[0] = 3; //3rd column largest column
439 //ixyz.v[1] : row with largest value in column 2, but not the same row as ixyz.v[1]
440 if (ixyz.v[0] == 1) {
441 ixyz.v[1] = 2;
442 if (foo.m[2][1] > foo.m[1][1])
443 ixyz.v[1] = 3;
444 } else if (ixyz.v[0] == 2) {
445 ixyz.v[1] = 1;
446 if (foo.m[2][1] > foo.m[0][1])
447 ixyz.v[1] = 3;
448 } else { //ixyz.v[0] == 3
449 ixyz.v[1] = 1;
450 if (foo.m[1][1] > foo.m[0][1])
451 ixyz.v[1] = 2;
452 }
453 //ixyz.v[2] : 3rd row, constrained by previous rows
454 ixyz.v[2] = 6 - ixyz.v[1] - ixyz.v[0]; //sum of 1+2+3
455 return ixyz;
456 }
457
sign(float x)458 int sign(float x) {
459 //returns -1,0,1 depending on if X is less than, equal to or greater than zero
460 if (x < 0)
461 return -1;
462 else if (x > 0)
463 return 1;
464 return 0;
465 }
466
467 // Subfunction: get dicom xform matrix and related info
468 // This is a direct port of Xiangrui Li's dicm2nii function
xform_mat(struct TDICOMdata d)469 mat44 xform_mat(struct TDICOMdata d) {
470 vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]);
471 vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]);
472 vec3 sliceV = crossProduct(readV, phaseV);
473 mat33 R;
474 LOAD_MAT33(R, readV.v[0], readV.v[1], readV.v[2],
475 phaseV.v[0], phaseV.v[1], phaseV.v[2],
476 sliceV.v[0], sliceV.v[1], sliceV.v[2]);
477 R = nifti_mat33_transpose(R);
478 //reportMat33((char*)"R",R);
479 ivec3 ixyz = maxCol(R);
480 //printMessage("%d %d %d\n", ixyz.v[0], ixyz.v[1], ixyz.v[2]);
481 int iSL = ixyz.v[2]; // 1/2/3 for Sag/Cor/Tra slice
482 float cosSL = R.m[iSL - 1][2];
483 //printMessage("cosSL\t%g\n", cosSL);
484 //vec3 pixdim = setVec3(d.xyzMM[1], d.xyzMM[2], d.xyzMM[3]);
485 //printMessage("%g %g %g\n", pixdim.v[0], pixdim.v[1], pixdim.v[2]);
486 mat33 pixdim;
487 LOAD_MAT33(pixdim, d.xyzMM[1], 0.0, 0.0,
488 0.0, d.xyzMM[2], 0.0,
489 0.0, 0.0, d.xyzMM[3]);
490 R = nifti_mat33_mul(R, pixdim);
491 //reportMat33((char*)"R",R);
492 mat44 R44;
493 LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.patientPosition[1],
494 R.m[1][0], R.m[1][1], R.m[1][2], d.patientPosition[2],
495 R.m[2][0], R.m[2][1], R.m[2][2], d.patientPosition[3]);
496 //reportMat44((char*)"R",R44);
497 //rest are former: R = verify_slice_dir(R, s, dim, iSL)
498 if ((d.xyzDim[3] < 2) && (d.CSA.mosaicSlices < 2))
499 return R44; //don't care direction for single slice
500 vec3 dim = setVec3(d.xyzDim[1], d.xyzDim[2], d.xyzDim[3]);
501 if (d.CSA.mosaicSlices > 1) { //Siemens mosaic: use dim(1) since no transpose to img
502 float nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices));
503 dim.v[0] = dim.v[0] / nRowCol;
504 dim.v[1] = dim.v[1] / nRowCol;
505 dim.v[2] = d.CSA.mosaicSlices;
506 vec4 dim4 = setVec4((nRowCol - 1) * dim.v[0] / 2.0f, (nRowCol - 1) * dim.v[1] / 2.0f, 0);
507 vec4 offset = nifti_vect44mat44_mul(dim4, R44);
508 //printMessage("%g %g %g\n", dim.v[0], dim.v[1], dim.v[2]);
509 //printMessage("%g %g %g\n", dim4.v[0], dim4.v[1], dim4.v[2]);
510 //printMessage("%g %g %g %g\n", offset.v[0], offset.v[1], offset.v[2], offset.v[3]);
511 //printMessage("nRowCol\t%g\n", nRowCol);
512 R44.m[0][3] = offset.v[0];
513 R44.m[1][3] = offset.v[1];
514 R44.m[2][3] = offset.v[2];
515 //R44.m[3][3] = offset.v[3];
516 if (sign(d.CSA.sliceNormV[iSL]) != sign(cosSL)) {
517 R44.m[0][2] = -R44.m[0][2];
518 R44.m[1][2] = -R44.m[1][2];
519 R44.m[2][2] = -R44.m[2][2];
520 R44.m[3][2] = -R44.m[3][2];
521 }
522 //reportMat44((char*)"iR44",R44);
523 return R44;
524 } else if (true) {
525 //SliceNormalVector TO DO
526 printMessage("Not completed");
527 #ifndef USING_R
528 exit(2);
529 #endif
530 return R44;
531 }
532 printMessage("Unable to determine spatial transform\n");
533 #ifndef USING_R
534 exit(1);
535 #else
536 return R44;
537 #endif
538 }
539
set_nii_header(struct TDICOMdata d)540 mat44 set_nii_header(struct TDICOMdata d) {
541 mat44 R = xform_mat(d);
542 //R(1:2,:) = -R(1:2,:); % dicom LPS to nifti RAS, xform matrix before reorient
543 for (int i = 0; i < 2; i++)
544 for (int j = 0; j < 4; j++)
545 R.m[i][j] = -R.m[i][j];
546 #ifdef MY_DEBUG
547 reportMat44((char *)"R44", R);
548 #endif
549 }
550 #endif
551
552 // This code predates Xiangrui Li's set_nii_header function
set_nii_header_x(struct TDICOMdata d,struct TDICOMdata d2,struct nifti_1_header * h,int * sliceDir,int isVerbose)553 mat44 set_nii_header_x(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int *sliceDir, int isVerbose) {
554 *sliceDir = 0;
555 mat44 Q44 = nifti_dicom2mat(d.orient, d.patientPosition, d.xyzMM);
556 //Q44 = doQuadruped(Q44);
557 if (d.isSegamiOasis == true) {
558 //Segami reconstructions appear to disregard DICOM spatial parameters: assume center of volume is isocenter and no table tilt
559 // 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
560 //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
561 // http://www.cs.ucl.ac.uk/fileadmin/cmic/Documents/DavidAtkinson/DICOM.pdf
562 // https://www.slicer.org/wiki/Coordinate_systems
563 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)
564 vec4 originVx = setVec4((h->dim[1] + 1.0f) / 2.0f, (h->dim[2] + 1.0f) / 2.0f, (h->dim[3] + 1.0f) / 2.0f);
565 vec4 originMm = nifti_vect44mat44_mul(originVx, Q44);
566 for (int i = 0; i < 3; i++)
567 Q44.m[i][3] = -originMm.v[i]; //set origin to center voxel
568 if (isVerbose) {
569 //printMessage("origin (vx) %g %g %g\n",originVx.v[0],originVx.v[1],originVx.v[2]);
570 //printMessage("origin (mm) %g %g %g\n",originMm.v[0],originMm.v[1],originMm.v[2]);
571 printWarning("Segami coordinates defy DICOM convention, please check orientation\n");
572 }
573 return Q44;
574 }
575 //next line only for Siemens mosaic: ignore for UIH grid
576 // https://github.com/xiangruili/dicm2nii/commit/47ad9e6d9bc8a999344cbd487d602d420fb1509f
577 if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.mosaicSlices > 1)) {
578 double nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices));
579 double lFactorX = (d.xyzDim[1] - (d.xyzDim[1] / nRowCol)) / 2.0;
580 double lFactorY = (d.xyzDim[2] - (d.xyzDim[2] / nRowCol)) / 2.0;
581 Q44.m[0][3] = (float)((Q44.m[0][0] * lFactorX) + (Q44.m[0][1] * lFactorY) + Q44.m[0][3]);
582 Q44.m[1][3] = (float)((Q44.m[1][0] * lFactorX) + (Q44.m[1][1] * lFactorY) + Q44.m[1][3]);
583 Q44.m[2][3] = (float)((Q44.m[2][0] * lFactorX) + (Q44.m[2][1] * lFactorY) + Q44.m[2][3]);
584 for (int c = 0; c < 2; c++)
585 for (int r = 0; r < 4; r++)
586 Q44.m[c][r] = -Q44.m[c][r];
587 mat33 Q;
588 LOAD_MAT33(Q, d.orient[1], d.orient[4], d.CSA.sliceNormV[1],
589 d.orient[2], d.orient[5], d.CSA.sliceNormV[2],
590 d.orient[3], d.orient[6], d.CSA.sliceNormV[3]);
591 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.
592 mat44 det;
593 *sliceDir = kSliceOrientMosaicNegativeDeterminant; //we need to handle DTI vectors accordingly
594 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);
595 //patient_to_tal.m[2][3] = 1-d.CSA.MosaicSlices;
596 Q44 = nifti_mat44_mul(Q44, det);
597 }
598 } else { //not a mosaic
599 *sliceDir = verify_slice_dir(d, d2, h, &Q44, isVerbose);
600 for (int c = 0; c < 4; c++) // LPS to nifti RAS, xform matrix before reorient
601 for (int r = 0; r < 2; r++) //swap rows 1 & 2
602 Q44.m[r][c] = -Q44.m[r][c];
603 }
604 #ifdef MY_DEBUG
605 reportMat44((char *)"Q44", Q44);
606 #endif
607 return Q44;
608 }
609
headerDcm2NiiSForm(struct TDICOMdata d,struct TDICOMdata d2,struct nifti_1_header * h,int isVerbose)610 int headerDcm2NiiSForm(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //fill header s and q form
611 //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c
612 //returns sliceDir: 0=unknown,1=sag,2=coro,3=axial,-=reversed slices
613 int sliceDir = 0;
614 if (h->dim[3] < 2) {
615 mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose);
616 setQSForm(h, Q44, isVerbose);
617 return sliceDir; //don't care direction for single slice
618 }
619 h->sform_code = NIFTI_XFORM_UNKNOWN;
620 h->qform_code = NIFTI_XFORM_UNKNOWN;
621 bool isOK = false;
622 for (int i = 1; i <= 6; i++)
623 if (d.orient[i] != 0.0)
624 isOK = true;
625 if (!isOK) {
626 //we will have to guess, assume axial acquisition saved in standard Siemens style?
627 d.orient[1] = 1.0f;
628 d.orient[2] = 0.0f;
629 d.orient[3] = 0.0f;
630 d.orient[1] = 0.0f;
631 d.orient[2] = 1.0f;
632 d.orient[3] = 0.0f;
633 if ((d.isDerived) || ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3) && (d.manufacturer == kMANUFACTURER_SIEMENS))) {
634 printMessage("Unable to determine spatial orientation: 0020,0037 missing (probably not a problem: derived image)\n");
635 } else {
636 printMessage("Unable to determine spatial orientation: 0020,0037 missing (Type 1 attribute: not a valid DICOM) Series %ld\n", d.seriesNum);
637 }
638 }
639 mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose);
640 setQSForm(h, Q44, isVerbose);
641 return sliceDir;
642 } //headerDcm2NiiSForm()
643
headerDcm2Nii2(struct TDICOMdata d,struct TDICOMdata d2,struct nifti_1_header * h,int isVerbose)644 int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //final pass after de-mosaic
645 char txt[1024] = {""};
646 if (h->slice_code == NIFTI_SLICE_UNKNOWN)
647 h->slice_code = d.CSA.sliceOrder;
648 if (h->slice_code == NIFTI_SLICE_UNKNOWN)
649 h->slice_code = d2.CSA.sliceOrder; //sometimes the first slice order is screwed up https://github.com/eauerbach/CMRR-MB/issues/29
650 if (d.modality == kMODALITY_MR)
651 sprintf(txt, "TE=%.2g;Time=%.3f", d.TE, d.acquisitionTime);
652 else
653 sprintf(txt, "Time=%.3f", d.acquisitionTime);
654 if (d.CSA.phaseEncodingDirectionPositive >= 0) {
655 char dtxt[1024] = {""};
656 sprintf(dtxt, ";phase=%d", d.CSA.phaseEncodingDirectionPositive);
657 strcat(txt, dtxt);
658 }
659 //from dicm2nii 20151117 InPlanePhaseEncodingDirection
660 if (d.phaseEncodingRC == 'R')
661 h->dim_info = (3 << 4) + (1 << 2) + 2;
662 if (d.phaseEncodingRC == 'C')
663 h->dim_info = (3 << 4) + (2 << 2) + 1;
664 if (d.CSA.multiBandFactor > 1) {
665 char dtxt[1024] = {""};
666 sprintf(dtxt, ";mb=%d", d.CSA.multiBandFactor);
667 strcat(txt, dtxt);
668 }
669 // GCC 8 warns about truncation using snprintf
670 // snprintf(h->descrip,80, "%s",txt);
671 memcpy(h->descrip, txt, 79);
672 h->descrip[79] = '\0';
673 if (strlen(d.imageComments) > 0)
674 snprintf(h->aux_file, 24, "%.23s", d.imageComments);
675 return headerDcm2NiiSForm(d, d2, h, isVerbose);
676 } //headerDcm2Nii2()
677
dcmStrLen(int len,int kMaxLen)678 int dcmStrLen(int len, int kMaxLen) {
679 if (len < kMaxLen)
680 return len + 1;
681 else
682 return kMaxLen;
683 } //dcmStrLen()
684
clear_dicom_data()685 struct TDICOMdata clear_dicom_data() {
686 struct TDICOMdata d;
687 //d.dti4D = NULL;
688 d.locationsInAcquisition = 0;
689 d.locationsInAcquisitionConflict = 0; //for GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081
690 d.modality = kMODALITY_UNKNOWN;
691 d.effectiveEchoSpacingGE = 0;
692 for (int i = 0; i < 4; i++) {
693 d.CSA.dtiV[i] = 0;
694 d.patientPosition[i] = NAN;
695 //d.patientPosition2nd[i] = NAN; //used to distinguish XYZT vs XYTZ for Philips 4D
696 d.patientPositionLast[i] = NAN; //used to compute slice direction for Philips 4D
697 d.stackOffcentre[i] = NAN;
698 d.angulation[i] = 0.0f;
699 d.xyzMM[i] = 1;
700 }
701 for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i)
702 d.dimensionIndexValues[i] = 0;
703 //d.CSA.sliceTiming[0] = -1.0f; //impossible value denotes not known
704 for (int z = 0; z < kMaxEPI3D; z++)
705 d.CSA.sliceTiming[z] = -1.0;
706 d.CSA.numDti = 0;
707 for (int i = 0; i < 5; i++)
708 d.xyzDim[i] = 1;
709 for (int i = 0; i < 7; i++)
710 d.orient[i] = 0.0f;
711 strcpy(d.patientName, "");
712 strcpy(d.patientID, "");
713 strcpy(d.accessionNumber, "");
714 strcpy(d.imageType, "");
715 strcpy(d.imageComments, "");
716 strcpy(d.imageBaseName, "");
717 strcpy(d.phaseEncodingDirectionDisplayedUIH, "");
718 strcpy(d.studyDate, "");
719 strcpy(d.studyTime, "");
720 strcpy(d.protocolName, "");
721 strcpy(d.seriesDescription, "");
722 strcpy(d.sequenceName, "");
723 strcpy(d.scanningSequence, "");
724 strcpy(d.sequenceVariant, "");
725 strcpy(d.manufacturersModelName, "");
726 strcpy(d.institutionalDepartmentName, "");
727 strcpy(d.procedureStepDescription, "");
728 strcpy(d.institutionName, "");
729 strcpy(d.referringPhysicianName, "");
730 strcpy(d.institutionAddress, "");
731 strcpy(d.deviceSerialNumber, "");
732 strcpy(d.softwareVersions, "");
733 strcpy(d.stationName, "");
734 strcpy(d.scanOptions, "");
735 //strcpy(d.mrAcquisitionType, "");
736 strcpy(d.seriesInstanceUID, "");
737 strcpy(d.instanceUID, "");
738 strcpy(d.studyID, "");
739 strcpy(d.studyInstanceUID, "");
740 strcpy(d.bodyPartExamined, "");
741 strcpy(d.coilName, "");
742 strcpy(d.coilElements, "");
743 strcpy(d.radiopharmaceutical, "");
744 strcpy(d.convolutionKernel, "");
745 strcpy(d.parallelAcquisitionTechnique, "");
746 strcpy(d.imageOrientationText, "");
747 strcpy(d.unitsPT, "");
748 strcpy(d.decayCorrection, "");
749 strcpy(d.attenuationCorrectionMethod, "");
750 strcpy(d.reconstructionMethod, "");
751 d.phaseEncodingLines = 0;
752 //~ d.patientPositionSequentialRepeats = 0;
753 //~ d.patientPositionRepeats = 0;
754 d.isHasPhase = false;
755 d.isHasReal = false;
756 d.isHasImaginary = false;
757 d.isHasMagnitude = false;
758 //d.maxGradDynVol = -1; //PAR/REC only
759 d.sliceOrient = kSliceOrientUnknown;
760 d.dateTime = (double)19770703150928.0;
761 d.acquisitionTime = 0.0f;
762 d.acquisitionDate = 0.0f;
763 d.manufacturer = kMANUFACTURER_UNKNOWN;
764 d.isPlanarRGB = false;
765 d.lastScanLoc = NAN;
766 d.TR = 0.0;
767 d.TE = 0.0;
768 d.TI = 0.0;
769 d.flipAngle = 0.0;
770 d.bandwidthPerPixelPhaseEncode = 0.0;
771 d.acquisitionDuration = 0.0;
772 d.imagingFrequency = 0.0;
773 d.numberOfAverages = 0.0;
774 d.fieldStrength = 0.0;
775 d.SAR = 0.0;
776 d.pixelBandwidth = 0.0;
777 d.zSpacing = 0.0;
778 d.zThick = 0.0;
779 //~ d.numberOfDynamicScans = 0;
780 d.echoNum = 1;
781 d.echoTrainLength = 0;
782 d.waterFatShift = 0.0;
783 d.groupDelay = 0.0;
784 d.decayFactor = 0.0;
785 d.percentSampling = 0.0;
786 d.phaseFieldofView = 0.0;
787 d.dwellTime = 0;
788 d.protocolBlockStartGE = 0;
789 d.protocolBlockLengthGE = 0;
790 d.phaseEncodingSteps = 0;
791 d.coilCrc = 0;
792 d.seriesUidCrc = 0;
793 d.instanceUidCrc = 0;
794 d.accelFactPE = 0.0;
795 d.accelFactOOP = 0.0;
796 //d.patientPositionNumPhilips = 0;
797 d.imageBytes = 0;
798 d.intenScale = 1;
799 d.intenScalePhilips = 0;
800 d.intenIntercept = 0;
801 d.gantryTilt = 0.0;
802 d.exposureTimeMs = 0.0;
803 d.xRayTubeCurrent = 0.0;
804 d.radionuclidePositronFraction = 0.0;
805 d.radionuclideHalfLife = 0.0;
806 d.doseCalibrationFactor = 0.0;
807 d.ecat_isotope_halflife = 0.0;
808 d.frameDuration = -1.0;
809 d.ecat_dosage = 0.0;
810 d.radionuclideTotalDose = 0.0;
811 d.seriesNum = 1;
812 d.acquNum = 0;
813 d.imageNum = 1;
814 d.imageStart = 0;
815 d.is3DAcq = false; //e.g. MP-RAGE, SPACE, TFE
816 d.is2DAcq = false; //
817 d.isDerived = false; //0008,0008 = DERIVED,CSAPARALLEL,POSDISP
818 d.isSegamiOasis = false; //these images do not store spatial coordinates
819 d.isBVecWorldCoordinates = false; //bvecs can be in image space (GE) or world coordinates (Siemens)
820 d.isGrayscaleSoftcopyPresentationState = false;
821 d.isRawDataStorage = false;
822 d.isPartialFourier = false;
823 d.isIR = false;
824 d.isEPI = false;
825 d.isDiffusion = false;
826 d.isVectorFromBMatrix = false;
827 d.isStackableSeries = false; //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252
828 d.isXA10A = false; //https://github.com/rordenlab/dcm2niix/issues/236
829 d.triggerDelayTime = 0.0;
830 d.RWVScale = 0.0;
831 d.RWVIntercept = 0.0;
832 d.isScaleOrTEVaries = false;
833 d.isScaleVariesEnh = false; //issue363
834 d.bitsAllocated = 16; //bits
835 d.bitsStored = 0;
836 d.samplesPerPixel = 1;
837 d.pixelPaddingValue = NAN;
838 d.isValid = false;
839 d.isXRay = false;
840 d.isMultiEcho = false;
841 d.isSigned = false; //default is unsigned!
842 d.isFloat = false; //default is for integers, not single or double precision
843 d.isResampled = false; //assume data not resliced to remove gantry tilt problems
844 d.isLocalizer = false;
845 d.isNonParallelSlices = false;
846 d.isCoilVaries = false;
847 d.compressionScheme = 0; //none
848 d.isExplicitVR = true;
849 d.isLittleEndian = true; //DICOM initially always little endian
850 d.converted2NII = 0;
851 d.numberOfDiffusionDirectionGE = -1;
852 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNKNOWN;
853 d.rtia_timerGE = -1.0;
854 d.rawDataRunNumber = -1;
855 d.maxEchoNumGE = -1;
856 d.epiVersionGE = -1;
857 d.internalepiVersionGE = -1;
858 d.durationLabelPulseGE = -1;
859 d.aslFlags = kASL_FLAG_NONE;
860 d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN;
861 d.mtState = -1;
862 d.numberOfExcitations = -1;
863 d.numberOfArms = -1;
864 d.numberOfPointsPerArm = -1;
865 d.phaseNumber = - 1; //Philips Multi-Phase ASL
866 d.spoiling = kSPOILING_UNKOWN;
867 d.interp3D = -1;
868 for (int i = 0; i < kMaxOverlay; i++)
869 d.overlayStart[i] = 0;
870 d.isHasOverlay = false;
871 d.isPrivateCreatorRemap = false;
872 d.isRealIsPhaseMapHz = false;
873 d.numberOfImagesInGridUIH = 0;
874 d.phaseEncodingRC = '?';
875 d.patientSex = '?';
876 d.patientWeight = 0.0;
877 strcpy(d.patientBirthDate, "");
878 strcpy(d.patientAge, "");
879 d.CSA.bandwidthPerPixelPhaseEncode = 0.0;
880 d.CSA.mosaicSlices = 0;
881 d.CSA.sliceNormV[1] = 0.0;
882 d.CSA.sliceNormV[2] = 0.0;
883 d.CSA.sliceNormV[3] = 1.0; //default Siemens Image Numbering is F>>H https://www.mccauslandcenter.sc.edu/crnl/tools/stc
884 d.CSA.sliceOrder = NIFTI_SLICE_UNKNOWN;
885 d.CSA.slice_start = 0;
886 d.CSA.slice_end = 0;
887 d.CSA.protocolSliceNumber1 = 0;
888 d.CSA.phaseEncodingDirectionPositive = -1; //unknown
889 d.CSA.isPhaseMap = false;
890 d.CSA.multiBandFactor = 1;
891 d.CSA.SeriesHeader_offset = 0;
892 d.CSA.SeriesHeader_length = 0;
893 return d;
894 } //clear_dicom_data()
895
isdigitdot(int c)896 int isdigitdot(int c) { //returns true if digit or '.'
897 if (c == '.')
898 return 1;
899 return isdigit(c);
900 }
901
dcmStrDigitsDotOnlyKey(char key,char * lStr)902 void dcmStrDigitsDotOnlyKey(char key, char *lStr) {
903 //e.g. string "F:2.50" returns 2.50 if key==":"
904 size_t len = strlen(lStr);
905 if (len < 1)
906 return;
907 bool isKey = false;
908 for (int i = 0; i < (int)len; i++) {
909 if (!isdigitdot(lStr[i])) {
910 isKey = (lStr[i] == key);
911 lStr[i] = ' ';
912 } else if (!isKey)
913 lStr[i] = ' ';
914 }
915 } //dcmStrDigitsOnlyKey()
916
dcmStrDigitsOnlyKey(char key,char * lStr)917 void dcmStrDigitsOnlyKey(char key, char *lStr) {
918 //e.g. string "p2s3" returns 2 if key=="p" and 3 if key=="s"
919 size_t len = strlen(lStr);
920 if (len < 1)
921 return;
922 bool isKey = false;
923 for (int i = 0; i < (int)len; i++) {
924 if (!isdigit(lStr[i])) {
925 isKey = (lStr[i] == key);
926 lStr[i] = ' ';
927 } else if (!isKey)
928 lStr[i] = ' ';
929 }
930 } //dcmStrDigitsOnlyKey()
931
dcmStrDigitsOnly(char * lStr)932 void dcmStrDigitsOnly(char *lStr) {
933 //e.g. change "H11" to " 11"
934 size_t len = strlen(lStr);
935 if (len < 1)
936 return;
937 for (int i = 0; i < (int)len; i++)
938 if (!isdigit(lStr[i]))
939 lStr[i] = ' ';
940 }
941
942 // Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/
mz_crc32X(unsigned char * ptr,size_t buf_len)943 uint32_t mz_crc32X(unsigned char *ptr, size_t buf_len) {
944 static const uint32_t s_crc32[16] = {0, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
945 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,0xedb88320, 0xf00f9344,
946 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c};
947 uint32_t crcu32 = 0;
948 if (!ptr)
949 return crcu32;
950 crcu32 = ~crcu32;
951 while (buf_len--) {
952 uint8_t b = *ptr++;
953 crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)];
954 crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)];
955 }
956 return ~crcu32;
957 }
958
dcmStr(int lLength,unsigned char lBuffer[],char * lOut,bool isStrLarge=false)959 void dcmStr(int lLength, unsigned char lBuffer[], char *lOut, bool isStrLarge = false) {
960 if (lLength < 1)
961 return;
962 char *cString = (char *)malloc(sizeof(char) * (lLength + 1));
963 cString[lLength] = 0;
964 memcpy(cString, (char *)&lBuffer[0], lLength);
965 //memcpy(cString, test, lLength);
966 //printMessage("X%dX\n", (unsigned char)d.patientName[1]);
967 #ifdef ISO8859
968 for (int i = 0; i < lLength; i++)
969 //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1
970 if (cString[i] < 1) {
971 unsigned char c = (unsigned char)cString[i];
972 if ((c >= 192) && (c <= 198))
973 cString[i] = 'A';
974 if (c == 199)
975 cString[i] = 'C';
976 if ((c >= 200) && (c <= 203))
977 cString[i] = 'E';
978 if ((c >= 204) && (c <= 207))
979 cString[i] = 'I';
980 if (c == 208)
981 cString[i] = 'D';
982 if (c == 209)
983 cString[i] = 'N';
984 if ((c >= 210) && (c <= 214))
985 cString[i] = 'O';
986 if (c == 215)
987 cString[i] = 'x';
988 if (c == 216)
989 cString[i] = 'O';
990 if ((c >= 217) && (c <= 220))
991 cString[i] = 'O';
992 if (c == 221)
993 cString[i] = 'Y';
994 if ((c >= 224) && (c <= 230))
995 cString[i] = 'a';
996 if (c == 231)
997 cString[i] = 'c';
998 if ((c >= 232) && (c <= 235))
999 cString[i] = 'e';
1000 if ((c >= 236) && (c <= 239))
1001 cString[i] = 'i';
1002 if (c == 240)
1003 cString[i] = 'o';
1004 if (c == 241)
1005 cString[i] = 'n';
1006 if ((c >= 242) && (c <= 246))
1007 cString[i] = 'o';
1008 if (c == 248)
1009 cString[i] = 'o';
1010 if ((c >= 249) && (c <= 252))
1011 cString[i] = 'u';
1012 if (c == 253)
1013 cString[i] = 'y';
1014 if (c == 255)
1015 cString[i] = 'y';
1016 }
1017 #endif
1018 //we no longer sanitize strings, see issue 425
1019 int len = lLength;
1020 if (cString[len - 1] == ' ')
1021 len--;
1022 //while ((len > 0) && (cString[len]=='_')) len--; //remove trailing '_'
1023 cString[len] = 0; //null-terminate, strlcpy does this anyway
1024 int maxLen = kDICOMStr;
1025 if (isStrLarge)
1026 maxLen = kDICOMStrLarge;
1027 len = dcmStrLen(len, maxLen);
1028 if (len == maxLen) { //we need space for null-termination
1029 if (cString[len - 2] == '_')
1030 len = len - 2;
1031 }
1032 memcpy(lOut, cString, len - 1);
1033 lOut[len - 1] = 0;
1034 free(cString);
1035 } //dcmStr()
1036
1037 #ifdef MY_OLD
1038 //this code works on Intel but not some older systems https://github.com/rordenlab/dcm2niix/issues/327
dcmFloat(int lByteLength,unsigned char lBuffer[],bool littleEndian)1039 float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 32-bit float
1040 //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian
1041 bool swap = (littleEndian != littleEndianPlatform());
1042 float retVal = 0;
1043 if (lByteLength < 4)
1044 return retVal;
1045 memcpy(&retVal, (char *)&lBuffer[0], 4);
1046 if (!swap)
1047 return retVal;
1048 float swapVal;
1049 char *inFloat = (char *)&retVal;
1050 char *outFloat = (char *)&swapVal;
1051 outFloat[0] = inFloat[3];
1052 outFloat[1] = inFloat[2];
1053 outFloat[2] = inFloat[1];
1054 outFloat[3] = inFloat[0];
1055 //printMessage("swapped val = %f\n",swapVal);
1056 return swapVal;
1057 } //dcmFloat()
1058
dcmFloatDouble(const size_t lByteLength,const unsigned char lBuffer[],const bool littleEndian)1059 double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { //read binary 64-bit float
1060 //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian
1061 bool swap = (littleEndian != littleEndianPlatform());
1062 double retVal = 0.0f;
1063 if (lByteLength < 8)
1064 return retVal;
1065 memcpy(&retVal, (char *)&lBuffer[0], 8);
1066 if (!swap)
1067 return retVal;
1068 char *floatToConvert = (char *)&lBuffer;
1069 char *returnFloat = (char *)&retVal;
1070 //swap the bytes into a temporary buffer
1071 returnFloat[0] = floatToConvert[7];
1072 returnFloat[1] = floatToConvert[6];
1073 returnFloat[2] = floatToConvert[5];
1074 returnFloat[3] = floatToConvert[4];
1075 returnFloat[4] = floatToConvert[3];
1076 returnFloat[5] = floatToConvert[2];
1077 returnFloat[6] = floatToConvert[1];
1078 returnFloat[7] = floatToConvert[0];
1079 //printMessage("swapped val = %f\n",retVal);
1080 return retVal;
1081 } //dcmFloatDouble()
1082 #else
1083
dcmFloat(int lByteLength,unsigned char lBuffer[],bool littleEndian)1084 float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 32-bit float
1085 //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian
1086 if (lByteLength < 4)
1087 return 0.0;
1088 bool swap = (littleEndian != littleEndianPlatform());
1089 union {
1090 uint32_t i;
1091 float f;
1092 uint8_t c[4];
1093 } i, o;
1094 memcpy(&i.i, (char *)&lBuffer[0], 4);
1095 //printf("%02x%02x%02x%02x\n",i.c[0], i.c[1], i.c[2], i.c[3]);
1096 if (!swap)
1097 return i.f;
1098 o.c[0] = i.c[3];
1099 o.c[1] = i.c[2];
1100 o.c[2] = i.c[1];
1101 o.c[3] = i.c[0];
1102 //printf("swp %02x%02x%02x%02x\n",o.c[0], o.c[1], o.c[2], o.c[3]);
1103 return o.f;
1104 } //dcmFloat()
1105
dcmFloatDouble(const size_t lByteLength,const unsigned char lBuffer[],const bool littleEndian)1106 double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { //read binary 64-bit float
1107 //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian
1108 if (lByteLength < 8)
1109 return 0.0;
1110 bool swap = (littleEndian != littleEndianPlatform());
1111 union {
1112 uint32_t i;
1113 double d;
1114 uint8_t c[8];
1115 } i, o;
1116 memcpy(&i.i, (char *)&lBuffer[0], 8);
1117 if (!swap)
1118 return i.d;
1119 o.c[0] = i.c[7];
1120 o.c[1] = i.c[6];
1121 o.c[2] = i.c[5];
1122 o.c[3] = i.c[4];
1123 o.c[4] = i.c[3];
1124 o.c[5] = i.c[2];
1125 o.c[6] = i.c[1];
1126 o.c[7] = i.c[0];
1127 return o.d;
1128 } //dcmFloatDouble()
1129 #endif
1130
dcmInt(int lByteLength,unsigned char lBuffer[],bool littleEndian)1131 int dcmInt(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 16 or 32 bit integer
1132 if (littleEndian) {
1133 if (lByteLength <= 3)
1134 return lBuffer[0] | (lBuffer[1] << 8); //shortint vs word?
1135 return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24); //shortint vs word?
1136 }
1137 if (lByteLength <= 3)
1138 return lBuffer[1] | (lBuffer[0] << 8); //shortint vs word?
1139 return lBuffer[3] + (lBuffer[2] << 8) + (lBuffer[1] << 16) + (lBuffer[0] << 24); //shortint vs word?
1140 } //dcmInt()
1141
dcmAttributeTag(unsigned char lBuffer[],bool littleEndian)1142 uint32_t dcmAttributeTag(unsigned char lBuffer[], bool littleEndian) {
1143 // read Attribute Tag (AT) value
1144 // return in Group + (Element << 16) format
1145 if (littleEndian)
1146 return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24);
1147 return lBuffer[1] + (lBuffer[0] << 8) + (lBuffer[3] << 16) + (lBuffer[2] << 24);
1148 } //dcmInt()
1149
dcmStrInt(const int lByteLength,const unsigned char lBuffer[])1150 int dcmStrInt(const int lByteLength, const unsigned char lBuffer[]) { //read int stored as a string
1151 char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1));
1152 cString[lByteLength] = 0;
1153 memcpy(cString, (const unsigned char *)(&lBuffer[0]), lByteLength);
1154 int ret = atoi(cString);
1155 free(cString);
1156 return ret;
1157 } //dcmStrInt()
1158
dcmStrManufacturer(const int lByteLength,unsigned char lBuffer[])1159 int dcmStrManufacturer(const int lByteLength, unsigned char lBuffer[]) { //read float stored as a string
1160 if (lByteLength < 2)
1161 return kMANUFACTURER_UNKNOWN;
1162 //#ifdef _MSC_VER
1163 char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1));
1164 //#else
1165 // char cString[lByteLength + 1];
1166 //#endif
1167 int ret = kMANUFACTURER_UNKNOWN;
1168 cString[lByteLength] = 0;
1169 memcpy(cString, (char *)&lBuffer[0], lByteLength);
1170 if ((toupper(cString[0]) == 'S') && (toupper(cString[1]) == 'I'))
1171 ret = kMANUFACTURER_SIEMENS;
1172 if ((toupper(cString[0]) == 'G') && (toupper(cString[1]) == 'E'))
1173 ret = kMANUFACTURER_GE;
1174 if ((toupper(cString[0]) == 'H') && (toupper(cString[1]) == 'I'))
1175 ret = kMANUFACTURER_HITACHI;
1176 if ((toupper(cString[0]) == 'M') && (toupper(cString[1]) == 'E'))
1177 ret = kMANUFACTURER_MEDISO;
1178 if ((toupper(cString[0]) == 'P') && (toupper(cString[1]) == 'H'))
1179 ret = kMANUFACTURER_PHILIPS;
1180 if ((toupper(cString[0]) == 'T') && (toupper(cString[1]) == 'O'))
1181 ret = kMANUFACTURER_TOSHIBA;
1182 //CANON_MEC
1183 if ((toupper(cString[0]) == 'C') && (toupper(cString[1]) == 'A'))
1184 ret = kMANUFACTURER_CANON;
1185 if ((toupper(cString[0]) == 'U') && (toupper(cString[1]) == 'I'))
1186 ret = kMANUFACTURER_UIH;
1187 if ((toupper(cString[0]) == 'B') && (toupper(cString[1]) == 'R'))
1188 ret = kMANUFACTURER_BRUKER;
1189 if (ret == kMANUFACTURER_UNKNOWN)
1190 printWarning("Unknown manufacturer %s\n", cString);
1191 //#ifdef _MSC_VER
1192 free(cString);
1193 //#endif
1194 return ret;
1195 } //dcmStrManufacturer
1196
csaMultiFloat(unsigned char buff[],int nItems,float Floats[],int * ItemsOK)1197 float csaMultiFloat(unsigned char buff[], int nItems, float Floats[], int *ItemsOK) {
1198 //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats]
1199 //if lnItems == 1, returns first item, if lnItems > 1 returns index of final successful conversion
1200 TCSAitem itemCSA;
1201 *ItemsOK = 0;
1202 if (nItems < 1)
1203 return 0.0f;
1204 Floats[1] = 0;
1205 int lPos = 0;
1206 for (int lI = 1; lI <= nItems; lI++) {
1207 memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA));
1208 lPos += sizeof(itemCSA);
1209 // Storage order is always little-endian, so byte-swap required values if necessary
1210 if (!littleEndianPlatform())
1211 nifti_swap_4bytes(1, &itemCSA.xx2_Len);
1212 if (itemCSA.xx2_Len > 0) {
1213 char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len));
1214 memcpy(cString, &buff[lPos], itemCSA.xx2_Len); //TPX memcpy(&cString, &buff[lPos], sizeof(cString));
1215 lPos += ((itemCSA.xx2_Len + 3) / 4) * 4;
1216 //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString);
1217 Floats[lI] = (float)atof(cString);
1218 *ItemsOK = lI; //some sequences have store empty items
1219 free(cString);
1220 }
1221 } //for each item
1222 return Floats[1];
1223 } //csaMultiFloat()
1224
csaIsPhaseMap(unsigned char buff[],int nItems)1225 bool csaIsPhaseMap(unsigned char buff[], int nItems) {
1226 //returns true if the tag "ImageHistory" has an item named "CC:ComplexAdd"
1227 TCSAitem itemCSA;
1228 if (nItems < 1)
1229 return false;
1230 int lPos = 0;
1231 for (int lI = 1; lI <= nItems; lI++) {
1232 memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA));
1233 lPos += sizeof(itemCSA);
1234 // Storage order is always little-endian, so byte-swap required values if necessary
1235 if (!littleEndianPlatform())
1236 nifti_swap_4bytes(1, &itemCSA.xx2_Len);
1237 if (itemCSA.xx2_Len > 0) {
1238 char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len + 1));
1239 memcpy(cString, &buff[lPos], sizeof(itemCSA.xx2_Len)); //TPX memcpy(&cString, &buff[lPos], sizeof(cString));
1240 lPos += ((itemCSA.xx2_Len + 3) / 4) * 4;
1241 //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString);
1242 if (strcmp(cString, "CC:ComplexAdd") == 0)
1243 return true;
1244 free(cString);
1245 }
1246 } //for each item
1247 return false;
1248 } //csaIsPhaseMap()
1249
checkSliceTimes(struct TCSAdata * CSA,int itemsOK,int isVerbose,bool is3DAcq)1250 void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3DAcq) {
1251 if ((is3DAcq) || (itemsOK < 1)) //we expect 3D sequences to be simultaneous
1252 return;
1253 if (itemsOK > kMaxEPI3D) {
1254 printError("Please increase kMaxEPI3D and recompile\n");
1255 return;
1256 }
1257 float maxTimeValue, minTimeValue, timeValue1;
1258 minTimeValue = CSA->sliceTiming[0];
1259 for (int z = 0; z < itemsOK; z++)
1260 if (CSA->sliceTiming[z] < minTimeValue)
1261 minTimeValue = CSA->sliceTiming[z];
1262 //CSA can report negative slice times
1263 // https://neurostars.org/t/slice-timing-illegal-values-in-fmriprep/1516/8
1264 // Nov 1, 2018 <siemens-healthineers.com> wrote:
1265 // If you have an interleaved dataset we can more definitively validate this formula (aka sliceTime(i) - min(sliceTimes())).
1266 if (minTimeValue < 0) {
1267 //printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); //if uncommented, overwhelming number of warnings (one per DICOM input), better once per series
1268 CSA->sliceTiming[kMaxEPI3D - 1] = -2.0; //issue 271: flag for unified warning
1269 for (int z = 0; z < itemsOK; z++)
1270 CSA->sliceTiming[z] = CSA->sliceTiming[z] - minTimeValue;
1271 }
1272 CSA->multiBandFactor = 1;
1273 timeValue1 = CSA->sliceTiming[0];
1274 int nTimeZero = 0;
1275 if (CSA->sliceTiming[0] == 0)
1276 nTimeZero++;
1277 int minTimeIndex = 0;
1278 int maxTimeIndex = minTimeIndex;
1279 minTimeValue = CSA->sliceTiming[0];
1280 maxTimeValue = minTimeValue;
1281 if (isVerbose > 1)
1282 printMessage(" sliceTimes %g\t", CSA->sliceTiming[0]);
1283 for (int z = 1; z < itemsOK; z++) { //find index and value of fastest time
1284 if (isVerbose > 1)
1285 printMessage("%g\t", CSA->sliceTiming[z]);
1286 if (CSA->sliceTiming[z] == 0)
1287 nTimeZero++;
1288 if (CSA->sliceTiming[z] < minTimeValue) {
1289 minTimeValue = CSA->sliceTiming[z];
1290 minTimeIndex = (float)z;
1291 }
1292 if (CSA->sliceTiming[z] > maxTimeValue) {
1293 maxTimeValue = CSA->sliceTiming[z];
1294 maxTimeIndex = (float)z;
1295 }
1296 if (CSA->sliceTiming[z] == timeValue1)
1297 CSA->multiBandFactor++;
1298 }
1299 if (isVerbose > 1)
1300 printMessage("\n");
1301 CSA->slice_start = minTimeIndex;
1302 CSA->slice_end = maxTimeIndex;
1303 if (minTimeIndex == maxTimeIndex) {
1304 if (isVerbose)
1305 printMessage("No variability in slice times (3D EPI?)\n");
1306 }
1307 if (nTimeZero < 2) { //not for multi-band, not 3D
1308 if (minTimeIndex == 1)
1309 CSA->sliceOrder = NIFTI_SLICE_ALT_INC2; // e.g. 3,1,4,2
1310 else if (minTimeIndex == (itemsOK - 2))
1311 CSA->sliceOrder = NIFTI_SLICE_ALT_DEC2; // e.g. 2,4,1,3 or 5,2,4,1,3
1312 else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] < CSA->sliceTiming[2]))
1313 CSA->sliceOrder = NIFTI_SLICE_SEQ_INC; // e.g. 1,2,3,4
1314 else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] > CSA->sliceTiming[2]))
1315 CSA->sliceOrder = NIFTI_SLICE_ALT_INC; //e.g. 1,3,2,4
1316 else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] > CSA->sliceTiming[itemsOK - 2]))
1317 CSA->sliceOrder = NIFTI_SLICE_SEQ_DEC; //e.g. 4,3,2,1 or 5,4,3,2,1
1318 else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] < CSA->sliceTiming[itemsOK - 2]))
1319 CSA->sliceOrder = NIFTI_SLICE_ALT_DEC; //e.g. 4,2,3,1 or 3,5,2,4,1
1320 else {
1321 if (!is3DAcq) //we expect 3D sequences to be simultaneous
1322 printWarning("Unable to determine slice order from CSA tag MosaicRefAcqTimes\n");
1323 }
1324 }
1325 if ((CSA->sliceOrder != NIFTI_SLICE_UNKNOWN) && (nTimeZero > 1) && (nTimeZero < itemsOK)) {
1326 if (isVerbose)
1327 printMessage(" Multiband x%d sequence: setting slice order as UNKNOWN (instead of %d)\n", nTimeZero, CSA->sliceOrder);
1328 CSA->sliceOrder = NIFTI_SLICE_UNKNOWN;
1329 }
1330 } //checkSliceTimes()
1331
readCSAImageHeader(unsigned char * buff,int lLength,struct TCSAdata * CSA,int isVerbose,bool is3DAcq)1332 int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, int isVerbose, bool is3DAcq) {
1333 //see also http://afni.nimh.nih.gov/pub/dist/src/siemens_dicom_csa.c
1334 //printMessage("%c%c%c%c\n",buff[0],buff[1],buff[2],buff[3]);
1335 if (lLength < 36)
1336 return EXIT_FAILURE;
1337 if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0'))
1338 return EXIT_FAILURE;
1339 int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2
1340 int lnTag = buff[lPos] + (buff[lPos + 1] << 8) + (buff[lPos + 2] << 16) + (buff[lPos + 3] << 24);
1341 if (buff[lPos + 4] != 77)
1342 return EXIT_FAILURE;
1343 lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0
1344 TCSAtag tagCSA;
1345 TCSAitem itemCSA;
1346 int itemsOK;
1347 float lFloats[7];
1348 for (int lT = 1; lT <= lnTag; lT++) {
1349 memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag
1350 lPos += sizeof(tagCSA);
1351 // Storage order is always little-endian, so byte-swap required values if necessary
1352 if (!littleEndianPlatform())
1353 nifti_swap_4bytes(1, &tagCSA.nitems);
1354 if (isVerbose > 1) //extreme verbosity: show every CSA tag
1355 printMessage(" %d CSA of %s %d\n", lPos, tagCSA.name, tagCSA.nitems);
1356 if (tagCSA.nitems > 0) {
1357 if (strcmp(tagCSA.name, "ImageHistory") == 0)
1358 CSA->isPhaseMap = csaIsPhaseMap(&buff[lPos], tagCSA.nitems);
1359 else if (strcmp(tagCSA.name, "NumberOfImagesInMosaic") == 0)
1360 CSA->mosaicSlices = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK));
1361 else if (strcmp(tagCSA.name, "B_value") == 0) {
1362 CSA->dtiV[0] = csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK);
1363 if (CSA->dtiV[0] < 0.0) {
1364 printWarning("(Corrupt) CSA reports negative b-value! %g\n", CSA->dtiV[0]);
1365 CSA->dtiV[0] = 0.0;
1366 }
1367 CSA->numDti = 1; //triggered by b-value, as B0 images do not have DiffusionGradientDirection tag
1368 } else if ((strcmp(tagCSA.name, "DiffusionGradientDirection") == 0) && (tagCSA.nitems > 2)) {
1369 CSA->dtiV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK);
1370 CSA->dtiV[2] = lFloats[2];
1371 CSA->dtiV[3] = lFloats[3];
1372 if (isVerbose)
1373 printMessage("DiffusionGradientDirection %f %f %f\n", lFloats[1], lFloats[2], lFloats[3]);
1374 } else if ((strcmp(tagCSA.name, "SliceNormalVector") == 0) && (tagCSA.nitems > 2)) {
1375 CSA->sliceNormV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK);
1376 CSA->sliceNormV[2] = lFloats[2];
1377 CSA->sliceNormV[3] = lFloats[3];
1378 if (isVerbose > 1)
1379 printMessage(" SliceNormalVector %f %f %f\n", CSA->sliceNormV[1], CSA->sliceNormV[2], CSA->sliceNormV[3]);
1380 } else if (strcmp(tagCSA.name, "SliceMeasurementDuration") == 0)
1381 CSA->sliceMeasurementDuration = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK);
1382 else if (strcmp(tagCSA.name, "BandwidthPerPixelPhaseEncode") == 0)
1383 CSA->bandwidthPerPixelPhaseEncode = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK);
1384 else if ((strcmp(tagCSA.name, "MosaicRefAcqTimes") == 0) && (tagCSA.nitems > 3)) {
1385 if (itemsOK > kMaxEPI3D) {
1386 printError("Please increase kMaxEPI3D and recompile\n");
1387 } else {
1388 float *sliceTimes = (float *)malloc(sizeof(float) * (tagCSA.nitems + 1));
1389 csaMultiFloat(&buff[lPos], tagCSA.nitems, sliceTimes, &itemsOK);
1390 for (int z = 0; z < kMaxEPI3D; z++)
1391 CSA->sliceTiming[z] = -1.0;
1392 for (int z = 0; z < itemsOK; z++)
1393 CSA->sliceTiming[z] = sliceTimes[z + 1];
1394 free(sliceTimes);
1395 checkSliceTimes(CSA, itemsOK, isVerbose, is3DAcq);
1396 }
1397 } else if (strcmp(tagCSA.name, "ProtocolSliceNumber") == 0)
1398 CSA->protocolSliceNumber1 = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK));
1399 else if (strcmp(tagCSA.name, "PhaseEncodingDirectionPositive") == 0)
1400 CSA->phaseEncodingDirectionPositive = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK));
1401 for (int lI = 1; lI <= tagCSA.nitems; lI++) {
1402 memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA));
1403 lPos += sizeof(itemCSA);
1404 // Storage order is always little-endian, so byte-swap required values if necessary
1405 if (!littleEndianPlatform())
1406 nifti_swap_4bytes(1, &itemCSA.xx2_Len);
1407 lPos += ((itemCSA.xx2_Len + 3) / 4) * 4;
1408 }
1409 } //if at least 1 item
1410 } // for lT 1..lnTag
1411 if (CSA->protocolSliceNumber1 > 1)
1412 CSA->sliceOrder = NIFTI_SLICE_UNKNOWN;
1413 return EXIT_SUCCESS;
1414 } // readCSAImageHeader()
1415
dcmMultiShorts(int lByteLength,unsigned char lBuffer[],int lnShorts,uint16_t * lShorts,bool littleEndian)1416 void dcmMultiShorts(int lByteLength, unsigned char lBuffer[], int lnShorts, uint16_t *lShorts, bool littleEndian) {
1417 //read array of unsigned shorts US http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html
1418 if ((lnShorts < 1) || (lByteLength != (lnShorts * 2)))
1419 return;
1420 memcpy(&lShorts[0], (uint16_t *)&lBuffer[0], lByteLength);
1421 bool swap = (littleEndian != littleEndianPlatform());
1422 if (swap)
1423 nifti_swap_2bytes(lnShorts, &lShorts[0]);
1424 } //dcmMultiShorts()
1425
dcmMultiLongs(int lByteLength,unsigned char lBuffer[],int lnLongs,uint32_t * lLongs,bool littleEndian)1426 void dcmMultiLongs(int lByteLength, unsigned char lBuffer[], int lnLongs, uint32_t *lLongs, bool littleEndian) {
1427 //read array of unsigned longs UL http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html
1428 if ((lnLongs < 1) || (lByteLength != (lnLongs * 4)))
1429 return;
1430 memcpy(&lLongs[0], (uint32_t *)&lBuffer[0], lByteLength);
1431 bool swap = (littleEndian != littleEndianPlatform());
1432 if (swap)
1433 nifti_swap_4bytes(lnLongs, &lLongs[0]);
1434 } //dcmMultiLongs()
1435
dcmMultiFloat(int lByteLength,char lBuffer[],int lnFloats,float * lFloats)1436 void dcmMultiFloat(int lByteLength, char lBuffer[], int lnFloats, float *lFloats) {
1437 //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats]
1438 if ((lnFloats < 1) || (lByteLength < 1))
1439 return;
1440 char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1));
1441 memcpy(cString, (char *)&lBuffer[0], lByteLength);
1442 cString[lByteLength] = 0; //null terminate
1443 char *temp = (char *)malloc(lByteLength + 1);
1444 int f = 0, lStart = 0;
1445 bool isOK = false;
1446 for (int i = 0; i <= lByteLength; i++) {
1447 if ((lBuffer[i] >= '0') && (lBuffer[i] <= '9'))
1448 isOK = true;
1449 if ((isOK) && ((i == (lByteLength)) || (lBuffer[i] == '/') || (lBuffer[i] == ' ') || (lBuffer[i] == '\\'))) {
1450 snprintf(temp, i - lStart + 1, "%s", &cString[lStart]);
1451 //printMessage("dcmMultiFloat %s\n",temp);
1452 if (f < lnFloats) {
1453 f++;
1454 lFloats[f] = (float)atof(temp);
1455 isOK = false;
1456 //printMessage("%d == %f\n", f, atof(temp));
1457 } //if f <= nFloats
1458 lStart = i + 1;
1459 } //if isOK
1460 } //for i to length
1461 free(temp);
1462 free(cString);
1463 } //dcmMultiFloat()
1464
dcmStrFloat(const int lByteLength,const unsigned char lBuffer[])1465 float dcmStrFloat(const int lByteLength, const unsigned char lBuffer[]) { //read float stored as a string
1466 char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1));
1467 memcpy(cString, (char *)&lBuffer[0], lByteLength);
1468 cString[lByteLength] = 0; //null terminate
1469 float ret = (float)atof(cString);
1470 free(cString);
1471 return ret;
1472 } //dcmStrFloat()
1473
headerDcm2Nii(struct TDICOMdata d,struct nifti_1_header * h,bool isComputeSForm)1474 int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm) {
1475 memset(h, 0, sizeof(nifti_1_header)); //zero-fill structure so unused items are consistent
1476 for (int i = 0; i < 80; i++)
1477 h->descrip[i] = 0;
1478 for (int i = 0; i < 24; i++)
1479 h->aux_file[i] = 0;
1480 for (int i = 0; i < 18; i++)
1481 h->db_name[i] = 0;
1482 for (int i = 0; i < 10; i++)
1483 h->data_type[i] = 0;
1484 for (int i = 0; i < 16; i++)
1485 h->intent_name[i] = 0;
1486 if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3)) {
1487 h->intent_code = NIFTI_INTENT_ESTIMATE; //make sure we treat this as RGBRGB...RGB
1488 h->datatype = DT_RGB24;
1489 } else if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 1))
1490 h->datatype = DT_UINT8;
1491 else if ((d.bitsAllocated == 12) && (d.samplesPerPixel == 1))
1492 h->datatype = DT_INT16;
1493 else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (d.isSigned))
1494 h->datatype = DT_INT16;
1495 else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (!d.isSigned))
1496 h->datatype = DT_UINT16;
1497 else if ((d.bitsAllocated == 32) && (d.isFloat))
1498 h->datatype = DT_FLOAT32;
1499 else if (d.bitsAllocated == 32)
1500 h->datatype = DT_INT32;
1501 else if ((d.bitsAllocated == 64) && (d.isFloat))
1502 h->datatype = DT_FLOAT64;
1503 else {
1504 printMessage("Unsupported DICOM bit-depth %d with %d samples per pixel\n", d.bitsAllocated, d.samplesPerPixel);
1505 return EXIT_FAILURE;
1506 }
1507 if ((h->datatype == DT_UINT16) && (d.bitsStored > 0) && (d.bitsStored < 16))
1508 h->datatype = DT_INT16; // DT_INT16 is more widely supported, same representation for values 0..32767
1509 for (int i = 0; i < 8; i++) {
1510 h->pixdim[i] = 0.0f;
1511 h->dim[i] = 0;
1512 }
1513 //next items listed as unused in NIfTI format, but zeroed for consistency across runs
1514 h->extents = 0;
1515 h->session_error = kSessionOK;
1516 h->glmin = 0; //unused, but make consistent
1517 h->glmax = 0; //unused, but make consistent
1518 h->regular = 114; //in legacy Analyze this was always 114
1519 //these are important
1520 h->scl_inter = d.intenIntercept;
1521 h->scl_slope = d.intenScale;
1522 h->cal_max = 0;
1523 h->cal_min = 0;
1524 h->magic[0] = 'n';
1525 h->magic[1] = '+';
1526 h->magic[2] = '1';
1527 h->magic[3] = '\0';
1528 h->vox_offset = (float)d.imageStart;
1529 if (d.bitsAllocated == 12)
1530 h->bitpix = 16 * d.samplesPerPixel;
1531 else
1532 h->bitpix = d.bitsAllocated * d.samplesPerPixel;
1533 h->pixdim[1] = d.xyzMM[1];
1534 h->pixdim[2] = d.xyzMM[2];
1535 h->pixdim[3] = d.xyzMM[3];
1536 h->pixdim[4] = d.TR / 1000.0; //TR reported in msec, time is in sec
1537 h->dim[1] = d.xyzDim[1];
1538 h->dim[2] = d.xyzDim[2];
1539 h->dim[3] = d.xyzDim[3];
1540 h->dim[4] = d.xyzDim[4];
1541 h->dim[5] = 1;
1542 h->dim[6] = 1;
1543 h->dim[7] = 1;
1544 if (h->dim[4] < 2)
1545 h->dim[0] = 3;
1546 else
1547 h->dim[0] = 4;
1548 for (int i = 0; i <= 3; i++) {
1549 h->srow_x[i] = 0.0f;
1550 h->srow_y[i] = 0.0f;
1551 h->srow_z[i] = 0.0f;
1552 }
1553 h->slice_start = 0;
1554 h->slice_end = 0;
1555 h->srow_x[0] = -1;
1556 h->srow_y[2] = 1;
1557 h->srow_z[1] = -1;
1558 h->srow_x[3] = ((float)h->dim[1] / 2);
1559 h->srow_y[3] = -((float)h->dim[3] / 2);
1560 h->srow_z[3] = ((float)h->dim[2] / 2);
1561 h->qform_code = NIFTI_XFORM_UNKNOWN;
1562 h->sform_code = NIFTI_XFORM_UNKNOWN;
1563 h->toffset = 0;
1564 h->intent_code = NIFTI_INTENT_NONE;
1565 h->dim_info = 0; //Freq, Phase and Slice all unknown
1566 h->xyzt_units = NIFTI_UNITS_MM + NIFTI_UNITS_SEC;
1567 h->slice_duration = 0; //avoid +inf/-inf, NaN
1568 h->intent_p1 = 0; //avoid +inf/-inf, NaN
1569 h->intent_p2 = 0; //avoid +inf/-inf, NaN
1570 h->intent_p3 = 0; //avoid +inf/-inf, NaN
1571 h->pixdim[0] = 1; //QFactor should be 1 or -1
1572 h->sizeof_hdr = 348; //used to signify header does not need to be byte-swapped
1573 h->slice_code = d.CSA.sliceOrder;
1574 if (isComputeSForm)
1575 headerDcm2Nii2(d, d, h, false);
1576 return EXIT_SUCCESS;
1577 } // headerDcm2Nii()
1578
isFloatDiff(float a,float b)1579 bool isFloatDiff(float a, float b) {
1580 return (fabs(a - b) > FLT_EPSILON);
1581 } //isFloatDiff()
1582
nifti_mat33_reorder_cols(mat33 m,ivec3 v)1583 mat33 nifti_mat33_reorder_cols(mat33 m, ivec3 v) {
1584 // matlab equivalent ret = m(:, v); where v is 1,2,3 [INDEXED FROM ONE!!!!]
1585 mat33 ret;
1586 for (int r = 0; r < 3; r++) {
1587 for (int c = 0; c < 3; c++)
1588 ret.m[r][c] = m.m[r][v.v[c] - 1];
1589 }
1590 return ret;
1591 } //nifti_mat33_reorder_cols()
1592
changeExt(char * file_name,const char * ext)1593 void changeExt(char *file_name, const char *ext) {
1594 char *p_extension;
1595 p_extension = strrchr(file_name, '.');
1596 if (p_extension)
1597 strcpy(++p_extension, ext);
1598 } //changeExt()
1599
cleanStr(char * lOut)1600 void cleanStr(char *lOut) {
1601 //e.g. strings such as image comments with special characters (e.g. "G/6/2009") can disrupt file saves
1602 size_t lLength = strlen(lOut);
1603 if (lLength < 1)
1604 return;
1605 char *cString = (char *)malloc(sizeof(char) * (lLength + 1));
1606 cString[lLength] = 0;
1607 memcpy(cString, (char *)&lOut[0], lLength);
1608 for (int i = 0; i < lLength; i++)
1609 //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1
1610 if (cString[i] < 1) {
1611 unsigned char c = (unsigned char)cString[i];
1612 if ((c >= 192) && (c <= 198))
1613 cString[i] = 'A';
1614 if (c == 199)
1615 cString[i] = 'C';
1616 if ((c >= 200) && (c <= 203))
1617 cString[i] = 'E';
1618 if ((c >= 204) && (c <= 207))
1619 cString[i] = 'I';
1620 if (c == 208)
1621 cString[i] = 'D';
1622 if (c == 209)
1623 cString[i] = 'N';
1624 if ((c >= 210) && (c <= 214))
1625 cString[i] = 'O';
1626 if (c == 215)
1627 cString[i] = 'x';
1628 if (c == 216)
1629 cString[i] = 'O';
1630 if ((c >= 217) && (c <= 220))
1631 cString[i] = 'O';
1632 if (c == 221)
1633 cString[i] = 'Y';
1634 if ((c >= 224) && (c <= 230))
1635 cString[i] = 'a';
1636 if (c == 231)
1637 cString[i] = 'c';
1638 if ((c >= 232) && (c <= 235))
1639 cString[i] = 'e';
1640 if ((c >= 236) && (c <= 239))
1641 cString[i] = 'i';
1642 if (c == 240)
1643 cString[i] = 'o';
1644 if (c == 241)
1645 cString[i] = 'n';
1646 if ((c >= 242) && (c <= 246))
1647 cString[i] = 'o';
1648 if (c == 248)
1649 cString[i] = 'o';
1650 if ((c >= 249) && (c <= 252))
1651 cString[i] = 'u';
1652 if (c == 253)
1653 cString[i] = 'y';
1654 if (c == 255)
1655 cString[i] = 'y';
1656 }
1657 for (int i = 0; i < lLength; i++)
1658 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))
1659 cString[i] = '_'; //issue398
1660 //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] = '_';
1661 int len = 1;
1662 for (int i = 1; i < lLength; i++) { //remove repeated "_"
1663 if ((cString[i - 1] != '_') || (cString[i] != '_')) {
1664 cString[len] = cString[i];
1665 len++;
1666 }
1667 } //for each item
1668 if (cString[len - 1] == '_')
1669 len--;
1670 cString[len] = 0; //null-terminate, strlcpy does this anyway
1671 int maxLen = kDICOMStr;
1672 len = dcmStrLen(len, maxLen);
1673 if (len == maxLen) { //we need space for null-termination
1674 if (cString[len - 2] == '_')
1675 len = len - 2;
1676 }
1677 memcpy(lOut, cString, len - 1);
1678 lOut[len - 1] = 0;
1679 free(cString);
1680 } //cleanStr()
1681
isSameFloatGE(float a,float b)1682 int isSameFloatGE(float a, float b) {
1683 //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 !!!
1684 //return (a == b); //niave approach does not have any tolerance for rounding errors
1685 return (fabs(a - b) <= 0.0001);
1686 }
1687
nii_readParRec(char * parname,int isVerbose,struct TDTI4D * dti4D,bool isReadPhase)1688 struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase) {
1689 struct TDICOMdata d = clear_dicom_data();
1690 dti4D->sliceOrder[0] = -1;
1691 dti4D->volumeOnsetTime[0] = -1;
1692 dti4D->decayFactor[0] = -1;
1693 dti4D->frameDuration[0] = -1;
1694 //dti4D->fragmentOffset[0] = -1;
1695 dti4D->intenScale[0] = 0.0;
1696 strcpy(d.protocolName, ""); //erase dummy with empty
1697 strcpy(d.seriesDescription, ""); //erase dummy with empty
1698 strcpy(d.sequenceName, ""); //erase dummy with empty
1699 strcpy(d.scanningSequence, "");
1700 FILE *fp = fopen(parname, "r");
1701 if (fp == NULL)
1702 return d;
1703 #define LINESZ 2048
1704 #define kSlice 0
1705 #define kEcho 1
1706 #define kDyn 2
1707 #define kCardiac 3
1708 #define kImageType 4
1709 #define kSequence 5
1710 #define kIndex 6
1711 //V3 only identical for columns 1..6
1712 #define kBitsPerVoxel 7 //V3: not per slice: "Image pixel size [8 or 16 bits]"
1713 #define kXdim 9 //V3: not per slice: "Recon resolution (x, y)"
1714 #define kYdim 10 //V3: not per slice: "Recon resolution (x, y)"
1715 int kRI = 11; //V3: 7
1716 int kRS = 12; //V3: 8
1717 int kSS = 13; //V3: 9
1718 int kAngulationAPs = 16; //V3: 12
1719 int kAngulationFHs = 17; //V3: 13
1720 int kAngulationRLs = 18; //V3: 14
1721 int kPositionAP = 19; //V3: 15
1722 int kPositionFH = 20; //V3: 16
1723 int kPositionRL = 21; //V3: 17
1724 #define kThickmm 22 //V3: not per slice: "Slice thickness [mm]"
1725 #define kGapmm 23 //V3: not per slice: "Slice gap [mm]"
1726 int kSliceOrients = 25; //V3: 19
1727 int kXmm = 28; //V3: 22
1728 int kYmm = 29; //V3: 23
1729 int kTEcho = 30; //V3: 24
1730 int kDynTime = 31; //V3: 25
1731 int kTriggerTime = 32; //V3: 26
1732 int kbval = 33;//V3: 27
1733 //the following do not exist in V3
1734 #define kInversionDelayMs 40
1735 #define kbvalNumber 41
1736 #define kGradientNumber 42
1737 //the following do not exist in V40 or earlier
1738 #define kv1 47
1739 #define kv2 45
1740 #define kv3 46
1741 //the following do not exist in V41 or earlier
1742 #define kASL 48
1743 #define kMaxImageType 4 //4 observed image types: real, imag, mag, phase (in theory also subsequent calculation such as B1)
1744 printWarning("dcm2niix PAR is not actively supported (hint: use dicm2nii)\n");
1745 if (isReadPhase)
1746 printWarning(" Reading phase images from PAR/REC\n");
1747 char buff[LINESZ];
1748 //next values: PAR V3 only
1749 int v3BitsPerVoxel = 16; //V3: not per slice: "Image pixel size [8 or 16 bits]"
1750 int v3Xdim = 128; //not per slice: "Recon resolution (x, y)"
1751 int v3Ydim = 128; //V3: not per slice: "Recon resolution (x, y)"
1752 float v3Thickmm = 2.0; //V3: not per slice: "Slice thickness [mm]"
1753 float v3Gapmm = 0.0; //V3: not per slice: "Slice gap [mm]"
1754 //from top of header
1755 int maxNumberOfDiffusionValues = 1;
1756 int maxNumberOfGradientOrients = 1;
1757 int maxNumberOfCardiacPhases = 1;
1758 int maxNumberOfEchoes = 1;
1759 int maxNumberOfDynamics = 1;
1760 int maxNumberOfMixes = 1;
1761 int maxNumberOfLabels = 1; //Number of label types <0=no ASL>
1762 float maxBValue = 0.0f;
1763 float maxDynTime = 0.0f;
1764 float minDynTime = 999999.0f;
1765 float TE = 0.0;
1766 int minDyn = 32767;
1767 int maxDyn = 0;
1768 int minSlice = 32767;
1769 int maxSlice = 0;
1770 bool ADCwarning = false;
1771 bool isTypeWarning = false;
1772 bool isType4Warning = false;
1773 bool isSequenceWarning = false;
1774 int numSlice2D = 0;
1775 int prevDyn = -1;
1776 bool dynNotAscending = false;
1777 int parVers = 0;
1778 int maxSeq = -1; //maximum value of Seq column
1779 int seq1 = -1; //value of Seq volume for first slice
1780 int maxEcho = 1;
1781 int maxCardiac = 1;
1782 int nCols = 26;
1783 //int diskSlice = 0;
1784 int num3DExpected = 0; //number of 3D volumes in the top part of the header
1785 int num2DExpected = 0; //number of 2D slices described in the top part of the header
1786 int maxVol = -1;
1787 int patientPositionNumPhilips = 0;
1788 d.isValid = false;
1789 const int kMaxCols = 49;
1790 float *cols = (float *)malloc(sizeof(float) * (kMaxCols + 1));
1791 for (int i = 0; i < kMaxCols; i++)
1792 cols[i] = 0.0; //old versions of PAR do not fill all columns - beware of buffer overflow
1793 char *p = fgets(buff, LINESZ, fp);
1794 bool isIntenScaleVaries = false;
1795 for (int i = 0; i < kMaxDTI4D; i++) {
1796 dti4D->S[i].V[0] = -1.0;
1797 dti4D->TE[i] = -1.0;
1798 }
1799 for (int i = 0; i < kMaxSlice2D; i++)
1800 dti4D->sliceOrder[i] = -1;
1801 while (p) {
1802 if (strlen(buff) < 1)
1803 continue;
1804 if (buff[0] == '#') { //comment
1805 char Comment[7][50];
1806 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]);
1807 if ((strcmp(Comment[0], "sl") == 0) && (strcmp(Comment[1], "ec") == 0)) {
1808 num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes;
1809 num2DExpected = d.xyzDim[3] * num3DExpected;
1810 if ((num2DExpected) >= kMaxSlice2D) {
1811 printError("Use dicm2nii or increase kMaxSlice2D to be more than %d\n", num2DExpected);
1812 printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n",
1813 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
1814 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels);
1815 free(cols);
1816 return d;
1817 }
1818 }
1819 if (strcmp(Comment[1], "TRYOUT") == 0) {
1820 //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]);
1821 parVers = (int)round(atof(Comment[6]) * 10); //4.2 = 42 etc
1822 if (parVers <= 29) {
1823 printMessage("Unsupported old PAR version %0.2f (use dicm2nii)\n", parVers / 10.0);
1824 return d;
1825 //nCols = 26; //e.g. PAR 3.0 has 26 relevant columns
1826 }
1827 if (parVers < 40) {
1828 nCols = 29; // PAR 3.0?
1829 kRI = 7;
1830 kRS = 8;
1831 kSS = 9;
1832 kAngulationAPs = 12;
1833 kAngulationFHs = 13;
1834 kAngulationRLs = 14;
1835 kPositionAP = 15;
1836 kPositionFH = 16;
1837 kPositionRL = 17;
1838 kSliceOrients = 19;
1839 kXmm = 22;
1840 kYmm = 23;
1841 kTEcho = 24;
1842 kDynTime = 25;
1843 kTriggerTime = 26;
1844 kbval = 27;
1845 } else if (parVers < 41)
1846 nCols = kv1; //e.g PAR 4.0
1847 else if (parVers < 42)
1848 nCols = kASL; //e.g. PAR 4.1 - last column is final diffusion b-value
1849 else
1850 nCols = kMaxCols; //e.g. PAR 4.2
1851 }
1852 //the following do not exist in V3
1853 p = fgets(buff, LINESZ, fp); //get next line
1854 continue;
1855 } //process '#' comment
1856 if (buff[0] == '.') { //tag
1857 char Comment[9][50];
1858 for (int i = 0; i < 9; i++)
1859 strcpy(Comment[i], "");
1860 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]);
1861 if ((strcmp(Comment[0], "Acquisition") == 0) && (strcmp(Comment[1], "nr") == 0)) {
1862 d.acquNum = atoi(Comment[3]);
1863 d.seriesNum = d.acquNum;
1864 }
1865 if ((strcmp(Comment[0], "Recon") == 0) && (strcmp(Comment[1], "resolution") == 0)) {
1866 v3Xdim = (int)atoi(Comment[5]);
1867 v3Ydim = (int)atoi(Comment[6]);
1868 //printMessage("recon %d,%d\n", v3Xdim,v3Ydim);
1869 }
1870 if ((strcmp(Comment[1], "pixel") == 0) && (strcmp(Comment[2], "size") == 0)) {
1871 v3BitsPerVoxel = (int)atoi(Comment[8]);
1872 //printMessage("bits %d\n", v3BitsPerVoxel);
1873 }
1874 if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "gap") == 0)) {
1875 v3Gapmm = (float)atof(Comment[4]);
1876 //printMessage("gap %g\n", v3Gapmm);
1877 }
1878 if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "thickness") == 0)) {
1879 v3Thickmm = (float)atof(Comment[4]);
1880 //printMessage("thick %g\n", v3Thickmm);
1881 }
1882 if ((strcmp(Comment[0], "Repetition") == 0) && (strcmp(Comment[1], "time") == 0))
1883 d.TR = (float)atof(Comment[4]);
1884 if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "name") == 0)) {
1885 strcpy(d.patientName, Comment[3]);
1886 strcat(d.patientName, Comment[4]);
1887 strcat(d.patientName, Comment[5]);
1888 strcat(d.patientName, Comment[6]);
1889 strcat(d.patientName, Comment[7]);
1890 cleanStr(d.patientName);
1891 //printMessage("%s\n",d.patientName);
1892 }
1893 if ((strcmp(Comment[0], "Technique") == 0) && (strcmp(Comment[1], ":") == 0)) {
1894 strcpy(d.patientID, Comment[2]);
1895 strcat(d.patientID, Comment[3]);
1896 strcat(d.patientID, Comment[4]);
1897 strcat(d.patientID, Comment[5]);
1898 strcat(d.patientID, Comment[6]);
1899 strcat(d.patientID, Comment[7]);
1900 cleanStr(d.patientID);
1901 }
1902 if ((strcmp(Comment[0], "Protocol") == 0) && (strcmp(Comment[1], "name") == 0)) {
1903 strcpy(d.protocolName, Comment[3]);
1904 strcat(d.protocolName, Comment[4]);
1905 strcat(d.protocolName, Comment[5]);
1906 strcat(d.protocolName, Comment[6]);
1907 strcat(d.protocolName, Comment[7]);
1908 cleanStr(d.protocolName);
1909 }
1910 if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "name") == 0)) {
1911 strcpy(d.imageComments, Comment[3]);
1912 strcat(d.imageComments, Comment[4]);
1913 strcat(d.imageComments, Comment[5]);
1914 strcat(d.imageComments, Comment[6]);
1915 strcat(d.imageComments, Comment[7]);
1916 cleanStr(d.imageComments);
1917 }
1918 if ((strcmp(Comment[0], "Series") == 0) && (strcmp(Comment[1], "Type") == 0)) {
1919 strcpy(d.seriesDescription, Comment[3]);
1920 strcat(d.seriesDescription, Comment[4]);
1921 strcat(d.seriesDescription, Comment[5]);
1922 strcat(d.seriesDescription, Comment[6]);
1923 strcat(d.seriesDescription, Comment[7]);
1924 cleanStr(d.seriesDescription);
1925 }
1926 if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "date/time") == 0)) {
1927 if ((strlen(Comment[3]) >= 10) && (strlen(Comment[5]) >= 8)) {
1928 //DICOM date format is YYYYMMDD, but PAR stores YYYY.MM.DD 2016.03.25
1929 d.studyDate[0] = Comment[3][0];
1930 d.studyDate[1] = Comment[3][1];
1931 d.studyDate[2] = Comment[3][2];
1932 d.studyDate[3] = Comment[3][3];
1933 d.studyDate[4] = Comment[3][5];
1934 d.studyDate[5] = Comment[3][6];
1935 d.studyDate[6] = Comment[3][8];
1936 d.studyDate[7] = Comment[3][9];
1937 d.studyDate[8] = '\0';
1938 //DICOM time format is HHMMSS.FFFFFF, but PAR stores HH:MM:SS, e.g. 18:00:42 or 09:34:16
1939 d.studyTime[0] = Comment[5][0];
1940 d.studyTime[1] = Comment[5][1];
1941 d.studyTime[2] = Comment[5][3];
1942 d.studyTime[3] = Comment[5][4];
1943 d.studyTime[4] = Comment[5][6];
1944 d.studyTime[5] = Comment[5][7];
1945 d.studyTime[6] = '\0';
1946 d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime);
1947 }
1948 }
1949 if ((strcmp(Comment[0], "Off") == 0) && (strcmp(Comment[1], "Centre") == 0)) {
1950 //Off Centre midslice(ap,fh,rl) [mm]
1951 d.stackOffcentre[2] = (float)atof(Comment[5]);
1952 d.stackOffcentre[3] = (float)atof(Comment[6]);
1953 d.stackOffcentre[1] = (float)atof(Comment[7]);
1954 }
1955 if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "position") == 0)) {
1956 //Off Centre midslice(ap,fh,rl) [mm]
1957 d.patientOrient[0] = toupper(Comment[3][0]);
1958 d.patientOrient[1] = toupper(Comment[4][0]);
1959 d.patientOrient[2] = toupper(Comment[5][0]);
1960 d.patientOrient[3] = 0;
1961 }
1962 if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "slices/locations") == 0)) {
1963 d.xyzDim[3] = atoi(Comment[5]);
1964 }
1965 if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "diffusion") == 0)) {
1966 maxNumberOfDiffusionValues = atoi(Comment[6]);
1967 //if (maxNumberOfDiffusionValues > 1) maxNumberOfDiffusionValues -= 1; //if two listed, one is B=0
1968 }
1969 if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "gradient") == 0)) {
1970 maxNumberOfGradientOrients = atoi(Comment[6]);
1971 //Warning ISOTROPIC scans may be stored that are not reported here! 32 directions plus isotropic = 33 volumes
1972 }
1973 if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "cardiac") == 0)) {
1974 maxNumberOfCardiacPhases = atoi(Comment[6]);
1975 }
1976 if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "echoes") == 0)) {
1977 maxNumberOfEchoes = atoi(Comment[5]);
1978 if (maxNumberOfEchoes > 1)
1979 d.isMultiEcho = true;
1980 }
1981 if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "dynamics") == 0)) {
1982 maxNumberOfDynamics = atoi(Comment[5]);
1983 }
1984 if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "mixes") == 0)) {
1985 maxNumberOfMixes = atoi(Comment[5]);
1986 if (maxNumberOfMixes > 1)
1987 printError("maxNumberOfMixes > 1. Please update this software to support these images\n");
1988 }
1989 if ((strcmp(Comment[0], "Number") == 0) && (strcmp(Comment[2], "label") == 0)) {
1990 maxNumberOfLabels = atoi(Comment[7]);
1991 if (maxNumberOfLabels < 1)
1992 maxNumberOfLabels = 1;
1993 }
1994 p = fgets(buff, LINESZ, fp); //get next line
1995 continue;
1996 } //process '.' tag
1997 if (strlen(buff) < 24) { //empty line
1998 p = fgets(buff, LINESZ, fp); //get next line
1999 continue;
2000 }
2001 if (parVers < 20) {
2002 printError("PAR files should have 'CLINICAL TRYOUT' line with a version from 2.0-4.2: %s\n", parname);
2003 free(cols);
2004 return d;
2005 }
2006 for (int i = 0; i <= nCols; i++)
2007 cols[i] = strtof(p, &p); // p+1 skip comma, read a float
2008 //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]);
2009 if ((int)cols[kSlice] == 0) { //line does not contain attributes
2010 p = fgets(buff, LINESZ, fp); //get next line
2011 continue;
2012 }
2013 //diskSlice ++;
2014 bool isADC = false;
2015 if ((maxNumberOfGradientOrients >= 2) && (cols[kbval] > 50) && isSameFloat(0.0, cols[kv1]) && isSameFloat(0.0, cols[kv2]) && isSameFloat(0.0, cols[kv3])) {
2016 isADC = true;
2017 ADCwarning = true;
2018 }
2019 if (numSlice2D < 1) {
2020 d.xyzMM[1] = cols[kXmm];
2021 d.xyzMM[2] = cols[kYmm];
2022 if (parVers < 40) { //v3 does things differently
2023 //cccc
2024 d.xyzDim[1] = v3Xdim;
2025 d.xyzDim[2] = v3Ydim;
2026 d.xyzMM[3] = v3Thickmm + v3Gapmm;
2027 d.bitsAllocated = v3BitsPerVoxel;
2028 d.bitsStored = v3BitsPerVoxel;
2029 } else {
2030 d.xyzDim[1] = (int)cols[kXdim];
2031 d.xyzDim[2] = (int)cols[kYdim];
2032 d.xyzMM[3] = cols[kThickmm] + cols[kGapmm];
2033 d.bitsAllocated = (int)cols[kBitsPerVoxel];
2034 d.bitsStored = (int)cols[kBitsPerVoxel];
2035 }
2036 d.patientPosition[1] = cols[kPositionRL];
2037 d.patientPosition[2] = cols[kPositionAP];
2038 d.patientPosition[3] = cols[kPositionFH];
2039 d.angulation[1] = cols[kAngulationRLs];
2040 d.angulation[2] = cols[kAngulationAPs];
2041 d.angulation[3] = cols[kAngulationFHs];
2042 d.sliceOrient = (int)cols[kSliceOrients];
2043 d.TE = cols[kTEcho];
2044 d.echoNum = cols[kEcho];
2045 d.TI = cols[kInversionDelayMs];
2046 d.intenIntercept = cols[kRI];
2047 d.intenScale = cols[kRS];
2048 d.intenScalePhilips = cols[kSS];
2049 } else {
2050 if (parVers >= 40) {
2051 if ((d.xyzDim[1] != cols[kXdim]) || (d.xyzDim[2] != cols[kYdim]) || (d.bitsAllocated != cols[kBitsPerVoxel])) {
2052 printError("Slice dimensions or bit depth varies %s\n", parname);
2053 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]);
2054 return d;
2055 }
2056 }
2057 if ((d.intenScale != cols[kRS]) || (d.intenIntercept != cols[kRI]))
2058 isIntenScaleVaries = true;
2059 }
2060 if (cols[kImageType] == 0)
2061 d.isHasMagnitude = true;
2062 if (cols[kImageType] != 0)
2063 d.isHasPhase = true;
2064 if (isSameFloat(cols[kImageType], 18)) {
2065 //printWarning("Field map in Hz will be saved as the 'real' image.\n");
2066 //isTypeWarning = true;
2067 d.isRealIsPhaseMapHz = true;
2068 } else if (((cols[kImageType] < 0.0) || (cols[kImageType] > 4.0)) && (!isTypeWarning)) {
2069 printError("Unknown type %g: not magnitude[0], real[1], imaginary[2] or phase[3].\n", cols[kImageType]);
2070 isTypeWarning = true;
2071 }
2072 if (cols[kDyn] > maxDyn)
2073 maxDyn = (int)cols[kDyn];
2074 if (cols[kDyn] < minDyn)
2075 minDyn = (int)cols[kDyn];
2076 if (cols[kDyn] < prevDyn)
2077 dynNotAscending = true;
2078 prevDyn = cols[kDyn];
2079 if (cols[kDynTime] > maxDynTime)
2080 maxDynTime = cols[kDynTime];
2081 if (cols[kDynTime] < minDynTime)
2082 minDynTime = cols[kDynTime];
2083 if (cols[kEcho] > maxEcho)
2084 maxEcho = cols[kEcho];
2085 if (cols[kCardiac] > maxCardiac)
2086 maxCardiac = cols[kCardiac];
2087 if ((cols[kEcho] == 1) && (cols[kDyn] == 1) && (cols[kCardiac] == 1) && (cols[kGradientNumber] == 1)) {
2088 if (cols[kSlice] == 1) {
2089 d.patientPosition[1] = cols[kPositionRL];
2090 d.patientPosition[2] = cols[kPositionAP];
2091 d.patientPosition[3] = cols[kPositionFH];
2092 }
2093 patientPositionNumPhilips++;
2094 }
2095 if (true) { //for every slice
2096 int slice = (int)cols[kSlice];
2097 if (slice < minSlice)
2098 minSlice = slice;
2099 if (slice > maxSlice) {
2100 maxSlice = slice;
2101 d.patientPositionLast[1] = cols[kPositionRL];
2102 d.patientPositionLast[2] = cols[kPositionAP];
2103 d.patientPositionLast[3] = cols[kPositionFH];
2104 }
2105 int volStep = maxNumberOfDynamics;
2106 int vol = ((int)cols[kDyn] - 1);
2107 #ifdef old
2108 int gradDynVol = (int)cols[kGradientNumber] - 1;
2109 if (gradDynVol < 0)
2110 gradDynVol = 0; //old PAREC without cols[kGradientNumber]
2111 vol = vol + (volStep * (gradDynVol));
2112 if (vol < 0)
2113 vol = 0;
2114 volStep = volStep * maxNumberOfGradientOrients;
2115 int bval = (int)cols[kbvalNumber];
2116 if (bval > 2) //b=0 is 0, b=1000 is 1, b=2000 is 2 - b=0 does not have multiple directions
2117 bval = bval - 1;
2118 else
2119 bval = 1;
2120 //if (slice == 1) printMessage("bVal %d bVec %d isADC %d nbVal %d nGrad %d\n",(int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients);
2121 vol = vol + (volStep * (bval - 1));
2122 volStep = volStep * (maxNumberOfDiffusionValues - 1);
2123 if (isADC)
2124 vol = volStep + (bval - 1);
2125 #else
2126 if (maxNumberOfDiffusionValues > 1) {
2127 int grad = (int)cols[kGradientNumber] - 1;
2128 if (grad < 0)
2129 grad = 0; //old v4 does not have this tag
2130 int bval = (int)cols[kbvalNumber] - 1;
2131 if (bval < 0)
2132 bval = 0; //old v4 does not have this tag
2133 if (isADC)
2134 vol = vol + (volStep * maxNumberOfDiffusionValues * maxNumberOfGradientOrients) + bval;
2135 else
2136 vol = vol + (volStep * grad) + (bval * maxNumberOfGradientOrients);
2137
2138 volStep = volStep * (maxNumberOfDiffusionValues + 1) * maxNumberOfGradientOrients;
2139 //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);
2140 }
2141 #endif
2142 vol = vol + (volStep * ((int)cols[kEcho] - 1));
2143 volStep = volStep * maxNumberOfEchoes;
2144 vol = vol + (volStep * ((int)cols[kCardiac] - 1));
2145 volStep = volStep * maxNumberOfCardiacPhases;
2146 int ASL = (int)cols[kASL];
2147 if (ASL < 1)
2148 ASL = 1;
2149 vol = vol + (volStep * (ASL - 1));
2150 volStep = volStep * maxNumberOfLabels;
2151 //if ((int)cols[kSequence] > 0)
2152 int seq = (int)cols[kSequence];
2153 if (seq1 < 0)
2154 seq1 = seq;
2155 if (seq > maxSeq)
2156 maxSeq = seq;
2157 if (seq != seq1) { //sequence varies within this PAR file
2158 if (!isSequenceWarning) {
2159 isSequenceWarning = true;
2160 printWarning("'scanning sequence' column varies within a single file. This behavior is not described at the top of the header.\n");
2161 }
2162 vol = vol + (volStep * 1);
2163 volStep = volStep * 2;
2164 }
2165 //if (slice == 1) printMessage("%d\t%d\t%d\t%d\t%d\n", isADC,(int)cols[kbvalNumber], (int)cols[kGradientNumber], bval, vol);
2166 if (vol > maxVol)
2167 maxVol = vol;
2168 bool isReal = (cols[kImageType] == 1);
2169 bool isImaginary = (cols[kImageType] == 2);
2170 bool isPhase = (cols[kImageType] == 3);
2171 if (cols[kImageType] == 18) {
2172 isReal = true;
2173 d.isRealIsPhaseMapHz = true;
2174 }
2175 if (cols[kImageType] == 4) {
2176 if (!isType4Warning) {
2177 printWarning("Unknown image type (4). Be aware the 'phase' image is of an unknown type.\n");
2178 isType4Warning = true;
2179 }
2180 isPhase = true; //2019
2181 }
2182 if ((cols[kImageType] != 18) && ((cols[kImageType] < 0.0) || (cols[kImageType] > 3.0))) {
2183 if (!isType4Warning) {
2184 printWarning("Unknown image type (%g). Be aware the 'phase' image is of an unknown type.\n", round(cols[kImageType]));
2185 isType4Warning = true;
2186 }
2187 isReal = true; //<- this is not correct, kludge for bug in ROGERS_20180526_WIP_B0_NS_8_1.PAR
2188 }
2189 if (isReal)
2190 vol += num3DExpected;
2191 if (isImaginary)
2192 vol += (2 * num3DExpected);
2193 if (isPhase)
2194 vol += (3 * num3DExpected);
2195 if (vol >= kMaxDTI4D) {
2196 printError("Use dicm2nii or increase kMaxDTI4D (currently %d)to be more than %d\n", kMaxDTI4D, kMaxImageType * num2DExpected);
2197 printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n",
2198 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
2199 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels);
2200 free(cols);
2201 return d;
2202 }
2203 // dti4D->S[vol].V[0] = cols[kbval];
2204 //dti4D->gradDynVol[vol] = gradDynVol;
2205 dti4D->TE[vol] = cols[kTEcho];
2206 if (isSameFloatGE(cols[kTEcho], 0))
2207 dti4D->TE[vol] = TE; //kludge for cols[kImageType]==18 where TE set as 0
2208 else
2209 TE = cols[kTEcho];
2210 dti4D->triggerDelayTime[vol] = cols[kTriggerTime];
2211 if (dti4D->TE[vol] < 0)
2212 dti4D->TE[vol] = 0; //used to detect sparse volumes
2213 //dti4D->intenIntercept[vol] = cols[kRI];
2214 //dti4D->intenScale[vol] = cols[kRS];
2215 //dti4D->intenScalePhilips[vol] = cols[kSS];
2216 dti4D->isReal[vol] = isReal;
2217 dti4D->isImaginary[vol] = isImaginary;
2218 dti4D->isPhase[vol] = isPhase;
2219 if ((maxNumberOfGradientOrients > 1) && (parVers > 40)) {
2220 dti4D->S[vol].V[0] = cols[kbval];
2221 dti4D->S[vol].V[1] = cols[kv1];
2222 dti4D->S[vol].V[2] = cols[kv2];
2223 dti4D->S[vol].V[3] = cols[kv3];
2224 if ((vol + 1) > d.CSA.numDti)
2225 d.CSA.numDti = vol + 1;
2226 }
2227 if (numSlice2D < kMaxDTI4D) { //issue 363: intensity can vary with each 2D slice of 4D volume
2228 dti4D->intenIntercept[numSlice2D] = cols[kRI];
2229 dti4D->intenScale[numSlice2D] = cols[kRS];
2230 dti4D->intenScalePhilips[numSlice2D] = cols[kSS];
2231 }
2232 //if (slice == 1) printWarning("%d\n", (int)cols[kEcho]);
2233 slice = slice + (vol * d.xyzDim[3]);
2234 //offset images by type: mag+0,real+1, imag+2,phase+3
2235 //if (cols[kImageType] != 0) //yikes - phase maps!
2236 // slice = slice + numExpected;
2237 //printWarning("%d\t%d\n", slice -1, numSlice2D);
2238 if ((slice >= 0) && (slice < kMaxSlice2D) && (numSlice2D < kMaxSlice2D) && (numSlice2D >= 0)) {
2239 dti4D->sliceOrder[slice - 1] = numSlice2D;
2240 //printMessage("%d\t%d\t%d\n", numSlice2D, slice, (int)cols[kSlice],(int)vol);
2241 }
2242 numSlice2D++;
2243 }
2244 //printMessage("%f %f %lu\n",cols[9],cols[kGradientNumber], strlen(buff))
2245 p = fgets(buff, LINESZ, fp); //get next line
2246 }
2247 free(cols);
2248 fclose(fp);
2249 if ((parVers <= 0) || (numSlice2D < 1)) {
2250 printError("Invalid PAR format header (unable to detect version or slices) %s\n", parname);
2251 return d;
2252 }
2253 if (numSlice2D > kMaxSlice2D) { //check again after reading, as top portion of header does not report image types or isotropics
2254 printError("Increase kMaxSlice2D from %d to at least %d (or use dicm2nii).\n", kMaxSlice2D, numSlice2D);
2255 return d;
2256 }
2257 if (numSlice2D > kMaxDTI4D) { //since issue460, kMaxSlice2D == kMaxSlice4D, so we should never get here
2258 printError("Increase kMaxDTI4D from %d to at least %d (or use dicm2nii).\n", kMaxDTI4D, numSlice2D);
2259 return d;
2260 }
2261 d.manufacturer = kMANUFACTURER_PHILIPS;
2262 d.isValid = true;
2263 d.isSigned = true;
2264 //remove unused volumes - this will happen if unless we have all 4 image types: real, imag, mag, phase
2265 maxVol = 0;
2266 for (int i = 0; i < kMaxDTI4D; i++) {
2267 if (dti4D->TE[i] > -1.0) {
2268 dti4D->TE[maxVol] = dti4D->TE[i];
2269 dti4D->triggerDelayTime[maxVol] = dti4D->triggerDelayTime[i];
2270 //dti4D->intenIntercept[maxVol] = dti4D->intenIntercept[i];
2271 //dti4D->intenScale[maxVol] = dti4D->intenScale[i];
2272 //dti4D->intenScalePhilips[maxVol] = dti4D->intenScalePhilips[i];
2273 dti4D->isReal[maxVol] = dti4D->isReal[i];
2274 dti4D->isImaginary[maxVol] = dti4D->isImaginary[i];
2275 dti4D->isPhase[maxVol] = dti4D->isPhase[i];
2276 dti4D->S[maxVol].V[0] = dti4D->S[i].V[0];
2277 dti4D->S[maxVol].V[1] = dti4D->S[i].V[1];
2278 dti4D->S[maxVol].V[2] = dti4D->S[i].V[2];
2279 dti4D->S[maxVol].V[3] = dti4D->S[i].V[3];
2280 maxVol = maxVol + 1;
2281 }
2282 }
2283 if (d.CSA.numDti > 0)
2284 d.CSA.numDti = maxVol; //e.g. gradient 2 can skip B=0 but include isotropic
2285 //remove unused slices - this will happen if unless we have all 4 image types: real, imag, mag, phase
2286 int slice = 0;
2287 for (int i = 0; i < kMaxSlice2D; i++) {
2288 if (dti4D->sliceOrder[i] > -1) { //this slice was populated
2289 dti4D->sliceOrder[slice] = dti4D->sliceOrder[i];
2290 slice = slice + 1;
2291 }
2292 }
2293 if (slice != numSlice2D) {
2294 printError("Catastrophic error: found %d but expected %d slices. %s\n", slice, numSlice2D, parname);
2295 printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n",
2296 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
2297 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels);
2298 d.isValid = false;
2299 }
2300 for (int i = 0; i < numSlice2D; i++) { //issue363
2301 if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0])
2302 d.isScaleVariesEnh = true;
2303 if (dti4D->intenScale[i] != dti4D->intenScale[0])
2304 d.isScaleVariesEnh = true;
2305 if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0])
2306 d.isScaleVariesEnh = true;
2307 //printf("%g --> %g\n", dti4D->intenIntercept[i], dti4D->intenScale[i]);
2308 }
2309 if (d.isScaleVariesEnh) { //juggle to sorted order, required for subsequent rescaling
2310 printWarning("PAR/REC intensity scaling varies between slices (please validate output).\n");
2311 TDTI4D tmp;
2312 for (int i = 0; i < numSlice2D; i++) { //issue363
2313 tmp.intenIntercept[i] = dti4D->intenIntercept[i];
2314 tmp.intenScale[i] = dti4D->intenScale[i];
2315 tmp.intenScalePhilips[i] = dti4D->intenScalePhilips[i];
2316 }
2317 for (int i = 0; i < numSlice2D; i++) {
2318 int j = dti4D->sliceOrder[i];
2319 dti4D->intenIntercept[i] = tmp.intenIntercept[j];
2320 dti4D->intenScale[i] = tmp.intenScale[j];
2321 dti4D->intenScalePhilips[i] = tmp.intenScalePhilips[j];
2322 }
2323 }
2324 d.isScaleOrTEVaries = true;
2325 if (numSlice2D > kMaxSlice2D) {
2326 printError("Overloaded slice re-ordering. Number of slices (%d) exceeds kMaxSlice2D (%d)\n", numSlice2D, kMaxSlice2D);
2327 dti4D->sliceOrder[0] = -1;
2328 dti4D->intenScale[0] = 0.0;
2329 }
2330 if ((maxSlice - minSlice + 1) != d.xyzDim[3]) {
2331 int numSlice = (maxSlice - minSlice) + 1;
2332 printWarning("Expected %d slices, but found %d (%d..%d). %s\n", d.xyzDim[3], numSlice, minSlice, maxSlice, parname);
2333 if (numSlice <= 0)
2334 d.isValid = false;
2335 d.xyzDim[3] = numSlice;
2336 num2DExpected = d.xyzDim[3] * num3DExpected;
2337 }
2338 if ((maxBValue <= 0.0f) && (maxDyn > minDyn) && (maxDynTime > minDynTime)) { //use max vs min Dyn instead of && (d.CSA.numDti > 1)
2339 int numDyn = (maxDyn - minDyn) + 1;
2340 if (numDyn != maxNumberOfDynamics) {
2341 printWarning("Expected %d dynamics, but found %d (%d..%d).\n", maxNumberOfDynamics, numDyn, minDyn, maxDyn);
2342 maxNumberOfDynamics = numDyn;
2343 num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes;
2344 num2DExpected = d.xyzDim[3] * num3DExpected;
2345 }
2346 float TRms = 1000.0f * (maxDynTime - minDynTime) / (float)(numDyn - 1); //-1 for fence post
2347 if (fabs(TRms - d.TR) > 0.005f)
2348 printWarning("Reported TR=%gms, measured TR=%gms (prospect. motion corr.?)\n", d.TR, TRms);
2349 d.TR = TRms;
2350 }
2351 if ((isTypeWarning) && ((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0)) {
2352 num2DExpected = numSlice2D;
2353 }
2354 if (((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0)) {
2355 num2DExpected = d.xyzDim[3] * (int)(numSlice2D / d.xyzDim[3]);
2356 if (!ADCwarning)
2357 printWarning("More volumes than described in header (ADC or isotropic?)\n");
2358 }
2359 if ((numSlice2D % num2DExpected) != 0) {
2360 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,
2361 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
2362 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels, parname);
2363 d.isValid = false;
2364 }
2365 if (dynNotAscending) {
2366 printWarning("PAR file volumes not saved in ascending temporal order (please check re-ordering)\n");
2367 }
2368 if ((slice % d.xyzDim[3]) != 0) {
2369 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);
2370 d.isValid = false;
2371 return d;
2372 }
2373 d.xyzDim[4] = slice / d.xyzDim[3];
2374 d.locationsInAcquisition = d.xyzDim[3];
2375 if (ADCwarning)
2376 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");
2377 if (isIntenScaleVaries)
2378 printWarning("Intensity slope/intercept varies between slices! [check resulting images]\n");
2379 if ((isVerbose) && (d.isValid)) {
2380 printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n",
2381 d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues,
2382 maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels);
2383 }
2384 if ((d.xyzDim[3] > 1) && (minSlice == 1) && (maxSlice > minSlice)) { //issue 273
2385 float dx[4];
2386 dx[1] = (d.patientPosition[1] - d.patientPositionLast[1]);
2387 dx[2] = (d.patientPosition[2] - d.patientPositionLast[2]);
2388 dx[3] = (d.patientPosition[3] - d.patientPositionLast[3]);
2389 //compute error using 3D pythagorean theorm
2390 float sliceMM = sqrt(pow(dx[1], 2) + pow(dx[2], 2) + pow(dx[3], 2));
2391 sliceMM = sliceMM / (maxSlice - minSlice);
2392 if (!(isSameFloatGE(sliceMM, d.xyzMM[3]))) {
2393 //if (d.xyzMM[3] > 0.0)
2394 printWarning("Distance between slices reported by slice gap+thick does not match estimate from slice positions (issue 273).\n");
2395 d.xyzMM[3] = sliceMM;
2396 }
2397 } //issue 273
2398 printMessage("Done reading PAR header version %.1f, with %d slices\n", (float)parVers / 10, numSlice2D);
2399 //see Xiangrui Li 's dicm2nii (also BSD license)
2400 // http://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter
2401 // Rotation order and signs are figured out by trial and error, not 100% sure
2402 float d2r = (float)(M_PI / 180.0);
2403 vec3 ca = setVec3(cos(d.angulation[1] * d2r), cos(d.angulation[2] * d2r), cos(d.angulation[3] * d2r));
2404 vec3 sa = setVec3(sin(d.angulation[1] * d2r), sin(d.angulation[2] * d2r), sin(d.angulation[3] * d2r));
2405 mat33 rx, ry, rz;
2406 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]);
2407 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]);
2408 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);
2409 mat33 R = nifti_mat33_mul(rx, ry);
2410 R = nifti_mat33_mul(R, rz);
2411 ivec3 ixyz = setiVec3(1, 2, 3);
2412 if (d.sliceOrient == kSliceOrientSag) {
2413 ixyz = setiVec3(2, 3, 1);
2414 for (int r = 0; r < 3; r++)
2415 for (int c = 0; c < 3; c++)
2416 if (c != 1)
2417 R.m[r][c] = -R.m[r][c]; //invert first and final columns
2418 } else if (d.sliceOrient == kSliceOrientCor) {
2419 ixyz = setiVec3(1, 3, 2);
2420 for (int r = 0; r < 3; r++)
2421 R.m[r][2] = -R.m[r][2]; //invert rows of final column
2422 }
2423 R = nifti_mat33_reorder_cols(R, ixyz); //dicom rotation matrix
2424 d.orient[1] = R.m[0][0];
2425 d.orient[2] = R.m[1][0];
2426 d.orient[3] = R.m[2][0];
2427 d.orient[4] = R.m[0][1];
2428 d.orient[5] = R.m[1][1];
2429 d.orient[6] = R.m[2][1];
2430 mat33 diag;
2431 LOAD_MAT33(diag, d.xyzMM[1], 0.0f, 0.0f, 0.0f, d.xyzMM[2], 0.0f, 0.0f, 0.0f, d.xyzMM[3]);
2432 R = nifti_mat33_mul(R, diag);
2433 mat44 R44;
2434 LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.stackOffcentre[1],
2435 R.m[1][0], R.m[1][1], R.m[1][2], d.stackOffcentre[2],
2436 R.m[2][0], R.m[2][1], R.m[2][2], d.stackOffcentre[3]);
2437 vec3 x;
2438 if (parVers > 40) //guess
2439 x = setVec3(((float)d.xyzDim[1] - 1) / 2, ((float)d.xyzDim[2] - 1) / 2, ((float)d.xyzDim[3] - 1) / 2);
2440 else
2441 x = setVec3((float)d.xyzDim[1] / 2, (float)d.xyzDim[2] / 2, ((float)d.xyzDim[3] - 1) / 2);
2442 mat44 eye;
2443 LOAD_MAT44(eye, 1.0f, 0.0f, 0.0f, x.v[0],
2444 0.0f, 1.0f, 0.0f, x.v[1],
2445 0.0f, 0.0f, 1.0f, x.v[2]);
2446 eye = nifti_mat44_inverse(eye); //we wish to compute R/eye, so compute invEye and calculate R*invEye
2447 R44 = nifti_mat44_mul(R44, eye);
2448 vec4 y;
2449 y.v[0] = 0.0f;
2450 y.v[1] = 0.0f;
2451 y.v[2] = (float)d.xyzDim[3] - 1.0f;
2452 y.v[3] = 1.0f;
2453 y = nifti_vect44mat44_mul(y, R44);
2454 int iOri = 2; //for axial, slices are 3rd dimenson (indexed from 0) (k)
2455 if (d.sliceOrient == kSliceOrientSag)
2456 iOri = 0; //for sagittal, slices are 1st dimension (i)
2457 if (d.sliceOrient == kSliceOrientCor)
2458 iOri = 1; //for coronal, slices are 2nd dimension (j)
2459 if (d.xyzDim[3] > 1) { //detect and fix Philips Bug
2460 //Est: assuming "image offcentre (ap,fh,rl in mm )" is correct
2461 float stackOffcentreEst[4];
2462 stackOffcentreEst[1] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5;
2463 stackOffcentreEst[2] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5;
2464 stackOffcentreEst[3] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5;
2465 //compute error using 3D pythagorean theorm
2466 stackOffcentreEst[0] = sqrt(pow(stackOffcentreEst[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreEst[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreEst[3] - d.stackOffcentre[3], 2));
2467 //Est: assuming "image offcentre (ap,fh,rl in mm )" is stored in order rl,ap,fh
2468 float stackOffcentreRev[4];
2469 stackOffcentreRev[1] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5;
2470 stackOffcentreRev[2] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5;
2471 stackOffcentreRev[3] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5;
2472 //compute error using 3D pythagorean theorm
2473 stackOffcentreRev[0] = sqrt(pow(stackOffcentreRev[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreRev[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreRev[3] - d.stackOffcentre[3], 2));
2474 //detect, report and fix error
2475 if ((stackOffcentreEst[0] > 1.0) && (stackOffcentreRev[0] < stackOffcentreEst[0])) {
2476 //error detected: the ">1.0" handles the low precision of the "Off Centre" values
2477 printMessage("Order of 'image offcentre (ap,fh,rl in mm )' appears incorrect (assuming rl,ap,fh)\n");
2478 printMessage(" err[ap,fh,rl]= %g (%g %g %g) \n", stackOffcentreEst[0], stackOffcentreEst[1], stackOffcentreEst[2], stackOffcentreEst[3]);
2479 printMessage(" err[rl,ap,fh]= %g (%g %g %g) \n", stackOffcentreRev[0], stackOffcentreRev[1], stackOffcentreRev[2], stackOffcentreRev[3]);
2480 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,
2481 d.patientPosition[1], d.patientPosition[2], d.patientPosition[3],
2482 d.stackOffcentre[1], d.stackOffcentre[2], d.stackOffcentre[3],
2483 d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3], (d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]), parname);
2484 //correct patientPosition
2485 for (int i = 1; i < 4; i++)
2486 stackOffcentreRev[i] = d.patientPosition[i];
2487 d.patientPosition[1] = stackOffcentreRev[2];
2488 d.patientPosition[2] = stackOffcentreRev[3];
2489 d.patientPosition[3] = stackOffcentreRev[1];
2490 //correct patientPositionLast
2491 for (int i = 1; i < 4; i++)
2492 stackOffcentreRev[i] = d.patientPositionLast[i];
2493 d.patientPositionLast[1] = stackOffcentreRev[2];
2494 d.patientPositionLast[2] = stackOffcentreRev[3];
2495 d.patientPositionLast[3] = stackOffcentreRev[1];
2496 } //if bug: report and fix
2497 } //if 3D data
2498 bool flip = false;
2499 //assume head first supine
2500 if ((iOri == 0) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) > 0)))
2501 flip = true; //6/2018 : TODO, not sure if this is >= or >
2502 if ((iOri == 1) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0)))
2503 flip = true; //<= not <, leslie_dti_6_1.PAR
2504 if ((iOri == 2) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0)))
2505 flip = true; //<= not <, see leslie_dti_3_1.PAR
2506 if (flip) {
2507 //if ((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) < 0) {
2508 //if (( (y.v[iOri]-R44.m[iOri][3])>0 ) == ( (y.v[iOri]-d.stackOffcentre[iOri+1])>0 ) ) {
2509 d.patientPosition[1] = R44.m[0][3];
2510 d.patientPosition[2] = R44.m[1][3];
2511 d.patientPosition[3] = R44.m[2][3];
2512 d.patientPositionLast[1] = y.v[0];
2513 d.patientPositionLast[2] = y.v[1];
2514 d.patientPositionLast[3] = y.v[2];
2515 //printWarning(" Flipping slice order: please verify %s\n", parname);
2516 } else {
2517 //printWarning(" NOT Flipping slice order: please verify %s\n", parname);
2518 d.patientPosition[1] = y.v[0];
2519 d.patientPosition[2] = y.v[1];
2520 d.patientPosition[3] = y.v[2];
2521 d.patientPositionLast[1] = R44.m[0][3];
2522 d.patientPositionLast[2] = R44.m[1][3];
2523 d.patientPositionLast[3] = R44.m[2][3];
2524 }
2525 //finish up
2526 changeExt(parname, "REC");
2527 #ifndef _MSC_VER //Linux is case sensitive, #include <unistd.h>
2528 if (access(parname, F_OK) != 0)
2529 changeExt(parname, "rec");
2530 #endif
2531 d.locationsInAcquisition = d.xyzDim[3];
2532 d.imageStart = 0;
2533 if (d.CSA.numDti >= kMaxDTI4D) {
2534 printError("Unable to convert DTI [increase kMaxDTI4D] found %d directions\n", d.CSA.numDti);
2535 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);
2536 d.CSA.numDti = 0;
2537 };
2538 //check if dimensions vary
2539 if (maxVol > 0) { //maxVol indexed from 0
2540 for (int i = 1; i <= maxVol; i++) {
2541 //if (dti4D->gradDynVol[i] > d.maxGradDynVol) d.maxGradDynVol = dti4D->gradDynVol[i];
2542 //issue363 slope/intercept can vary for each 2D slice, not only between 3D volumes in a 4D time series
2543 //if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleOrTEVaries = true;
2544 //if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleOrTEVaries = true;
2545 //if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleOrTEVaries = true;
2546 if (dti4D->isPhase[i] != dti4D->isPhase[0])
2547 d.isScaleOrTEVaries = true;
2548 if (dti4D->isReal[i] != dti4D->isReal[0])
2549 d.isScaleOrTEVaries = true;
2550 if (dti4D->isImaginary[i] != dti4D->isImaginary[0])
2551 d.isScaleOrTEVaries = true;
2552 if (dti4D->triggerDelayTime[i] != dti4D->triggerDelayTime[0])
2553 d.isScaleOrTEVaries = true;
2554 }
2555 //if (d.isScaleOrTEVaries)
2556 // printWarning("Varying dimensions (echoes, phase maps, intensity scaling) will require volumes to be saved separately (hint: you may prefer dicm2nii output)\n");
2557 }
2558 //if (d.CSA.numDti > 1)
2559 // for (int i = 0; i < d.CSA.numDti; i++)
2560 // 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]);
2561 //check DTI makes sense
2562 if (d.CSA.numDti > 1) {
2563 bool v1varies = false;
2564 bool v2varies = false;
2565 bool v3varies = false;
2566 for (int i = 1; i < d.CSA.numDti; i++) {
2567 if (dti4D->S[0].V[1] != dti4D->S[i].V[1])
2568 v1varies = true;
2569 if (dti4D->S[0].V[2] != dti4D->S[i].V[2])
2570 v2varies = true;
2571 if (dti4D->S[0].V[3] != dti4D->S[i].V[3])
2572 v3varies = true;
2573 }
2574 if ((!v1varies) || (!v2varies) || (!v3varies))
2575 printError("Bizarre b-vectors %s\n", parname);
2576 }
2577 if ((maxEcho > 1) || (maxCardiac > 1))
2578 printWarning("Multiple Echo (%d) or Cardiac (%d). Carefully inspect output\n", maxEcho, maxCardiac);
2579 if ((maxEcho > 1) || (maxCardiac > 1))
2580 d.isScaleOrTEVaries = true;
2581 return d;
2582 } //nii_readParRec()
2583
nii_SliceBytes(struct nifti_1_header hdr)2584 size_t nii_SliceBytes(struct nifti_1_header hdr) {
2585 //size of 2D slice
2586 size_t imgsz = hdr.bitpix / 8;
2587 for (int i = 1; i < 3; i++)
2588 if (hdr.dim[i] > 1)
2589 imgsz = imgsz * hdr.dim[i];
2590 return imgsz;
2591 } //nii_SliceBytes()
2592
nii_ImgBytes(struct nifti_1_header hdr)2593 size_t nii_ImgBytes(struct nifti_1_header hdr) {
2594 size_t imgsz = hdr.bitpix / 8;
2595 for (int i = 1; i < 8; i++)
2596 if (hdr.dim[i] > 1)
2597 imgsz = imgsz * hdr.dim[i];
2598 return imgsz;
2599 } //nii_ImgBytes()
2600
2601 //unsigned char * nii_demosaic(unsigned char* inImg, struct nifti_1_header *hdr, int nMosaicSlices, int ProtocolSliceNumber1) {
nii_demosaic(unsigned char * inImg,struct nifti_1_header * hdr,int nMosaicSlices,bool isUIH)2602 unsigned char *nii_demosaic(unsigned char *inImg, struct nifti_1_header *hdr, int nMosaicSlices, bool isUIH) {
2603 //demosaic http://nipy.org/nibabel/dicom/dicom_mosaic.html
2604 if (nMosaicSlices < 2)
2605 return inImg;
2606 //Byte inImg[ [img length] ];
2607 //[img getBytes:&inImg length:[img length]];
2608 int nCol = (int)ceil(sqrt((double)nMosaicSlices));
2609 int nRow = nCol;
2610 //n.b. Siemens store 20 images as 5x5 grid, UIH as 5rows, 4 Col https://github.com/rordenlab/dcm2niix/issues/225
2611 if (isUIH)
2612 nRow = ceil((float)nMosaicSlices / (float)nCol);
2613 //printf("%d = %dx%d\n", nMosaicSlices, nCol, nRow);
2614 int colBytes = hdr->dim[1] / nCol * hdr->bitpix / 8;
2615 int lineBytes = hdr->dim[1] * hdr->bitpix / 8;
2616 int rowBytes = hdr->dim[1] * hdr->dim[2] / nRow * hdr->bitpix / 8;
2617 int col = 0;
2618 int row = 0;
2619 int lOutPos = 0;
2620 hdr->dim[1] = hdr->dim[1] / nCol;
2621 hdr->dim[2] = hdr->dim[2] / nRow;
2622 hdr->dim[3] = nMosaicSlices;
2623 size_t imgsz = nii_ImgBytes(*hdr);
2624 unsigned char *outImg = (unsigned char *)malloc(imgsz);
2625 for (int m = 1; m <= nMosaicSlices; m++) {
2626 int lPos = (row * rowBytes) + (col * colBytes);
2627 for (int y = 0; y < hdr->dim[2]; y++) {
2628 memcpy(&outImg[lOutPos], &inImg[lPos], colBytes); // dest, src, bytes
2629 lPos += lineBytes;
2630 lOutPos += colBytes;
2631 }
2632 col++;
2633 if (col >= nCol) {
2634 row++;
2635 col = 0;
2636 } //start new column
2637 } //for m = each mosaic slice
2638 free(inImg);
2639 return outImg;
2640 } // nii_demosaic()
2641
nii_flipImgY(unsigned char * bImg,struct nifti_1_header * hdr)2642 unsigned char *nii_flipImgY(unsigned char *bImg, struct nifti_1_header *hdr) {
2643 //DICOM row order opposite from NIfTI
2644 int dim3to7 = 1;
2645 for (int i = 3; i < 8; i++)
2646 if (hdr->dim[i] > 1)
2647 dim3to7 = dim3to7 * hdr->dim[i];
2648 size_t lineBytes = hdr->dim[1] * hdr->bitpix / 8;
2649 if ((hdr->datatype == DT_RGB24) && (hdr->bitpix == 24) && (hdr->intent_code == NIFTI_INTENT_NONE)) {
2650 //we use the intent code to indicate planar vs triplet...
2651 lineBytes = hdr->dim[1];
2652 dim3to7 = dim3to7 * 3;
2653 } //rgb data saved planar (RRR..RGGGG..GBBB..B
2654 unsigned char *line = (unsigned char *)malloc(sizeof(unsigned char) * (lineBytes));
2655 size_t sliceBytes = hdr->dim[2] * lineBytes;
2656 int halfY = hdr->dim[2] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns
2657 for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice
2658 size_t slBottom = (size_t)sl * sliceBytes;
2659 size_t slTop = (((size_t)sl + 1) * sliceBytes) - lineBytes;
2660 for (int y = 0; y < halfY; y++) {
2661 //swap order of lines
2662 memcpy(line, &bImg[slBottom], lineBytes); //memcpy(&line, &bImg[slBottom], lineBytes);
2663 memcpy(&bImg[slBottom], &bImg[slTop], lineBytes);
2664 memcpy(&bImg[slTop], line, lineBytes); //tpx memcpy(&bImg[slTop], &line, lineBytes);
2665 slTop -= lineBytes;
2666 slBottom += lineBytes;
2667 } //for y
2668 } //for each slice
2669 free(line);
2670 return bImg;
2671 } // nii_flipImgY()
2672
nii_flipImgZ(unsigned char * bImg,struct nifti_1_header * hdr)2673 unsigned char *nii_flipImgZ(unsigned char *bImg, struct nifti_1_header *hdr) {
2674 //DICOM row order opposite from NIfTI
2675 int halfZ = hdr->dim[3] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns
2676 if (halfZ < 1)
2677 return bImg;
2678 int dim4to7 = 1;
2679 for (int i = 4; i < 8; i++)
2680 if (hdr->dim[i] > 1)
2681 dim4to7 = dim4to7 * hdr->dim[i];
2682 size_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8;
2683 size_t volBytes = sliceBytes * hdr->dim[3];
2684 unsigned char *slice = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes));
2685 for (int vol = 0; vol < dim4to7; vol++) { //for each 2D slice
2686 size_t slBottom = vol * volBytes;
2687 size_t slTop = ((vol + 1) * volBytes) - sliceBytes;
2688 for (int z = 0; z < halfZ; z++) {
2689 //swap order of lines
2690 memcpy(slice, &bImg[slBottom], sliceBytes); //TPX memcpy(&slice, &bImg[slBottom], sliceBytes);
2691 memcpy(&bImg[slBottom], &bImg[slTop], sliceBytes);
2692 memcpy(&bImg[slTop], slice, sliceBytes); //TPX
2693 slTop -= sliceBytes;
2694 slBottom += sliceBytes;
2695 } //for Z
2696 } //for each volume
2697 free(slice);
2698 return bImg;
2699 } // nii_flipImgZ()
2700
nii_flipZ(unsigned char * bImg,struct nifti_1_header * h)2701 unsigned char *nii_flipZ(unsigned char *bImg, struct nifti_1_header *h) {
2702 //flip slice order
2703 if (h->dim[3] < 2)
2704 return bImg;
2705 mat33 s;
2706 mat44 Q44;
2707 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],
2708 h->srow_z[0], h->srow_z[1], h->srow_z[2]);
2709 LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3],
2710 h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3],
2711 h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]);
2712 vec4 v = setVec4(0.0f, 0.0f, (float)h->dim[3] - 1.0f);
2713 v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin
2714 mat33 mFlipZ;
2715 LOAD_MAT33(mFlipZ, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f);
2716 s = nifti_mat33_mul(s, mFlipZ);
2717 LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0],
2718 s.m[1][0], s.m[1][1], s.m[1][2], v.v[1],
2719 s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]);
2720 //printMessage(" ----------> %f %f %f\n",v.v[0],v.v[1],v.v[2]);
2721 setQSForm(h, Q44, true);
2722 //printMessage("nii_flipImgY dims %dx%dx%d %d \n",h->dim[1],h->dim[2], dim3to7,h->bitpix/8);
2723 return nii_flipImgZ(bImg, h);
2724 } // nii_flipZ()
2725
nii_flipY(unsigned char * bImg,struct nifti_1_header * h)2726 unsigned char *nii_flipY(unsigned char *bImg, struct nifti_1_header *h) {
2727 mat33 s;
2728 mat44 Q44;
2729 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],
2730 h->srow_z[0], h->srow_z[1], h->srow_z[2]);
2731 LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3],
2732 h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3],
2733 h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]);
2734 vec4 v = setVec4(0, (float)h->dim[2] - 1, 0);
2735 v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin
2736 mat33 mFlipY;
2737 LOAD_MAT33(mFlipY, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
2738 s = nifti_mat33_mul(s, mFlipY);
2739 LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0],
2740 s.m[1][0], s.m[1][1], s.m[1][2], v.v[1],
2741 s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]);
2742 setQSForm(h, Q44, true);
2743 //printMessage("nii_flipImgY dims %dx%d %d \n",h->dim[1],h->dim[2], h->bitpix/8);
2744 return nii_flipImgY(bImg, h);
2745 } // nii_flipY()
2746
conv12bit16bit(unsigned char * img,struct nifti_1_header hdr)2747 void conv12bit16bit(unsigned char *img, struct nifti_1_header hdr) {
2748 //convert 12-bit allocated data to 16-bit
2749 // works for MR-MONO2-12-angio-an1 from http://www.barre.nom.fr/medical/samples/
2750 // looks wrong: this sample toggles between big and little endian stores
2751 printWarning("Support for images that allocate 12 bits is experimental\n");
2752 int nVox = (int)nii_ImgBytes(hdr) / (hdr.bitpix / 8);
2753 for (int i = (nVox - 1); i >= 0; i--) {
2754 int i16 = i * 2;
2755 int i12 = floor(i * 1.5);
2756 uint16_t val;
2757 if ((i % 2) != 1) {
2758 val = img[i12 + 1] + (img[i12 + 0] << 8);
2759 val = val >> 4;
2760 } else {
2761 val = img[i12 + 0] + (img[i12 + 1] << 8);
2762 }
2763 img[i16 + 0] = val & 0xFF;
2764 img[i16 + 1] = (val >> 8) & 0xFF;
2765 }
2766 } //conv12bit16bit()
2767
nii_loadImgCore(char * imgname,struct nifti_1_header hdr,int bitsAllocated,int imageStart32)2768 unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bitsAllocated, int imageStart32) {
2769 size_t imgsz = nii_ImgBytes(hdr);
2770 size_t imgszRead = imgsz;
2771 size_t imageStart = imageStart32;
2772 if (bitsAllocated == 12)
2773 imgszRead = round(imgsz * 0.75);
2774 FILE *file = fopen(imgname, "rb");
2775 if (!file) {
2776 printError("Unable to open '%s'\n", imgname);
2777 return NULL;
2778 }
2779 fseek(file, 0, SEEK_END);
2780 long fileLen = ftell(file);
2781 if (fileLen < (imgszRead + imageStart)) {
2782 //note hdr.vox_offset is a float: issue507
2783 //https://www.nitrc.org/forum/message.php?msg_id=27155
2784 printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu+%zu) \n", fileLen, imgszRead, imageStart);
2785 printWarning("File not large enough to store image data: %s\n", imgname);
2786 return NULL;
2787 }
2788 fseek(file, (long)imageStart, SEEK_SET);
2789 unsigned char *bImg = (unsigned char *)malloc(imgsz);
2790 //int i = 0;
2791 //while (bImg[i] == 0) i++;
2792 //printMessage("%d %d<\n",i,bImg[i]);
2793 size_t sz = fread(bImg, 1, imgszRead, file);
2794 fclose(file);
2795 if (sz < imgszRead) {
2796 printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname);
2797 return NULL;
2798 }
2799 if (bitsAllocated == 12)
2800 conv12bit16bit(bImg, hdr);
2801 return bImg;
2802 } //nii_loadImgCore()
2803
nii_planar2rgb(unsigned char * bImg,struct nifti_1_header * hdr,int isPlanar)2804 unsigned char *nii_planar2rgb(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar) {
2805 //DICOM data saved in triples RGBRGBRGB, NIfTI RGB saved in planes RRR..RGGG..GBBBB..B
2806 if (bImg == NULL)
2807 return NULL;
2808 if (hdr->datatype != DT_RGB24)
2809 return bImg;
2810 if (isPlanar == 0)
2811 return bImg;
2812 int dim3to7 = 1;
2813 for (int i = 3; i < 8; i++)
2814 if (hdr->dim[i] > 1)
2815 dim3to7 = dim3to7 * hdr->dim[i];
2816 int sliceBytes8 = hdr->dim[1] * hdr->dim[2];
2817 int sliceBytes24 = sliceBytes8 * 3;
2818 unsigned char *slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24));
2819 int sliceOffsetRGB = 0;
2820 int sliceOffsetR = 0;
2821 int sliceOffsetG = sliceOffsetR + sliceBytes8;
2822 int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8;
2823 //printMessage("planar->rgb %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7);
2824 int i = 0;
2825 for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice
2826 memcpy(slice24, &bImg[sliceOffsetRGB], sliceBytes24);
2827 for (int rgb = 0; rgb < sliceBytes8; rgb++) {
2828 bImg[i++] = slice24[sliceOffsetR + rgb];
2829 bImg[i++] = slice24[sliceOffsetG + rgb];
2830 bImg[i++] = slice24[sliceOffsetB + rgb];
2831 }
2832 sliceOffsetRGB += sliceBytes24;
2833 } //for each slice
2834 free(slice24);
2835 return bImg;
2836 } //nii_planar2rgb()
2837
nii_rgb2planar(unsigned char * bImg,struct nifti_1_header * hdr,int isPlanar)2838 unsigned char *nii_rgb2planar(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar) {
2839 //DICOM data saved in triples RGBRGBRGB, Analyze RGB saved in planes RRR..RGGG..GBBBB..B
2840 if (bImg == NULL)
2841 return NULL;
2842 if (hdr->datatype != DT_RGB24)
2843 return bImg;
2844 if (isPlanar == 1)
2845 return bImg; //return nii_bgr2rgb(bImg,hdr);
2846 int dim3to7 = 1;
2847 for (int i = 3; i < 8; i++)
2848 if (hdr->dim[i] > 1)
2849 dim3to7 = dim3to7 * hdr->dim[i];
2850 int sliceBytes8 = hdr->dim[1] * hdr->dim[2];
2851 int sliceBytes24 = sliceBytes8 * 3;
2852 unsigned char *slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24));
2853 //printMessage("rgb->planar %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7);
2854 int sliceOffsetR = 0;
2855 for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice
2856 memcpy(slice24, &bImg[sliceOffsetR], sliceBytes24); //TPX memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24);
2857 int sliceOffsetG = sliceOffsetR + sliceBytes8;
2858 int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8;
2859 int i = 0;
2860 int j = 0;
2861 for (int rgb = 0; rgb < sliceBytes8; rgb++) {
2862 bImg[sliceOffsetR + j] = slice24[i++];
2863 bImg[sliceOffsetG + j] = slice24[i++];
2864 bImg[sliceOffsetB + j] = slice24[i++];
2865 j++;
2866 }
2867 sliceOffsetR += sliceBytes24;
2868 } //for each slice
2869 free(slice24);
2870 return bImg;
2871 } //nii_rgb2Planar()
2872
nii_iVaries(unsigned char * img,struct nifti_1_header * hdr,struct TDTI4D * dti4D)2873 unsigned char *nii_iVaries(unsigned char *img, struct nifti_1_header *hdr, struct TDTI4D *dti4D) {
2874 //each DICOM image can have its own intensity scaling, whereas NIfTI requires the same scaling for all images in a file
2875 //WARNING: do this BEFORE nii_check16bitUnsigned!!!!
2876 //if (hdr->datatype != DT_INT16) return img;
2877 int dim3to7 = 1;
2878 for (int i = 3; i < 8; i++)
2879 if (hdr->dim[i] > 1)
2880 dim3to7 = dim3to7 * hdr->dim[i];
2881 int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7;
2882 if (nVox < 1)
2883 return img;
2884 float *img32 = (float *)malloc(nVox * sizeof(float));
2885 if (hdr->datatype == DT_UINT8) {
2886 uint8_t *img8i = (uint8_t *)img;
2887 for (int i = 0; i < nVox; i++)
2888 img32[i] = img8i[i];
2889 } else if (hdr->datatype == DT_UINT16) {
2890 uint16_t *img16ui = (uint16_t *)img;
2891 for (int i = 0; i < nVox; i++)
2892 img32[i] = img16ui[i];
2893 } else if (hdr->datatype == DT_INT16) {
2894 int16_t *img16i = (int16_t *)img;
2895 for (int i = 0; i < nVox; i++)
2896 img32[i] = img16i[i];
2897 } else if (hdr->datatype == DT_INT32) {
2898 int32_t *img32i = (int32_t *)img;
2899 for (int i = 0; i < nVox; i++)
2900 img32[i] = (float)img32i[i];
2901 }
2902 free(img); //release previous image
2903 if ((dti4D != NULL) && (dti4D->intenScale[0] != 0.0)) { //enhanced dataset, intensity varies across slices of a single file
2904 if (dti4D->RWVScale[0] != 0.0)
2905 printWarning("Intensity scale/slope using 0028,1053 and 0028,1052"); //to do: real-world values and precise values
2906 int dim1to2 = hdr->dim[1] * hdr->dim[2];
2907 int slice = -1;
2908 //(0028,1052) SS = scale slope (2005,100E) RealWorldIntercept = (0040,9224) Real World Slope = (0040,9225)
2909 //printf("vol\tRS(0028,1053)\tRI(0028,1052)\tSS(2005,100E)\trwS(0040,9225)\trwI(0040,9224)\n");
2910 for (int i = 0; i < nVox; i++) { //issue 363
2911 if ((i % dim1to2) == 0) {
2912 slice++;
2913 //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]);
2914 }
2915 img32[i] = (img32[i] * dti4D->intenScale[slice]) + dti4D->intenIntercept[slice];
2916 }
2917 } else { //
2918 for (int i = 0; i < nVox; i++)
2919 img32[i] = (img32[i] * hdr->scl_slope) + hdr->scl_inter;
2920 }
2921 hdr->scl_slope = 1;
2922 hdr->scl_inter = 0;
2923 hdr->datatype = DT_FLOAT;
2924 hdr->bitpix = 32;
2925 return (unsigned char *)img32;
2926 } //nii_iVaries()
2927
nii_reorderSlicesX(unsigned char * bImg,struct nifti_1_header * hdr,struct TDTI4D * dti4D)2928 unsigned char *nii_reorderSlicesX(unsigned char *bImg, struct nifti_1_header *hdr, struct TDTI4D *dti4D) {
2929 //Philips can save slices in any random order... rearrange all of them
2930 int dim3to7 = 1;
2931 for (int i = 3; i < 8; i++)
2932 if (hdr->dim[i] > 1)
2933 dim3to7 = dim3to7 * hdr->dim[i];
2934 if (dim3to7 < 2)
2935 return bImg;
2936 if (dim3to7 > kMaxSlice2D)
2937 return bImg;
2938 uint64_t imgSz = nii_ImgBytes(*hdr);
2939 uint64_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8;
2940 unsigned char *outImg = (unsigned char *)malloc(imgSz);
2941 memcpy(&outImg[0], &bImg[0], imgSz);
2942 for (int i = 0; i < dim3to7; i++) { //for each volume
2943 int fromSlice = dti4D->sliceOrder[i];
2944 //if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice);
2945 //printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", fromSlice, i);
2946 if ((i < 0) || (fromSlice >= dim3to7)) {
2947 printError("Re-ordered slice out-of-volume %d\n", fromSlice);
2948 } else if (i != fromSlice) {
2949 uint64_t inPos = fromSlice * sliceBytes;
2950 uint64_t outPos = i * sliceBytes;
2951 memcpy(&bImg[outPos], &outImg[inPos], sliceBytes);
2952 }
2953 }
2954 free(outImg);
2955 return bImg;
2956 }
2957
nii_byteswap(unsigned char * img,struct nifti_1_header * hdr)2958 unsigned char *nii_byteswap(unsigned char *img, struct nifti_1_header *hdr) {
2959 if (hdr->bitpix < 9)
2960 return img;
2961 uint64_t nvox = nii_ImgBytes(*hdr) / (hdr->bitpix / 8);
2962 void *ar = (void *)img;
2963 if (hdr->bitpix == 16)
2964 nifti_swap_2bytes(nvox, ar);
2965 if (hdr->bitpix == 32)
2966 nifti_swap_4bytes(nvox, ar);
2967 if (hdr->bitpix == 64)
2968 nifti_swap_8bytes(nvox, ar);
2969 return img;
2970 } //nii_byteswap()
2971
2972 #ifdef myEnableJasper
nii_loadImgCoreJasper(char * imgname,struct nifti_1_header hdr,struct TDICOMdata dcm,int compressFlag)2973 unsigned char *nii_loadImgCoreJasper(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) {
2974 if (jas_init()) {
2975 return NULL;
2976 }
2977 jas_stream_t *in;
2978 jas_image_t *image;
2979 jas_setdbglevel(0);
2980 if (!(in = jas_stream_fopen(imgname, "rb"))) {
2981 printError("Cannot open input image file %s\n", imgname);
2982 return NULL;
2983 }
2984 //int isSeekable = jas_stream_isseekable(in);
2985 jas_stream_seek(in, dcm.imageStart, 0);
2986 int infmt = jas_image_getfmt(in);
2987 if (infmt < 0) {
2988 printError("Input image has unknown format %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes);
2989 return NULL;
2990 }
2991 char opt[] = "\0";
2992 char *inopts = opt;
2993 if (!(image = jas_image_decode(in, infmt, inopts))) {
2994 printError("Cannot decode image data %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes);
2995 return NULL;
2996 }
2997 int numcmpts;
2998 int cmpts[4];
2999 switch (jas_clrspc_fam(jas_image_clrspc(image))) {
3000 case JAS_CLRSPC_FAM_RGB:
3001 if (jas_image_clrspc(image) != JAS_CLRSPC_SRGB)
3002 printWarning("Inaccurate color\n");
3003 numcmpts = 3;
3004 if ((cmpts[0] = jas_image_getcmptbytype(image,
3005 JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R))) < 0 ||
3006 (cmpts[1] = jas_image_getcmptbytype(image,
3007 JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G))) < 0 ||
3008 (cmpts[2] = jas_image_getcmptbytype(image,
3009 JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B))) < 0) {
3010 printError("Missing color component\n");
3011 return NULL;
3012 }
3013 break;
3014 case JAS_CLRSPC_FAM_GRAY:
3015 if (jas_image_clrspc(image) != JAS_CLRSPC_SGRAY)
3016 printWarning("Inaccurate color\n");
3017 numcmpts = 1;
3018 if ((cmpts[0] = jas_image_getcmptbytype(image,
3019 JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y))) < 0) {
3020 printError("Missing color component\n");
3021 return NULL;
3022 }
3023 break;
3024 default:
3025 printError("Unsupported color space\n");
3026 return NULL;
3027 break;
3028 }
3029 int width = jas_image_cmptwidth(image, cmpts[0]);
3030 int height = jas_image_cmptheight(image, cmpts[0]);
3031 int prec = jas_image_cmptprec(image, cmpts[0]);
3032 int sgnd = jas_image_cmptsgnd(image, cmpts[0]);
3033 #ifdef MY_DEBUG
3034 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());
3035 #endif
3036 for (int cmptno = 0; cmptno < numcmpts; ++cmptno) {
3037 if (jas_image_cmptwidth(image, cmpts[cmptno]) != width ||
3038 jas_image_cmptheight(image, cmpts[cmptno]) != height ||
3039 jas_image_cmptprec(image, cmpts[cmptno]) != prec ||
3040 jas_image_cmptsgnd(image, cmpts[cmptno]) != sgnd ||
3041 jas_image_cmpthstep(image, cmpts[cmptno]) != jas_image_cmpthstep(image, 0) ||
3042 jas_image_cmptvstep(image, cmpts[cmptno]) != jas_image_cmptvstep(image, 0) ||
3043 jas_image_cmpttlx(image, cmpts[cmptno]) != jas_image_cmpttlx(image, 0) ||
3044 jas_image_cmpttly(image, cmpts[cmptno]) != jas_image_cmpttly(image, 0)) {
3045 printMessage("The NIfTI format cannot be used to represent an image with this geometry.\n");
3046 return NULL;
3047 }
3048 }
3049 //extract the data
3050 int bpp = (prec + 7) >> 3; //e.g. 12 bits requires 2 bytes
3051 int imgbytes = bpp * width * height * numcmpts;
3052 if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) {
3053 printError("Catastrophic decompression error\n");
3054 return NULL;
3055 }
3056 jas_seqent_t v;
3057 unsigned char *img = (unsigned char *)malloc(imgbytes);
3058 uint16_t *img16ui = (uint16_t *)img; //unsigned 16-bit
3059 int16_t *img16i = (int16_t *)img; //signed 16-bit
3060 if (sgnd)
3061 bpp = -bpp;
3062 if (bpp == -1) {
3063 printError("Signed 8-bit DICOM?\n");
3064 return NULL;
3065 }
3066 jas_matrix_t *data;
3067 jas_seqent_t *d;
3068 data = 0;
3069 int cmptno, y, x;
3070 int pix = 0;
3071 for (cmptno = 0; cmptno < numcmpts; ++cmptno) {
3072 if (!(data = jas_matrix_create(1, width))) {
3073 free(img);
3074 return NULL;
3075 }
3076 }
3077 //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB
3078 for (cmptno = 0; cmptno < numcmpts; ++cmptno) {
3079 for (y = 0; y < height; ++y) {
3080 if (jas_image_readcmpt(image, cmpts[cmptno], 0, y, width, 1, data)) {
3081 free(img);
3082 return NULL;
3083 }
3084 d = jas_matrix_getref(data, 0, 0);
3085 for (x = 0; x < width; ++x) {
3086 v = *d;
3087 switch (bpp) {
3088 case 1:
3089 img[pix] = v;
3090 break;
3091 case 2:
3092 img16ui[pix] = v;
3093 break;
3094 case -2:
3095 img16i[pix] = v;
3096 break;
3097 }
3098 pix++;
3099 ++d;
3100 } //for x
3101 } //for y
3102 } //for each component
3103 jas_matrix_destroy(data);
3104 jas_image_destroy(image);
3105 jas_image_clearfmts();
3106 return img;
3107 } //nii_loadImgCoreJasper()
3108 #endif
3109
3110 struct TJPEG {
3111 long offset;
3112 long size;
3113 };
3114
decode_JPEG_SOF_0XC3_stack(const char * fn,int skipBytes,int isVerbose,int frames,bool isLittleEndian)3115 TJPEG *decode_JPEG_SOF_0XC3_stack(const char *fn, int skipBytes, int isVerbose, int frames, bool isLittleEndian) {
3116 #define abortGoto() free(lOffsetRA); return NULL;
3117 TJPEG *lOffsetRA = (TJPEG *)malloc(frames * sizeof(TJPEG));
3118 FILE *reader = fopen(fn, "rb");
3119 fseek(reader, 0, SEEK_END);
3120 long lRawSz = ftell(reader) - skipBytes;
3121 if (lRawSz <= 8) {
3122 printError("Unable to open %s\n", fn);
3123 abortGoto(); //read failure
3124 }
3125 fseek(reader, skipBytes, SEEK_SET);
3126 unsigned char *lRawRA = (unsigned char *)malloc(lRawSz);
3127 size_t lSz = fread(lRawRA, 1, lRawSz, reader);
3128 fclose(reader);
3129 if (lSz < (size_t)lRawSz) {
3130 printError("Unable to read %s\n", fn);
3131 abortGoto(); //read failure
3132 }
3133 long lRawPos = 0; //starting position
3134 int frame = 0;
3135 while ((frame < frames) && ((lRawPos + 10) < lRawSz)) {
3136 int tag = dcmInt(4, &lRawRA[lRawPos], isLittleEndian);
3137 lRawPos += 4; //read tag
3138 int tagLength = dcmInt(4, &lRawRA[lRawPos], isLittleEndian);
3139 long tagEnd = lRawPos + tagLength + 4;
3140 if (isVerbose)
3141 printMessage("Frame %d Tag %#x length %d end at %ld\n", frame + 1, tag, tagLength, tagEnd + skipBytes);
3142 lRawPos += 4; //read tag length
3143 if ((lRawRA[lRawPos] != 0xFF) || (lRawRA[lRawPos + 1] != 0xD8) || (lRawRA[lRawPos + 2] != 0xFF)) {
3144 if (isVerbose)
3145 printWarning("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn);
3146 } else {
3147 lOffsetRA[frame].offset = lRawPos + skipBytes;
3148 lOffsetRA[frame].size = tagLength;
3149 frame++;
3150 }
3151 lRawPos = tagEnd;
3152 }
3153 free(lRawRA);
3154 if (frame < frames) {
3155 printMessage("Only found %d of %d JPEG fragments. Please use dcmdjpeg or gdcmconv to uncompress data.\n", frame, frames);
3156 abortGoto();
3157 }
3158 return lOffsetRA;
3159 }
3160
nii_loadImgJPEGC3(char * imgname,struct nifti_1_header hdr,struct TDICOMdata dcm,int isVerbose)3161 unsigned char *nii_loadImgJPEGC3(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int isVerbose) {
3162 //arcane and inefficient lossless compression method popularized by dcmcjpeg, examples at http://www.osirix-viewer.com/resources/dicom-image-library/
3163 int dimX, dimY, bits, frames;
3164 //clock_t start = clock();
3165 // https://github.com/rii-mango/JPEGLosslessDecoderJS/blob/master/tests/data/jpeg_lossless_sel1-8bit.dcm
3166 //N.B. this current code can not extract a 2D image that is saved as multiple fragments, for example see the JPLL files at
3167 // ftp://medical.nema.org/MEDICAL/Dicom/DataSets/WG04/
3168 //Live javascript code that can handle these is at
3169 // https://github.com/chafey/cornerstoneWADOImageLoader
3170 //I have never seen these segmented images in the wild, so we will simply warn the user if we encounter such a file
3171 //int Sz = JPEG_SOF_0XC3_sz (imgname, (dcm.imageStart - 4), dcm.isLittleEndian);
3172 //printMessage("Sz %d %d\n", Sz, dcm.imageBytes );
3173 //This behavior is legal but appears extremely rare
3174 //ftp://medical.nema.org/medical/dicom/final/cp900_ft.pdf
3175 if (65536 == dcm.imageBytes)
3176 printError("One frame may span multiple fragments. SOFxC3 lossless JPEG. Please extract with dcmdjpeg or gdcmconv.\n");
3177 unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, dcm.imageStart, isVerbose, &dimX, &dimY, &bits, &frames, 0);
3178 if (ret == NULL) {
3179 printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n");
3180 return NULL;
3181 }
3182 if (hdr.dim[3] != frames) { //multi-slice image saved as multiple image fragments rather than a single image
3183 //printMessage("Unable to decode all slices (%d/%d). Please use dcmdjpeg to uncompress data.\n", frames, hdr.dim[3]);
3184 if (ret != NULL)
3185 free(ret);
3186 TJPEG *offsetRA = decode_JPEG_SOF_0XC3_stack(imgname, dcm.imageStart - 8, isVerbose, hdr.dim[3], dcm.isLittleEndian);
3187 if (offsetRA == NULL)
3188 return NULL;
3189 size_t slicesz = nii_SliceBytes(hdr);
3190 size_t imgsz = slicesz * hdr.dim[3];
3191 size_t pos = 0;
3192 unsigned char *bImg = (unsigned char *)malloc(imgsz);
3193 for (int frame = 0; frame < hdr.dim[3]; frame++) {
3194 if (isVerbose)
3195 printMessage("JPEG frame %d has %ld bytes @ %ld\n", frame, offsetRA[frame].size, offsetRA[frame].offset);
3196 unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, (int)offsetRA[frame].offset, false, &dimX, &dimY, &bits, &frames, (int)offsetRA[frame].size);
3197 if (ret == NULL) {
3198 printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n");
3199 free(bImg);
3200 return NULL;
3201 }
3202 memcpy(&bImg[pos], ret, slicesz); //dest, src, size
3203 free(ret);
3204 pos += slicesz;
3205 }
3206 free(offsetRA);
3207 return bImg;
3208 }
3209 return ret;
3210 }
3211
3212 #ifndef F_OK
3213 #define F_OK 0 /* existence check */
3214 #endif
3215
3216 #ifndef myDisableClassicJPEG
3217
3218 #ifdef myTurboJPEG //if turboJPEG instead of nanoJPEG for classic JPEG decompression
3219
3220 //unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
nii_loadImgJPEG50(char * imgname,struct TDICOMdata dcm)3221 unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) {
3222 //decode classic JPEG using nanoJPEG
3223 //printMessage("50 offset %d\n", dcm.imageStart);
3224 if ((dcm.samplesPerPixel != 1) && (dcm.samplesPerPixel != 3)) {
3225 printError("%d components (expected 1 or 3) in a JPEG image '%s'\n", dcm.samplesPerPixel, imgname);
3226 return NULL;
3227 }
3228 if (access(imgname, F_OK) == -1) {
3229 printError("Unable to find '%s'\n", imgname);
3230 return NULL;
3231 }
3232 //load compressed data
3233 FILE *f = fopen(imgname, "rb");
3234 fseek(f, 0, SEEK_END);
3235 long unsigned int _jpegSize = (long unsigned int)ftell(f);
3236 _jpegSize = _jpegSize - dcm.imageStart;
3237 if (_jpegSize < 8) {
3238 printError("File too small\n");
3239 fclose(f);
3240 return NULL;
3241 }
3242 unsigned char *_compressedImage = (unsigned char *)malloc(_jpegSize);
3243 fseek(f, dcm.imageStart, SEEK_SET);
3244 _jpegSize = (long unsigned int)fread(_compressedImage, 1, _jpegSize, f);
3245 fclose(f);
3246 int jpegSubsamp, width, height;
3247 //printMessage("Decoding with turboJPEG\n");
3248 tjhandle _jpegDecompressor = tjInitDecompress();
3249 tjDecompressHeader2(_jpegDecompressor, _compressedImage, _jpegSize, &width, &height, &jpegSubsamp);
3250 int COLOR_COMPONENTS = dcm.samplesPerPixel;
3251 //printMessage("turboJPEG h*w %d*%d sampling %d components %d\n", width, height, jpegSubsamp, COLOR_COMPONENTS);
3252 if ((jpegSubsamp == TJSAMP_GRAY) && (COLOR_COMPONENTS != 1)) {
3253 printError("Grayscale jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname);
3254 }
3255 if ((jpegSubsamp != TJSAMP_GRAY) && (COLOR_COMPONENTS != 3)) {
3256 printError("Color jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname);
3257 }
3258 //unsigned char bImg[width*height*COLOR_COMPONENTS]; //!< will contain the decompressed image
3259 unsigned char *bImg = (unsigned char *)malloc(width * height * COLOR_COMPONENTS);
3260 if (COLOR_COMPONENTS == 1) //TJPF_GRAY
3261 tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_GRAY, TJFLAG_FASTDCT);
3262 else
3263 tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_RGB, TJFLAG_FASTDCT);
3264 //printMessage("turboJPEG h*w %d*%d (sampling %d)\n", width, height, jpegSubsamp);
3265 tjDestroy(_jpegDecompressor);
3266 return bImg;
3267 }
3268
3269 #else //if turboJPEG else use nanojpeg...
3270
3271 //unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
nii_loadImgJPEG50(char * imgname,struct TDICOMdata dcm)3272 unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) {
3273 //decode classic JPEG using nanoJPEG
3274 //printMessage("50 offset %d\n", dcm.imageStart);
3275 if (access(imgname, F_OK) == -1) {
3276 printError("Unable to find '%s'\n", imgname);
3277 return NULL;
3278 }
3279 //load compressed data
3280 FILE *f = fopen(imgname, "rb");
3281 fseek(f, 0, SEEK_END);
3282 int size = (int)ftell(f);
3283 size = size - dcm.imageStart;
3284 if (size < 8) {
3285 printError("File too small '%s'\n", imgname);
3286 fclose(f);
3287 return NULL;
3288 }
3289 char *buf = (char *)malloc(size);
3290 fseek(f, dcm.imageStart, SEEK_SET);
3291 size = (int)fread(buf, 1, size, f);
3292 fclose(f);
3293 //decode
3294 njInit();
3295 if (njDecode(buf, size)) {
3296 printError("Unable to decode JPEG image.\n");
3297 return NULL;
3298 }
3299 free(buf);
3300 unsigned char *bImg = (unsigned char *)malloc(njGetImageSize());
3301 memcpy(bImg, njGetImage(), njGetImageSize()); //dest, src, size
3302 njDone();
3303 return bImg;
3304 }
3305 #endif
3306 #endif
3307
rleInt(int lIndex,unsigned char lBuffer[],bool swap)3308 uint32_t rleInt(int lIndex, unsigned char lBuffer[], bool swap) { //read binary 32-bit integer
3309 uint32_t retVal = 0;
3310 memcpy(&retVal, (char *)&lBuffer[lIndex * 4], 4);
3311 if (!swap)
3312 return retVal;
3313 uint32_t swapVal;
3314 char *inInt = (char *)&retVal;
3315 char *outInt = (char *)&swapVal;
3316 outInt[0] = inInt[3];
3317 outInt[1] = inInt[2];
3318 outInt[2] = inInt[1];
3319 outInt[3] = inInt[0];
3320 return swapVal;
3321 } //rleInt()
3322
nii_loadImgPMSCT_RLE1(char * imgname,struct nifti_1_header hdr,struct TDICOMdata dcm)3323 unsigned char *nii_loadImgPMSCT_RLE1(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
3324 //Transfer Syntax 1.3.46.670589.33.1.4.1 also handled by TomoVision and GDCM's rle2img
3325 //https://github.com/malaterre/GDCM/blob/a923f206060e85e8d81add565ae1b9dd7b210481/Examples/Cxx/rle2img.cxx
3326 //see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1
3327 if (dcm.imageBytes < 66) { //64 for header+ 2 byte minimum image
3328 printError("%d is not enough bytes for PMSCT_RLE1 compression '%s'\n", dcm.imageBytes, imgname);
3329 return NULL;
3330 }
3331 int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8);
3332 if (bytesPerSample != 2) { //there is an RGB variation of this format, but we have not seen it in the wild
3333 printError("PMSCT_RLE1 should be 16-bits per sample (please report on Github and use pmsct_rgb1).\n");
3334 return NULL;
3335 }
3336 FILE *file = fopen(imgname, "rb");
3337 if (!file) {
3338 printError("Unable to open %s\n", imgname);
3339 return NULL;
3340 }
3341 fseek(file, 0, SEEK_END);
3342 long fileLen = ftell(file);
3343 if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) {
3344 printMessage("File not large enough to store image data: %s\n", imgname);
3345 fclose(file);
3346 return NULL;
3347 }
3348 fseek(file, (long)dcm.imageStart, SEEK_SET);
3349 size_t imgsz = nii_ImgBytes(hdr);
3350 char *cImg = (char *)malloc(dcm.imageBytes); //compressed input
3351 size_t sz = fread(cImg, 1, dcm.imageBytes, file);
3352 fclose(file);
3353 if (sz < (size_t)dcm.imageBytes) {
3354 printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname);
3355 free(cImg);
3356 return NULL;
3357 }
3358 if (imgsz == dcm.imageBytes) { // Handle special case that data is not compressed:
3359 return (unsigned char *)cImg;
3360 }
3361 unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output
3362 // RLE pass: compressed -> temp (bImg -> tImg)
3363 char *tImg = (char *)malloc(imgsz); //temp output
3364 int o = 0;
3365 for (size_t i = 0; i < dcm.imageBytes; ++i) {
3366 if (cImg[i] == (char)0xa5) {
3367 int repeat = (unsigned char)cImg[i + 1] + 1;
3368 char value = cImg[i + 2];
3369 while (repeat) {
3370 tImg[o] = value;
3371 o++;
3372 --repeat;
3373 }
3374 i += 2;
3375 } else {
3376 tImg[o] = cImg[i];
3377 o++;
3378 }
3379 } //for i
3380 free(cImg);
3381 int tempsize = o;
3382 //Looks like this RLE is pretty ineffective...
3383 // printMessage("RLE %d -> %d\n", dcm.imageBytes, o);
3384 //Delta encoding pass: temp -> output (tImg -> bImg)
3385 unsigned short delta = 0;
3386 o = 0;
3387 int n16 = (int)imgsz >> 1;
3388 unsigned short *bImg16 = (unsigned short *)bImg;
3389 for (size_t i = 0; i < tempsize; ++i) {
3390 if (tImg[i] == (unsigned char)0x5a) {
3391 unsigned char v1 = (unsigned char)tImg[i + 1];
3392 unsigned char v2 = (unsigned char)tImg[i + 2];
3393 unsigned short value = (unsigned short)(v2 * 256 + v1);
3394 if (o < n16)
3395 bImg16[o] = value;
3396 o++;
3397 delta = value;
3398 i += 2;
3399 } else {
3400 unsigned short value = (unsigned short)(tImg[i] + delta);
3401 if (o < n16)
3402 bImg16[o] = value;
3403 o++;
3404 delta = value;
3405 }
3406 } //for i
3407 //printMessage("Delta %d -> %d (of %d)\n", tempsize, 2*(o-1), imgsz);
3408 free(tImg);
3409 return bImg;
3410 } // nii_loadImgPMSCT_RLE1()
3411
nii_loadImgRLE(char * imgname,struct nifti_1_header hdr,struct TDICOMdata dcm)3412 unsigned char *nii_loadImgRLE(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
3413 //decompress PackBits run-length encoding https://en.wikipedia.org/wiki/PackBits
3414 if (dcm.imageBytes < 66) { //64 for header+ 2 byte minimum image
3415 printError("%d is not enough bytes for RLE compression '%s'\n", dcm.imageBytes, imgname);
3416 return NULL;
3417 }
3418 FILE *file = fopen(imgname, "rb");
3419 if (!file) {
3420 printError("Unable to open %s\n", imgname);
3421 return NULL;
3422 }
3423 fseek(file, 0, SEEK_END);
3424 long fileLen = ftell(file);
3425 if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) {
3426 printMessage("File not large enough to store image data: %s\n", imgname);
3427 fclose(file);
3428 return NULL;
3429 }
3430 fseek(file, (long)dcm.imageStart, SEEK_SET);
3431 size_t imgsz = nii_ImgBytes(hdr);
3432 unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input
3433 size_t sz = fread(cImg, 1, dcm.imageBytes, file);
3434 fclose(file);
3435 if (sz < (size_t)dcm.imageBytes) {
3436 printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname);
3437 free(cImg);
3438 return NULL;
3439 }
3440 //read header http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html
3441 bool swap = (dcm.isLittleEndian != littleEndianPlatform());
3442 int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8);
3443 uint32_t bytesPerSampleRLE = rleInt(0, cImg, swap);
3444 if ((bytesPerSample < 0) || (bytesPerSampleRLE != (uint32_t)bytesPerSample)) {
3445 printError("RLE header corrupted %d != %d\n", bytesPerSampleRLE, bytesPerSample);
3446 free(cImg);
3447 return NULL;
3448 }
3449 unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output
3450 for (size_t i = 0; i < imgsz; i++)
3451 bImg[i] = 0;
3452 for (int i = 0; i < bytesPerSample; i++) {
3453 uint32_t offset = rleInt(i + 1, cImg, swap);
3454 if ((dcm.imageBytes < 0) || (offset > (uint32_t)dcm.imageBytes)) {
3455 printError("RLE header error\n");
3456 free(cImg);
3457 free(bImg);
3458 return NULL;
3459 }
3460 //save in platform's endian:
3461 // The first Segment is generated by stripping off the most significant byte of each Padded Composite Pixel Code...
3462 size_t vx = i;
3463 if ((dcm.samplesPerPixel == 1) && (littleEndianPlatform())) //endian, except for RGB
3464 vx = (bytesPerSample - 1) - i;
3465 while (vx < imgsz) {
3466 int8_t n = (int8_t)cImg[offset];
3467 offset++;
3468 //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html
3469 //if ((n >= 0) && (n <= 127)) { //not needed: int8_t always <=127
3470 if (n >= 0) { //literal bytes
3471 int reps = 1 + (int)n;
3472 for (int r = 0; r < reps; r++) {
3473 int8_t v = cImg[offset];
3474 offset++;
3475 if (vx >= imgsz)
3476 ; //printMessage("literal overflow %d %d\n", r, reps);
3477 else
3478 bImg[vx] = v;
3479 vx = vx + bytesPerSample;
3480 }
3481 } else if ((n <= -1) && (n >= -127)) { //repeated run
3482 int8_t v = cImg[offset];
3483 offset++;
3484 int reps = -(int)n + 1;
3485 for (int r = 0; r < reps; r++) {
3486 if (vx >= imgsz)
3487 ; //printMessage("repeat overflow %d\n", reps);
3488 else
3489 bImg[vx] = v;
3490 vx = vx + bytesPerSample;
3491 }
3492 }; //n.b. we ignore -128!
3493 } //while vx < imgsz
3494 } //for i < bytesPerSample
3495 free(cImg);
3496 return bImg;
3497 } // nii_loadImgRLE()
3498
3499 #ifdef myDisableOpenJPEG
3500 #ifndef myEnableJasper
3501 //avoid compiler warning, see https://stackoverflow.com/questions/3599160/unused-parameter-warnings-in-c
3502 #define UNUSED(x) (void)(x)
3503 #endif
3504 #endif
3505
3506 #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) //Support for JPEG-LS
3507 //JPEG-LS: Transfer Syntaxes 1.2.840.10008.1.2.4.80 1.2.840.10008.1.2.4.81
3508
3509 #ifdef myEnableJPEGLS1 //use CharLS v1.* requires c++03
3510 //-std=c++03 -DmyEnableJPEGLS1 charls1/header.cpp charls1/jpegls.cpp charls1/jpegmarkersegment.cpp charls1/interface.cpp charls1/jpegstreamwriter.cpp
3511 #include "charls1/interface.h"
3512 #else //use latest release of CharLS: CharLS 2.x requires c++14
3513 //-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp
3514 #include "charls/charls.h"
3515 #endif
3516 #include "charls/publictypes.h"
3517
nii_loadImgJPEGLS(char * imgname,struct nifti_1_header hdr,struct TDICOMdata dcm)3518 unsigned char *nii_loadImgJPEGLS(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) {
3519 //load compressed data
3520 FILE *file = fopen(imgname, "rb");
3521 if (!file) {
3522 printError("Unable to open %s\n", imgname);
3523 return NULL;
3524 }
3525 fseek(file, 0, SEEK_END);
3526 long fileLen = ftell(file);
3527 if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) {
3528 printMessage("File not large enough to store JPEG-LS data: %s\n", imgname);
3529 fclose(file);
3530 return NULL;
3531 }
3532 fseek(file, (long)dcm.imageStart, SEEK_SET);
3533 unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input
3534 size_t sz = fread(cImg, 1, dcm.imageBytes, file);
3535 fclose(file);
3536 if (sz < (size_t)dcm.imageBytes) {
3537 printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname);
3538 free(cImg);
3539 return NULL;
3540 }
3541 //create buffer for uncompressed data
3542 size_t imgsz = nii_ImgBytes(hdr);
3543 unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output
3544 JlsParameters params = {};
3545 #ifdef myEnableJPEGLS1
3546 if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms) != OK) {
3547 #else
3548 using namespace charls;
3549 if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) {
3550 #endif
3551 printMessage("CharLS failed to read header.\n");
3552 return NULL;
3553 }
3554 #ifdef myEnableJPEGLS1
3555 if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms) != OK) {
3556 #else
3557 if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) {
3558 #endif
3559 free(bImg);
3560 printMessage("CharLS failed to read image.\n");
3561 return NULL;
3562 }
3563 return (bImg);
3564 }
3565 #endif
3566
3567 unsigned char *nii_loadImgXL(char *imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D) {
3568 //provided with a filename (imgname) and DICOM header (dcm), creates NIfTI header (hdr) and img
3569 if (headerDcm2Nii(dcm, hdr, true) == EXIT_FAILURE)
3570 return NULL; //TOFU
3571 unsigned char *img;
3572 if (dcm.compressionScheme == kCompress50) {
3573 #ifdef myDisableClassicJPEG
3574 printMessage("Software not compiled to decompress classic JPEG DICOM images\n");
3575 return NULL;
3576 #else
3577 //img = nii_loadImgJPEG50(imgname, *hdr, dcm);
3578 img = nii_loadImgJPEG50(imgname, dcm);
3579 if (hdr->datatype == DT_RGB24) //convert to planar
3580 img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped
3581 #endif
3582 } else if (dcm.compressionScheme == kCompressJPEGLS) {
3583 #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1)
3584 img = nii_loadImgJPEGLS(imgname, *hdr, dcm);
3585 if (hdr->datatype == DT_RGB24) //convert to planar
3586 img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped
3587 #else
3588 printMessage("Software not compiled to decompress JPEG-LS DICOM images\n");
3589 return NULL;
3590 #endif
3591 } else if (dcm.compressionScheme == kCompressPMSCT_RLE1) {
3592 img = nii_loadImgPMSCT_RLE1(imgname, *hdr, dcm);
3593 } else if (dcm.compressionScheme == kCompressRLE) {
3594 img = nii_loadImgRLE(imgname, *hdr, dcm);
3595 if (hdr->datatype == DT_RGB24) //convert to planar
3596 img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped
3597 } else if (dcm.compressionScheme == kCompressC3)
3598 img = nii_loadImgJPEGC3(imgname, *hdr, dcm, isVerbose);
3599 else
3600 #ifndef myDisableOpenJPEG
3601 if (((dcm.compressionScheme == kCompress50) || (dcm.compressionScheme == kCompressYes)) && (compressFlag != kCompressNone))
3602 img = nii_loadImgCoreOpenJPEG(imgname, *hdr, dcm, compressFlag);
3603 else
3604 #else
3605 #ifdef myEnableJasper
3606 if ((dcm.compressionScheme == kCompressYes) && (compressFlag != kCompressNone))
3607 img = nii_loadImgCoreJasper(imgname, *hdr, dcm, compressFlag);
3608 else
3609 #endif
3610 #endif
3611 if (dcm.compressionScheme == kCompressYes) {
3612 printMessage("Software not set up to decompress DICOM\n");
3613 return NULL;
3614 } else
3615 img = nii_loadImgCore(imgname, *hdr, dcm.bitsAllocated, dcm.imageStart);
3616 if (img == NULL)
3617 return img;
3618 if ((dcm.compressionScheme == kCompressNone) && (dcm.isLittleEndian != littleEndianPlatform()) && (hdr->bitpix > 8))
3619 img = nii_byteswap(img, hdr);
3620 if ((dcm.compressionScheme == kCompressNone) && (hdr->datatype == DT_RGB24))
3621 img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped
3622 dcm.isPlanarRGB = true;
3623 if (dcm.CSA.mosaicSlices > 1) {
3624 img = nii_demosaic(img, hdr, dcm.CSA.mosaicSlices, (dcm.manufacturer == kMANUFACTURER_UIH)); //, dcm.CSA.protocolSliceNumber1);
3625 }
3626 if ((dti4D == NULL) && (!dcm.isFloat) && (iVaries)) //must do afte
3627 img = nii_iVaries(img, hdr, NULL);
3628 int nAcq = dcm.locationsInAcquisition;
3629 if ((nAcq > 1) && (hdr->dim[0] < 4) && ((hdr->dim[3] % nAcq) == 0) && (hdr->dim[3] > nAcq)) {
3630 hdr->dim[4] = hdr->dim[3] / nAcq;
3631 hdr->dim[3] = nAcq;
3632 hdr->dim[0] = 4;
3633 }
3634 if ((dti4D != NULL) && (dti4D->sliceOrder[0] >= 0))
3635 img = nii_reorderSlicesX(img, hdr, dti4D);
3636 if ((dti4D != NULL) && (!dcm.isFloat) && (iVaries))
3637 img = nii_iVaries(img, hdr, dti4D);
3638 headerDcm2NiiSForm(dcm, dcm, hdr, false);
3639 return img;
3640 } //nii_loadImgXL()
3641
3642 int isSQ(uint32_t groupElement) { //Detect sequence VR ("SQ") for implicit tags
3643 static const int array_size = 35;
3644 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)};
3645 for (int i = 0; i < array_size; i++) {
3646 //if (array[i] == groupElement) printMessage(" implicitSQ %04x,%04x\n", groupElement & 65535,groupElement>>16);
3647 if (array[i] == groupElement)
3648 return 1;
3649 }
3650 return 0;
3651 } //isSQ()
3652
3653 int isDICOMfile(const char *fname) { //0=NotDICOM, 1=DICOM, 2=Maybe(not Part 10 compliant)
3654 //Someday: it might be worthwhile to detect "IMGF" at offset 3228 to warn user if they attempt to convert Signa data
3655 FILE *fp = fopen(fname, "rb");
3656 if (!fp)
3657 return 0;
3658 fseek(fp, 0, SEEK_END);
3659 long fileLen = ftell(fp);
3660 if (fileLen < 256) {
3661 fclose(fp);
3662 return 0;
3663 }
3664 fseek(fp, 0, SEEK_SET);
3665 unsigned char buffer[256];
3666 size_t sz = fread(buffer, 1, 256, fp);
3667 fclose(fp);
3668 if (sz < 256)
3669 return 0;
3670 if ((buffer[128] == 'D') && (buffer[129] == 'I') && (buffer[130] == 'C') && (buffer[131] == 'M'))
3671 return 1; //valid DICOM
3672 if ((buffer[0] == 8) && (buffer[1] == 0) && (buffer[3] == 0))
3673 return 2; //not valid Part 10 file, perhaps DICOM object
3674 return 0;
3675 } //isDICOMfile()
3676
3677 //START RIR 12/2017 Robert I. Reid
3678
3679 // Gathering spot for all the info needed to get the b value and direction
3680 // for a volume.
3681 struct TVolumeDiffusion {
3682 struct TDICOMdata *pdd; // The multivolume
3683 struct TDTI4D *pdti4D; // permanent records.
3684 uint8_t manufacturer; // kMANUFACTURER_UNKNOWN, kMANUFACTURER_SIEMENS, etc.
3685
3686 //void set_manufacturer(const uint8_t m) {manufacturer = m; update();} // unnecessary
3687
3688 // Everything after this in the structure would be private if it were a C++
3689 // class, but it has been rewritten as a struct for C compatibility. I am
3690 // using _ as a hint of that, although _ for privacy is not really a
3691 // universal convention in C. Privacy is desired because immediately
3692 // any of these are updated _update_tvd() should be called.
3693
3694 bool _isAtFirstPatientPosition; // Limit b vals and vecs to 1 per volume.
3695
3696 //float bVal0018_9087; // kDiffusion_b_value, always present in Philips/Siemens.
3697 //float bVal2001_1003; // kDiffusionBFactor
3698 // float dirRL2005_10b0; // kDiffusionDirectionRL
3699 // float dirAP2005_10b1; // kDiffusionDirectionAP
3700 // float dirFH2005_10b2; // kDiffusionDirectionFH
3701 // Philips diffusion scans tend to have a "trace" (average of the diffusion
3702 // weighted volumes) volume tacked on, usually but not always at the end,
3703 // so b is > 0, but the direction is meaningless. Most software versions
3704 // explicitly set the direction to 0, but version 3 does not, making (0x18,
3705 // 0x9075) necessary.
3706 bool _isPhilipsNonDirectional;
3707 //char _directionality0018_9075[16]; // DiffusionDirectionality, not in Philips 2.6.
3708 // float _orientation0018_9089[3]; // kDiffusionOrientation, always present in Philips/Siemens for volumes with a direction.
3709 //char _seq0018_9117[64]; // MRDiffusionSequence, not in Philips 2.6.
3710 float _dtiV[4];
3711 double _symBMatrix[6];
3712 //uint16_t numDti;
3713 };
3714 struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D);
3715 void clear_volume(struct TVolumeDiffusion *ptvd); // Blank the volume-specific members or set them to impossible values.
3716 void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf);
3717 void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian);
3718 void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, bool iafpp);
3719 int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf);
3720 void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis);
3721 void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, int axis);
3722 void set_bVal(struct TVolumeDiffusion *ptvd, float b);
3723 void set_bMatrix(struct TVolumeDiffusion *ptvd, float b, int component);
3724 void _update_tvd(struct TVolumeDiffusion *ptvd);
3725
3726 struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D) {
3727 struct TVolumeDiffusion tvd;
3728 tvd.pdd = ptdd;
3729 tvd.pdti4D = dti4D;
3730 clear_volume(&tvd);
3731 return tvd;
3732 } //initTVolumeDiffusion()
3733
3734 void clear_volume(struct TVolumeDiffusion *ptvd) {
3735 ptvd->_isAtFirstPatientPosition = false;
3736 ptvd->manufacturer = kMANUFACTURER_UNKNOWN;
3737 //bVal0018_9087 = -1;
3738 //ptvd->_directionality0018_9075[0] = 0;
3739 //ptvd->seq0018_9117[0] = 0;
3740 //bVal2001_1003 = -1;
3741 // dirRL2005_10b0 = 2;
3742 // dirAP2005_10b1 = 2;
3743 // dirFH2005_10b2 = 2;
3744 ptvd->_isPhilipsNonDirectional = false;
3745 ptvd->_dtiV[0] = -1;
3746 for (int i = 1; i < 4; ++i)
3747 ptvd->_dtiV[i] = 2;
3748 for (int i = 1; i < 6; ++i)
3749 ptvd->_symBMatrix[i] = NAN;
3750 //numDti = 0;
3751 } //clear_volume()
3752
3753 void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf) {
3754 //if(!strncmp(( char*)(inbuf), "BMATRIX", 4)) printf("FOUND BMATRIX----%s\n",inbuf );
3755 //n.b. strncmp returns 0 if the contents of both strings are equal, for boolean 0 = false!
3756 // elsewhere we use strstr() which returns 0/null if match is not present
3757 if (strncmp((char *)(inbuf), "DIRECTIONAL", 11) && // strncmp = 0 for ==.
3758 //strncmp(( char*)(inbuf), "NONE", 4) && //issue 256
3759 strncmp((char *)(inbuf), "BMATRIX", 7)) { // Siemens XA10
3760 ptvd->_isPhilipsNonDirectional = true;
3761 // Explicitly set the direction to 0 now, because there may
3762 // not be a 0018,9089 for this frame.
3763 for (int i = 1; i < 4; ++i) // 1-3 is intentional.
3764 ptvd->_dtiV[i] = 0.0;
3765 } else {
3766 ptvd->_isPhilipsNonDirectional = false;
3767 // Wait for 0018,9089 to get the direction.
3768 }
3769 _update_tvd(ptvd);
3770 } //set_directionality0018_9075()
3771
3772 int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf) {
3773 //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
3774 int bVal = dcmStrInt(lLength, inbuf);
3775 bVal = (bVal % 10000);
3776 ptvd->_dtiV[0] = bVal;
3777 //printf("(0043,1039) '%s' Slop_int_6 -->%d \n", inbuf, bVal);
3778 //dd.CSA.numDti = 1; // Always true for GE.
3779 _update_tvd(ptvd);
3780 return bVal;
3781 } //set_bValGE()
3782
3783 // axis: 0 -> x, 1 -> y , 2 -> z
3784 void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis) {
3785 ptvd->_dtiV[axis + 1] = vec;
3786 //printf("(2005,10b0..2) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]);
3787 _update_tvd(ptvd);
3788 } //set_diffusion_directionPhilips()
3789
3790 // axis: 0 -> x, 1 -> y , 2 -> z
3791 void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, const int axis) {
3792 ptvd->_dtiV[axis + 1] = dcmStrFloat(lLength, inbuf);
3793 //printf("(0019,10bb..d) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]);
3794 _update_tvd(ptvd);
3795 } //set_diffusion_directionGE()
3796
3797 void dcmMultiFloatDouble(size_t lByteLength, unsigned char lBuffer[], size_t lnFloats, float *lFloats, bool isLittleEndian) {
3798 size_t floatlen = lByteLength / lnFloats;
3799 for (size_t i = 0; i < lnFloats; ++i)
3800 lFloats[i] = dcmFloatDouble((int)floatlen, lBuffer + i * floatlen, isLittleEndian);
3801 } //dcmMultiFloatDouble()
3802
3803 void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian) {
3804 if (ptvd->_isPhilipsNonDirectional) {
3805 for (int i = 1; i < 4; ++i) // Deliberately ignore inbuf; it might be nonsense.
3806 ptvd->_dtiV[i] = 0.0;
3807 } else
3808 dcmMultiFloatDouble(lLength, inbuf, 3, ptvd->_dtiV + 1, isLittleEndian);
3809 _update_tvd(ptvd);
3810 } //set_orientation0018_9089()
3811
3812 void set_bVal(struct TVolumeDiffusion *ptvd, const float b) {
3813 ptvd->_dtiV[0] = b;
3814 _update_tvd(ptvd);
3815 } //set_bVal()
3816
3817 void set_bMatrix(struct TVolumeDiffusion *ptvd, double b, int idx) {
3818 if ((idx < 0) || (idx > 5))
3819 return;
3820 ptvd->_symBMatrix[idx] = b;
3821 _update_tvd(ptvd);
3822 }
3823
3824 void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, const bool iafpp) {
3825 ptvd->_isAtFirstPatientPosition = iafpp;
3826 _update_tvd(ptvd);
3827 } //set_isAtFirstPatientPosition_tvd()
3828
3829 // Update the diffusion info in dd and *pdti4D for a volume once all the
3830 // diffusion info for that volume has been read into pvd.
3831 //
3832 // Note that depending on the scanner software the diffusion info can arrive in
3833 // different tags, in different orders (because of enclosing sequence tags),
3834 // and the values in some tags may be invalid, or may be essential, depending
3835 // on the presence of other tags. Thus it is best to gather all the diffusion
3836 // info for a volume (frame) before taking action on it.
3837 //
3838 // On the other hand, dd and *pdti4D need to be updated as soon as the
3839 // diffusion info is ready, before diffusion info for the next volume is read
3840 // in.
3841 void _update_tvd(struct TVolumeDiffusion *ptvd) {
3842 // Figure out if we have both the b value and direction (if any) for this
3843 // volume, and if isFirstPosition.
3844 // // GE (software version 27) is liable to NOT include kDiffusion_b_value for the
3845 // // slice if it is 0, but should still have kDiffusionBFactor, which comes
3846 // // after PatientPosition.
3847 // if(isAtFirstPatientPosition && manufacturer == kMANUFACTURER_GE && dtiV[0] < 0)
3848 // dtiV[0] = 0; // Implied 0.
3849 bool isReady = (ptvd->_isAtFirstPatientPosition && (ptvd->_dtiV[0] >= 0));
3850 if (!isReady)
3851 return; //no B=0
3852 if (isReady) {
3853 for (int i = 1; i < 4; ++i) {
3854 if (ptvd->_dtiV[i] > 1) {
3855 isReady = false;
3856 break;
3857 }
3858 }
3859 }
3860 if (!isReady) { //bvecs NOT filled: see if symBMatrix filled
3861 isReady = true;
3862 for (int i = 1; i < 6; ++i)
3863 if (isnan(ptvd->_symBMatrix[i]))
3864 isReady = false;
3865 if (!isReady)
3866 return; // symBMatrix not filled
3867 //START BRUKER KLUDGE
3868 //see issue 265: Bruker stores xx,xy,xz,yx,yy,yz instead of xx,xy,xz,yy,yz,zz
3869 // we can recover since xx+yy+zz = bval
3870 // since any value squared is positive, a negative diagonal reveals fault
3871 double xx = ptvd->_symBMatrix[0]; //x*x
3872 double xy = ptvd->_symBMatrix[1]; //x*y
3873 double xz = ptvd->_symBMatrix[2]; //x*z
3874 double yy = ptvd->_symBMatrix[3]; //y*y
3875 double yz = ptvd->_symBMatrix[4]; //y*z
3876 double zz = ptvd->_symBMatrix[5]; //z*z
3877 bool isBrukerBug = false;
3878 if ((xx < 0.0) || (yy < 0.0) || (zz < 0.0))
3879 isBrukerBug = true;
3880 double sumDiag = ptvd->_symBMatrix[0] + ptvd->_symBMatrix[3] + ptvd->_symBMatrix[5]; //if correct xx+yy+zz = bval
3881 double bVecError = fabs(sumDiag - ptvd->pdd->CSA.dtiV[0]);
3882 if (bVecError > 0.5)
3883 isBrukerBug = true;
3884 //next: check diagonals
3885 double x = sqrt(xx);
3886 double y = sqrt(yy);
3887 double z = sqrt(zz);
3888 if ((fabs((x * y) - xy)) > 0.5)
3889 isBrukerBug = true;
3890 if ((fabs((x * z) - xz)) > 0.5)
3891 isBrukerBug = true;
3892 if ((fabs((y * z) - yz)) > 0.5)
3893 isBrukerBug = true;
3894 if (isBrukerBug)
3895 printWarning("Fixing corrupt bmat (issue 265). [%g %g %g %g %g %g]\n", xx, xy, xz, yy, yz, zz);
3896 if (isBrukerBug) {
3897 ptvd->_symBMatrix[3] = ptvd->_symBMatrix[4];
3898 ptvd->_symBMatrix[4] = ptvd->_symBMatrix[5];
3899 //next: solve for zz given bvalue, xx, and yy
3900 ptvd->_symBMatrix[5] = ptvd->_dtiV[0] - ptvd->_symBMatrix[0] - ptvd->_symBMatrix[3];
3901 if ((ptvd->_symBMatrix[0] < 0.0) || (ptvd->_symBMatrix[5] < 0.0))
3902 printError("DICOM BMatrix corrupt.\n");
3903 }
3904 //END BRUKER_KLUDGE
3905 vec3 bVec = nifti_mat33_eig3(ptvd->_symBMatrix[0], ptvd->_symBMatrix[1], ptvd->_symBMatrix[2], ptvd->_symBMatrix[3], ptvd->_symBMatrix[4], ptvd->_symBMatrix[5]);
3906 ptvd->_dtiV[1] = bVec.v[0];
3907 ptvd->_dtiV[2] = bVec.v[1];
3908 ptvd->_dtiV[3] = bVec.v[2];
3909 //printf("bmat=[%g %g %g %g %g %g %g %g %g]\n", ptvd->_symBMatrix[0],ptvd->_symBMatrix[1],ptvd->_symBMatrix[2], ptvd->_symBMatrix[1],ptvd->_symBMatrix[3],ptvd->_symBMatrix[4], ptvd->_symBMatrix[2],ptvd->_symBMatrix[4],ptvd->_symBMatrix[5]);
3910 //printf("bmats=[%g %g %g %g %g %g];\n", ptvd->_symBMatrix[0],ptvd->_symBMatrix[1],ptvd->_symBMatrix[2],ptvd->_symBMatrix[3],ptvd->_symBMatrix[4],ptvd->_symBMatrix[5]);
3911 //printf("bvec=[%g %g %g];\n", ptvd->_dtiV[1], ptvd->_dtiV[2], ptvd->_dtiV[3]);
3912 //printf("bval=%g;\n\n", ptvd->_dtiV[0]);
3913 }
3914 if (!isReady)
3915 return;
3916 // If still here, update dd and *pdti4D.
3917 ptvd->pdd->CSA.numDti++;
3918 if (ptvd->pdd->CSA.numDti == 2) { // First time we know that this is a 4D DTI dataset;
3919 for (int i = 0; i < 4; ++i) // Start *pdti4D before ptvd->pdd->CSA.dtiV
3920 ptvd->pdti4D->S[0].V[i] = ptvd->pdd->CSA.dtiV[i]; // is updated.
3921 }
3922 for (int i = 0; i < 4; ++i) // Update pdd
3923 ptvd->pdd->CSA.dtiV[i] = ptvd->_dtiV[i];
3924 if ((ptvd->pdd->CSA.numDti > 1) && (ptvd->pdd->CSA.numDti < kMaxDTI4D)) { // Update *pdti4D
3925 //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI));
3926 for (int i = 0; i < 4; ++i)
3927 ptvd->pdti4D->S[ptvd->pdd->CSA.numDti - 1].V[i] = ptvd->_dtiV[i];
3928 }
3929 clear_volume(ptvd); // clear the slate for the next volume.
3930 } //_update_tvd()
3931 //END RIR
3932
3933 struct TDCMdim { //DimensionIndexValues
3934 uint32_t dimIdx[MAX_NUMBER_OF_DIMENSIONS];
3935 uint32_t diskPos;
3936 float triggerDelayTime, TE, intenScale, intenIntercept, intenScalePhilips, RWVScale, RWVIntercept;
3937 float V[4];
3938 bool isPhase;
3939 bool isReal;
3940 bool isImaginary;
3941 };
3942
3943 void getFileNameX(char *pathParent, const char *path, int maxLen) { //if path is c:\d1\d2 then filename is 'd2'
3944 const char *filename = strrchr(path, '/'); //UNIX
3945 const char *filenamew = strrchr(path, '\\'); //Windows
3946 if (filename == NULL)
3947 filename = filenamew;
3948 //if ((filename != NULL) && (filenamew != NULL)) filename = std::max(filename, filenamew);
3949 if ((filename != NULL) && (filenamew != NULL) && (filenamew > filename))
3950 filename = filenamew; //for mixed file separators, e.g. "C:/dir\filenane.tmp"
3951 if (filename == NULL) { //no path separator
3952 strcpy(pathParent, path);
3953 return;
3954 }
3955 filename++;
3956 strncpy(pathParent, filename, maxLen - 1);
3957 }
3958
3959 void getFileName(char *pathParent, const char *path) { //if path is c:\d1\d2 then filename is 'd2'
3960 getFileNameX(pathParent, path, kDICOMStr);
3961 }
3962
3963 struct fidx {
3964 float value;
3965 int index;
3966 };
3967
3968 int fcmp(const void *a, const void *b) {
3969 struct fidx *a1 = (struct fidx *)a;
3970 struct fidx *a2 = (struct fidx *)b;
3971 if ((*a1).value > (*a2).value)
3972 return 1;
3973 else if ((*a1).value < (*a2).value)
3974 return -1;
3975 else
3976 return 0;
3977 }
3978
3979 #ifdef USING_R
3980
3981 // True iff dcm1 sorts *before* dcm2
3982 bool compareTDCMdim(const TDCMdim &dcm1, const TDCMdim &dcm2) {
3983 for (int i = MAX_NUMBER_OF_DIMENSIONS - 1; i >= 0; i--) {
3984 if (dcm1.dimIdx[i] < dcm2.dimIdx[i])
3985 return true;
3986 else if (dcm1.dimIdx[i] > dcm2.dimIdx[i])
3987 return false;
3988 }
3989 return false;
3990 } //compareTDCMdim()
3991
3992 bool compareTDCMdimRev(const TDCMdim &dcm1, const TDCMdim &dcm2) {
3993 for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) {
3994 if (dcm1.dimIdx[i] < dcm2.dimIdx[i])
3995 return true;
3996 else if (dcm1.dimIdx[i] > dcm2.dimIdx[i])
3997 return false;
3998 }
3999 return false;
4000 } //compareTDCMdimRev()
4001
4002 #else
4003
4004 int compareTDCMdim(void const *item1, void const *item2) {
4005 struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1;
4006 struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2;
4007 //for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) {
4008 for (int i = MAX_NUMBER_OF_DIMENSIONS - 1; i >= 0; i--) {
4009 if (dcm1->dimIdx[i] < dcm2->dimIdx[i])
4010 return -1;
4011 else if (dcm1->dimIdx[i] > dcm2->dimIdx[i])
4012 return 1;
4013 }
4014 return 0;
4015 } //compareTDCMdim()
4016
4017 int compareTDCMdimRev(void const *item1, void const *item2) {
4018 struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1;
4019 struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2;
4020 for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) {
4021 if (dcm1->dimIdx[i] < dcm2->dimIdx[i])
4022 return -1;
4023 else if (dcm1->dimIdx[i] > dcm2->dimIdx[i])
4024 return 1;
4025 }
4026 return 0;
4027 } //compareTDCMdimRev()
4028
4029 #endif // USING_R
4030
4031 struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D *dti4D) {
4032 //struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) {
4033 int isVerbose = prefs->isVerbose;
4034 int compressFlag = prefs->compressFlag;
4035 struct TDICOMdata d = clear_dicom_data();
4036 d.imageNum = 0; //not set
4037 strcpy(d.protocolName, ""); //erase dummy with empty
4038 strcpy(d.protocolName, ""); //erase dummy with empty
4039 strcpy(d.seriesDescription, ""); //erase dummy with empty
4040 strcpy(d.sequenceName, ""); //erase dummy with empty
4041 //do not read folders - code specific to GCC (LLVM/Clang seems to recognize a small file size)
4042 dti4D->sliceOrder[0] = -1;
4043 dti4D->volumeOnsetTime[0] = -1;
4044 dti4D->decayFactor[0] = -1;
4045 dti4D->frameDuration[0] = -1;
4046 //dti4D->fragmentOffset[0] = -1;
4047 dti4D->intenScale[0] = 0.0;
4048 struct TVolumeDiffusion volDiffusion = initTVolumeDiffusion(&d, dti4D);
4049 struct stat s;
4050 if (stat(fname, &s) == 0) {
4051 if (!(s.st_mode & S_IFREG)) {
4052 printMessage("DICOM read fail: not a valid file (perhaps a directory) %s\n", fname);
4053 return d;
4054 }
4055 }
4056 bool isPart10prefix = true;
4057 int isOK = isDICOMfile(fname);
4058 if (isOK == 0)
4059 return d;
4060 if (isOK == 2) {
4061 d.isExplicitVR = false;
4062 isPart10prefix = false;
4063 }
4064 FILE *file = fopen(fname, "rb");
4065 if (!file) {
4066 printMessage("Unable to open file %s\n", fname);
4067 return d;
4068 }
4069 fseek(file, 0, SEEK_END);
4070 long fileLen = ftell(file); //Get file length
4071 if (fileLen < 256) {
4072 printMessage("File too small to be a DICOM image %s\n", fname);
4073 return d;
4074 }
4075 //Since size of DICOM header is unknown, we will load it in 1mb segments
4076 //This uses less RAM and makes is faster for computers with slow disk access
4077 //Benefit is largest for 4D images.
4078 //To disable caching and load entire file to RAM, compile with "-dmyLoadWholeFileToReadHeader"
4079 //To implement the segments, we define these variables:
4080 // fileLen = size of file in bytes
4081 // MaxBufferSz = maximum size of buffer in bytes
4082 // Buffer = array with n elements, where n is smaller of fileLen or MaxBufferSz
4083 // lPos = position in Buffer (indexed from 0), 0..(n-1)
4084 // lFileOffset = offset of Buffer in file: true file position is lOffset+lPos (initially 0)
4085 #ifdef myLoadWholeFileToReadHeader
4086 size_t MaxBufferSz = fileLen;
4087 #else
4088 size_t MaxBufferSz = 1000000; //ideally size of DICOM header, but this varies from 2D to 4D files
4089 #endif
4090 if (MaxBufferSz > (size_t)fileLen)
4091 MaxBufferSz = fileLen;
4092 //printf("%d -> %d\n", MaxBufferSz, fileLen);
4093 long lFileOffset = 0;
4094 fseek(file, 0, SEEK_SET);
4095 //Allocate memory
4096 unsigned char *buffer = (unsigned char *)malloc(MaxBufferSz + 1);
4097 if (!buffer) {
4098 printError("Memory exhausted!");
4099 fclose(file);
4100 return d;
4101 }
4102 //Read file contents into buffer
4103 size_t sz = fread(buffer, 1, MaxBufferSz, file);
4104 if (sz < MaxBufferSz) {
4105 printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname);
4106 fclose(file);
4107 return d;
4108 }
4109 #ifdef myLoadWholeFileToReadHeader
4110 fclose(file);
4111 #endif
4112 //DEFINE DICOM TAGS
4113 #define kUnused 0x0001 + (0x0001 << 16)
4114 #define kStart 0x0002 + (0x0000 << 16)
4115 #define kMediaStorageSOPClassUID 0x0002 + (0x0002 << 16)
4116 #define kMediaStorageSOPInstanceUID 0x0002 + (0x0003 << 16)
4117 #define kTransferSyntax 0x0002 + (0x0010 << 16)
4118 #define kImplementationVersionName 0x0002 + (0x0013 << 16)
4119 #define kSourceApplicationEntityTitle 0x0002 + (0x0016 << 16)
4120 #define kDirectoryRecordSequence 0x0004 + (0x1220 << 16)
4121 //#define kSpecificCharacterSet 0x0008+(0x0005 << 16 ) //someday we should handle foreign characters...
4122 #define kImageTypeTag 0x0008 + (0x0008 << 16)
4123 //#define kSOPInstanceUID 0x0008+(0x0018 << 16 ) //Philips inserts time as last item, e.g. ?.?.?.YYYYMMDDHHmmSS.SSSS
4124 // not reliable https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/21
4125 #define kStudyDate 0x0008 + (0x0020 << 16)
4126 #define kAcquisitionDate 0x0008 + (0x0022 << 16)
4127 #define kAcquisitionDateTime 0x0008 + (0x002A << 16)
4128 #define kStudyTime 0x0008 + (0x0030 << 16)
4129 #define kSeriesTime 0x0008 + (0x0031 << 16)
4130 #define kAcquisitionTime 0x0008 + (0x0032 << 16) //TM
4131 //#define kContentTime 0x0008+(0x0033 << 16 ) //TM
4132 #define kModality 0x0008 + (0x0060 << 16) //CS
4133 #define kManufacturer 0x0008 + (0x0070 << 16)
4134 #define kInstitutionName 0x0008 + (0x0080 << 16)
4135 #define kInstitutionAddress 0x0008 + (0x0081 << 16)
4136 #define kReferringPhysicianName 0x0008 + (0x0090 << 16)
4137 #define kStationName 0x0008 + (0x1010 << 16)
4138 #define kSeriesDescription 0x0008 + (0x103E << 16) // '0008' '103E' 'LO' 'SeriesDescription'
4139 #define kInstitutionalDepartmentName 0x0008 + (0x1040 << 16)
4140 #define kManufacturersModelName 0x0008 + (0x1090 << 16)
4141 #define kDerivationDescription 0x0008 + (0x2111 << 16)
4142 #define kComplexImageComponent (uint32_t)0x0008 + (0x9208 << 16) //'0008' '9208' 'CS' 'ComplexImageComponent'
4143 #define kAcquisitionContrast (uint32_t)0x0008 + (0x9209 << 16) //'0008' '9209' 'CS' 'AcquisitionContrast'
4144 #define kPatientName 0x0010 + (0x0010 << 16)
4145 #define kPatientID 0x0010 + (0x0020 << 16)
4146 #define kAccessionNumber 0x0008 + (0x0050 << 16)
4147 #define kPatientBirthDate 0x0010 + (0x0030 << 16)
4148 #define kPatientSex 0x0010 + (0x0040 << 16)
4149 #define kPatientAge 0x0010 + (0x1010 << 16)
4150 #define kPatientWeight 0x0010 + (0x1030 << 16)
4151 #define kAnatomicalOrientationType 0x0010 + (0x2210 << 16)
4152 #define kDeidentificationMethod 0x0012 + (0x0063 << 16) //[DICOMANON, issue 383
4153 #define kBodyPartExamined 0x0018 + (0x0015 << 16)
4154 #define kBodyPartExamined 0x0018 + (0x0015 << 16)
4155 #define kScanningSequence 0x0018 + (0x0020 << 16)
4156 #define kSequenceVariant 0x0018 + (0x0021 << 16)
4157 #define kScanOptions 0x0018 + (0x0022 << 16)
4158 #define kMRAcquisitionType 0x0018 + (0x0023 << 16)
4159 #define kSequenceName 0x0018 + (0x0024 << 16)
4160 #define kRadiopharmaceutical 0x0018 + (0x0031 << 16) //LO
4161 #define kZThick 0x0018 + (0x0050 << 16)
4162 #define kTR 0x0018 + (0x0080 << 16)
4163 #define kTE 0x0018 + (0x0081 << 16)
4164 #define kTI 0x0018 + (0x0082 << 16) // Inversion time
4165 #define kNumberOfAverages 0x0018 + (0x0083 << 16) //DS
4166 #define kImagingFrequency 0x0018 + (0x0084 << 16) //DS
4167 //#define kEffectiveTE 0x0018+(0x9082 << 16 )
4168 #define kEchoNum 0x0018 + (0x0086 << 16) //IS
4169 #define kMagneticFieldStrength 0x0018 + (0x0087 << 16) //DS
4170 #define kZSpacing 0x0018 + (0x0088 << 16) //'DS' 'SpacingBetweenSlices'
4171 #define kPhaseEncodingSteps 0x0018 + (0x0089 << 16) //'IS'
4172 #define kEchoTrainLength 0x0018 + (0x0091 << 16) //IS
4173 #define kPercentSampling 0x0018 + (0x0093 << 16) //'DS'
4174 #define kPhaseFieldofView 0x0018 + (0x0094 << 16) //'DS'
4175 #define kPixelBandwidth 0x0018 + (0x0095 << 16) //'DS' 'PixelBandwidth'
4176 #define kDeviceSerialNumber 0x0018 + (0x1000 << 16) //LO
4177 #define kSoftwareVersions 0x0018 + (0x1020 << 16) //LO
4178 #define kProtocolName 0x0018 + (0x1030 << 16)
4179 #define kTriggerTime 0x0018 + (0x1060 << 16) //DS
4180 #define kRadionuclideTotalDose 0x0018 + (0x1074 << 16)
4181 #define kRadionuclideHalfLife 0x0018 + (0x1075 << 16)
4182 #define kRadionuclidePositronFraction 0x0018 + (0x1076 << 16)
4183 #define kGantryTilt 0x0018 + (0x1120 << 16)
4184 #define kXRayTimeMS 0x0018 + (0x1150 << 16) //IS
4185 #define kXRayTubeCurrent 0x0018 + (0x1151 << 16) //IS
4186 #define kXRayExposure 0x0018 + (0x1152 << 16)
4187 #define kConvolutionKernel 0x0018 + (0x1210 << 16) //SH
4188 #define kFrameDuration 0x0018 + (0x1242 << 16) //IS
4189 #define kReceiveCoilName 0x0018 + (0x1250 << 16) // SH
4190 //#define kTransmitCoilName 0x0018 + (0x1251 << 16) // SH issue527
4191 #define kAcquisitionMatrix 0x0018 + (0x1310 << 16) //US
4192 #define kFlipAngle 0x0018 + (0x1314 << 16)
4193 #define kInPlanePhaseEncodingDirection 0x0018 + (0x1312 << 16) //CS
4194 #define kSAR 0x0018 + (0x1316 << 16) //'DS' 'SAR'
4195 #define kPatientOrient 0x0018 + (0x5100 << 16) //0018,5100. patient orientation - 'HFS'
4196 #define kInversionRecovery 0x0018 + uint32_t(0x9009 << 16) //'CS' 'YES'/'NO'
4197 #define kSpoiling 0x0018 + uint32_t(0x9016 << 16) //'CS'
4198 #define kEchoPlanarPulseSequence 0x0018 + uint32_t(0x9018 << 16) //'CS' 'YES'/'NO'
4199 #define kMagnetizationTransferAttribute 0x0018 + uint32_t(0x9020 << 16) //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE'
4200 #define kRectilinearPhaseEncodeReordering 0x0018 + uint32_t(0x9034 << 16) //'CS' 'REVERSE_LINEAR'/'LINEAR'
4201 #define kPartialFourierDirection 0x0018 + uint32_t(0x9036 << 16) //'CS'
4202 #define kCardiacSynchronizationTechnique 0x0018 + uint32_t(0x9037 << 16) //'CS'
4203 #define kParallelReductionFactorInPlane 0x0018 + uint32_t(0x9069 << 16) //FD
4204 #define kAcquisitionDuration 0x0018 + uint32_t(0x9073 << 16) //FD
4205 //#define kFrameAcquisitionDateTime 0x0018+uint32_t(0x9074<< 16 ) //DT "20181019212528.232500"
4206 #define kDiffusionDirectionality 0x0018 + uint32_t(0x9075 << 16) // NONE, ISOTROPIC, or DIRECTIONAL
4207 #define kParallelAcquisitionTechnique 0x0018 + uint32_t(0x9078 << 16) //CS: SENSE, SMASH
4208 #define kInversionTimes 0x0018 + uint32_t(0x9079 << 16) //FD
4209 #define kPartialFourier 0x0018 + uint32_t(0x9081 << 16) //CS
4210 const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16);
4211 //#define kDiffusionBFactorSiemens 0x0019+(0x100C<< 16 ) // 0019;000C;SIEMENS MR HEADER;B_value
4212 #define kDiffusion_bValue 0x0018 + uint32_t(0x9087 << 16) // FD
4213 #define kDiffusionOrientation 0x0018 + uint32_t(0x9089 << 16) // FD, seen in enhanced DICOM from Philips 5.* and Siemens XA10.
4214 #define kImagingFrequency2 0x0018 + uint32_t(0x9098 << 16) //FD
4215 #define kParallelReductionFactorOutOfPlane 0x0018 + uint32_t(0x9155 << 16) //FD
4216 //#define kFrameAcquisitionDuration 0x0018+uint32_t(0x9220 << 16 ) //FD
4217 #define kDiffusionBValueXX 0x0018 + uint32_t(0x9602 << 16) //FD
4218 #define kDiffusionBValueXY 0x0018 + uint32_t(0x9603 << 16) //FD
4219 #define kDiffusionBValueXZ 0x0018 + uint32_t(0x9604 << 16) //FD
4220 #define kDiffusionBValueYY 0x0018 + uint32_t(0x9605 << 16) //FD
4221 #define kDiffusionBValueYZ 0x0018 + uint32_t(0x9606 << 16) //FD
4222 #define kDiffusionBValueZZ 0x0018 + uint32_t(0x9607 << 16) //FD
4223 #define kMREchoSequence 0x0018 + uint32_t(0x9114 << 16) //SQ
4224 #define kMRAcquisitionPhaseEncodingStepsInPlane 0x0018 + uint32_t(0x9231 << 16) //US
4225 #define kNumberOfImagesInMosaic 0x0019 + (0x100A << 16) //US NumberOfImagesInMosaic
4226 //https://nmrimaging.wordpress.com/2011/12/20/when-we-process/
4227 // https://nciphub.org/groups/qindicom/wiki/DiffusionrelatedDICOMtags:experienceacrosssites?action=pdf
4228 #define kDiffusion_bValueSiemens 0x0019 + (0x100C << 16) //IS
4229 #define kDiffusionGradientDirectionSiemens 0x0019 + (0x100E << 16) //FD
4230 #define kSeriesPlaneGE 0x0019 + (0x1017 << 16) //SS
4231 #define kDwellTime 0x0019 + (0x1018 << 16) //IS in NSec, see https://github.com/rordenlab/dcm2niix/issues/127
4232 #define kLastScanLoc 0x0019 + (0x101B << 16)
4233 #define kBandwidthPerPixelPhaseEncode 0x0019 + (0x1028 << 16) //FD
4234 #define kSliceTimeSiemens 0x0019 + (0x1029 << 16) ///FD
4235 #define kPulseSequenceNameGE 0x0019 + (0x109C << 16) //LO 'epiRT' or 'epi'
4236 #define kInternalPulseSequenceNameGE 0x0019 + (0x109E << 16) //LO 'EPI' or 'EPI2'
4237 #define kRawDataRunNumberGE 0x0019 + (0x10A2 << 16)//SL
4238 #define kMaxEchoNumGE 0x0019 + (0x10A9 << 16) //DS
4239 #define kUserData12GE 0x0019 + (0x10B3 << 16) //DS phase diffusion direction
4240 #define kDiffusionDirectionGEX 0x0019 + (0x10BB << 16) //DS phase diffusion direction
4241 #define kDiffusionDirectionGEY 0x0019 + (0x10BC << 16) //DS frequency diffusion direction
4242 #define kDiffusionDirectionGEZ 0x0019 + (0x10BD << 16) //DS slice diffusion direction
4243 #define kNumberOfDiffusionDirectionGE 0x0019 + (0x10E0 << 16) ///DS NumberOfDiffusionDirection:UserData24
4244 #define kStudyID 0x0020 + (0x0010 << 16)
4245 #define kSeriesNum 0x0020 + (0x0011 << 16)
4246 #define kAcquNum 0x0020 + (0x0012 << 16)
4247 #define kImageNum 0x0020 + (0x0013 << 16)
4248 #define kStudyInstanceUID 0x0020 + (0x000D << 16)
4249 #define kSeriesInstanceUID 0x0020 + (0x000E << 16)
4250 #define kImagePositionPatient 0x0020 + (0x0032 << 16) // Actually !
4251 #define kOrientationACR 0x0020 + (0x0035 << 16)
4252 #define kOrientation 0x0020 + (0x0037 << 16)
4253 #define kTemporalPosition 0x0020+(0x0100 << 16 ) //IS
4254 //#define kNumberOfTemporalPositions 0x0020+(0x0105 << 16 ) //IS public tag for NumberOfDynamicScans
4255 #define kTemporalResolution 0x0020 + (0x0110 << 16) //DS
4256 #define kImagesInAcquisition 0x0020 + (0x1002 << 16) //IS
4257 //#define kSliceLocation 0x0020+(0x1041 << 16 ) //DS would be useful if not optional type 3
4258 #define kImageComments 0x0020 + (0x4000 << 16) // '0020' '4000' 'LT' 'ImageComments'
4259 #define kFrameContentSequence 0x0020 + uint32_t(0x9111 << 16) //SQ
4260 #define kTriggerDelayTime 0x0020 + uint32_t(0x9153 << 16) //FD
4261 #define kDimensionIndexValues 0x0020 + uint32_t(0x9157 << 16) // UL n-dimensional index of frame.
4262 #define kInStackPositionNumber 0x0020 + uint32_t(0x9057 << 16) // UL can help determine slices in volume
4263 #define kTemporalPositionIndex 0x0020 + uint32_t(0x9128 << 16) // UL
4264 #define kDimensionIndexPointer 0x0020 + uint32_t(0x9165 << 16)
4265 //Private Group 21 as Used by Siemens:
4266 #define kSequenceVariant21 0x0021 + (0x105B << 16) //CS
4267 #define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText
4268 #define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS
4269 #define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 16) //IS
4270 //#define kRealDwellTime 0x0021+(0x1142<< 16 )//IS
4271 #define kBandwidthPerPixelPhaseEncode21 0x0021 + (0x1153 << 16) //FD
4272 #define kCoilElements 0x0021 + (0x114F << 16) //LO
4273 #define kAcquisitionMatrixText21 0x0021 + (0x1158 << 16) //SH
4274 //Private Group 21 as used by GE:
4275 #define kLocationsInAcquisitionGE 0x0021 + (0x104F << 16) //SS 'LocationsInAcquisitionGE'
4276 #define kRTIA_timer 0x0021 + (0x105E << 16) //DS
4277 #define kProtocolDataBlockGE 0x0025 + (0x101B << 16) //OB
4278 #define kNumberOfPointsPerArm 0x0027 + (0x1060 << 16) //FL
4279 #define kNumberOfArms 0x0027 + (0x1061 << 16) //FL
4280 #define kNumberOfExcitations 0x0027 + (0x1062 << 16) //FL
4281 #define kSamplesPerPixel 0x0028 + (0x0002 << 16)
4282 #define kPhotometricInterpretation 0x0028 + (0x0004 << 16)
4283 #define kPlanarRGB 0x0028 + (0x0006 << 16)
4284 #define kDim3 0x0028 + (0x0008 << 16) //number of frames - for Philips this is Dim3*Dim4
4285 #define kDim2 0x0028 + (0x0010 << 16)
4286 #define kDim1 0x0028 + (0x0011 << 16)
4287 #define kXYSpacing 0x0028 + (0x0030 << 16) //DS 'PixelSpacing'
4288 #define kBitsAllocated 0x0028 + (0x0100 << 16)
4289 #define kBitsStored 0x0028 + (0x0101 << 16) //US 'BitsStored'
4290 #define kIsSigned 0x0028 + (0x0103 << 16) //PixelRepresentation
4291 #define kPixelPaddingValue 0x0028 + (0x0120 << 16) // https://github.com/rordenlab/dcm2niix/issues/262
4292 #define kFloatPixelPaddingValue 0x0028 + (0x0122 << 16) // https://github.com/rordenlab/dcm2niix/issues/262
4293 #define kIntercept 0x0028 + (0x1052 << 16)
4294 #define kSlope 0x0028 + (0x1053 << 16)
4295 //#define kRescaleType 0x0028+(0x1053 << 16 ) //LO e.g. for Philips Fieldmap: [Hz]
4296 //#define kSpectroscopyDataPointColumns 0x0028+(0x9002 << 16 ) //IS
4297 #define kGeiisFlag 0x0029 + (0x0010 << 16) //warn user if dreaded GEIIS was used to process image
4298 #define kCSAImageHeaderInfo 0x0029 + (0x1010 << 16)
4299 #define kCSASeriesHeaderInfo 0x0029 + (0x1020 << 16)
4300 #define kStudyComments 0x0032 + (0x4000 << 16) //LT StudyComments
4301 //#define kObjectGraphics 0x0029+(0x1210 << 16 ) //0029,1210 syngoPlatformOOGInfo Object Oriented Graphics
4302 #define kProcedureStepDescription 0x0040 + (0x0254 << 16)
4303 #define kRealWorldIntercept 0x0040 + uint32_t(0x9224 << 16) //IS dicm2nii's SlopInt_6_9
4304 #define kRealWorldSlope 0x0040 + uint32_t(0x9225 << 16) //IS dicm2nii's SlopInt_6_9
4305 #define kUserDefineDataGE 0x0043 + (0x102A << 16) //OB
4306 #define kEffectiveEchoSpacingGE 0x0043 + (0x102C << 16) //SS
4307 #define kImageTypeGE 0x0043 + (0x102F << 16) //SS 0/1/2/3 for magnitude/phase/real/imaginary
4308 #define kDiffusion_bValueGE 0x0043 + (0x1039 << 16) //IS dicm2nii's SlopInt_6_9
4309 #define kEpiRTGroupDelayGE 0x0043 + (0x107C << 16) //FL
4310 #define kAssetRFactorsGE 0x0043 + (0x1083 << 16) //DS
4311 #define kASLContrastTechniqueGE 0x0043 + (0x10A3 << 16) //CS
4312 #define kASLLabelingTechniqueGE 0x0043 + (0x10A4 << 16) //LO
4313 #define kDurationLabelPulseGE 0x0043 + (0x10A5 << 16) //IS
4314 #define kMultiBandGE 0x0043 + (0x10B6 << 16) //LO
4315 #define kAcquisitionMatrixText 0x0051 + (0x100B << 16) //LO
4316 #define kImageOrientationText 0x0051 + (0x100E << 16) //
4317 #define kCoilSiemens 0x0051 + (0x100F << 16)
4318 #define kImaPATModeText 0x0051 + (0x1011 << 16)
4319 #define kLocationsInAcquisition 0x0054 + (0x0081 << 16)
4320 #define kUnitsPT 0x0054 + (0x1001 << 16) //CS
4321 #define kAttenuationCorrectionMethod 0x0054 + (0x1101 << 16) //LO
4322 #define kDecayCorrection 0x0054 + (0x1102 << 16) //CS
4323 #define kReconstructionMethod 0x0054 + (0x1103 << 16) //LO
4324 #define kDecayFactor 0x0054 + (0x1321 << 16) //LO
4325 //ftp://dicom.nema.org/MEDICAL/dicom/2014c/output/chtml/part03/sect_C.8.9.4.html
4326 //If ImageType is REPROJECTION we slice direction is reversed - need example to test
4327 // #define kSeriesType 0x0054+(0x1000 << 16 )
4328 #define kDoseCalibrationFactor 0x0054 + (0x1322 << 16)
4329 #define kPETImageIndex 0x0054 + (0x1330 << 16)
4330 #define kPEDirectionDisplayedUIH 0x0065 + (0x1005 << 16) //SH
4331 #define kDiffusion_bValueUIH 0x0065 + (0x1009 << 16) //FD
4332 #define kParallelInformationUIH 0x0065 + (0x100D << 16) //SH
4333 #define kNumberOfImagesInGridUIH 0x0065 + (0x1050 << 16) //DS
4334 #define kDiffusionGradientDirectionUIH 0x0065 + (0x1037 << 16) //FD
4335 //#define kMRVFrameSequenceUIH 0x0065+(0x1050<< 16 ) //SQ
4336 #define kPhaseEncodingDirectionPositiveUIH 0x0065 + (0x1058 << 16) //IS issue410
4337 #define kIconImageSequence 0x0088 + (0x0200 << 16)
4338 #define kElscintIcon 0x07a3 + (0x10ce << 16) //see kGeiisFlag and https://github.com/rordenlab/dcm2niix/issues/239
4339 #define kPMSCT_RLE1 0x07a1 + (0x100a << 16) //Elscint/Philips compression
4340 #define kPrivateCreator 0x2001 + (0x0010 << 16) // LO (Private creator is any tag where group is odd and element is x0010-x00FF
4341 #define kDiffusion_bValuePhilips 0x2001 + (0x1003 << 16) // FL
4342 #define kPhaseNumber 0x2001 + (0x1008 << 16) //IS
4343 #define kCardiacSync 0x2001 + (0x1010 << 16) //CS
4344 //#define kDiffusionDirectionPhilips 0x2001+(0x1004 << 16 )//CS Diffusion Direction
4345 #define kSliceNumberMrPhilips 0x2001 + (0x100A << 16) //IS Slice_Number_MR
4346 #define kSliceOrient 0x2001 + (0x100B << 16) //2001,100B Philips slice orientation (TRANSVERSAL, AXIAL, SAGITTAL)
4347 #define kEPIFactorPhilips 0x2001 + (0x1013 << 16) //SL
4348 #define kPrepulseDelay 0x2001 + (0x101B << 16) //FL
4349 #define kPrepulseType 0x2001 + (0x101C << 16) //CS
4350 #define kRespirationSync 0x2001 + (0x101F << 16) //CS
4351 #define kNumberOfSlicesMrPhilips 0x2001 + (0x1018 << 16) //SL 0x2001, 0x1018 ), "Number_of_Slices_MR"
4352 #define kPartialMatrixScannedPhilips 0x2001 + (0x1019 << 16) // CS
4353 #define kWaterFatShiftPhilips 0x2001 + (0x1022 << 16) //FL
4354 //#define kMRSeriesAcquisitionNumber 0x2001+(0x107B << 16 ) //IS
4355 //#define kNumberOfLocationsPhilips 0x2001+(0x1015 << 16 ) //SS
4356 //#define kStackSliceNumber 0x2001+(0x1035 << 16 )//? Potential way to determine slice order for Philips?
4357 #define kNumberOfDynamicScans 0x2001 + (0x1081 << 16) //'2001' '1081' 'IS' 'NumberOfDynamicScans'
4358 #define kMRfMRIStatusIndicationPhilips 0x2005 + (0x1063 << 16)
4359 #define kMRAcquisitionTypePhilips 0x2005 + (0x106F << 16) //SS
4360 #define kAngulationAP 0x2005 + (0x1071 << 16) //'2005' '1071' 'FL' 'MRStackAngulationAP'
4361 #define kAngulationFH 0x2005 + (0x1072 << 16) //'2005' '1072' 'FL' 'MRStackAngulationFH'
4362 #define kAngulationRL 0x2005 + (0x1073 << 16) //'2005' '1073' 'FL' 'MRStackAngulationRL'
4363 #define kMRStackOffcentreAP 0x2005 + (0x1078 << 16)
4364 #define kMRStackOffcentreFH 0x2005 + (0x1079 << 16)
4365 #define kMRStackOffcentreRL 0x2005 + (0x107A << 16)
4366 #define kPhilipsSlope 0x2005 + (0x100E << 16)
4367 #define kMRImageDynamicScanBeginTime 0x2005 + (0x10a0 << 16) //FL
4368 #define kDiffusionDirectionRL 0x2005 + (0x10B0 << 16)
4369 #define kDiffusionDirectionAP 0x2005 + (0x10B1 << 16)
4370 #define kDiffusionDirectionFH 0x2005 + (0x10B2 << 16)
4371 #define kPrivatePerFrameSq 0x2005 + (0x140F << 16)
4372 #define kMRImageDiffBValueNumber 0x2005 + (0x1412 << 16) //IS
4373 #define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS
4374 #define kMRImageLabelType 0x2005 + (0x1429 << 16) //CS ASL LBL_CTL https://github.com/physimals/dcm_convert_phillips/
4375 #define kMRImageDiffVolumeNumber 0x2005+(0x1596 << 16) //IS
4376 #define kSharedFunctionalGroupsSequence 0x5200 + uint32_t(0x9229 << 16) // SQ
4377 #define kPerFrameFunctionalGroupsSequence 0x5200 + uint32_t(0x9230 << 16) // SQ
4378 #define kWaveformSq 0x5400 + (0x0100 << 16)
4379 #define kSpectroscopyData 0x5600 + (0x0020 << 16) //OF
4380 #define kImageStart 0x7FE0 + (0x0010 << 16)
4381 #define kImageStartFloat 0x7FE0 + (0x0008 << 16)
4382 #define kImageStartDouble 0x7FE0 + (0x0009 << 16)
4383 uint32_t kItemTag = 0xFFFE + (0xE000 << 16);
4384 uint32_t kItemDelimitationTag = 0xFFFE + (0xE00D << 16);
4385 uint32_t kSequenceDelimitationItemTag = 0xFFFE + (0xE0DD << 16);
4386 #define salvageAgfa
4387 #ifdef salvageAgfa //issue435
4388 // handle PrivateCreator renaming e.g. 0021,10xx -> 0021,11xx
4389 // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl
4390 // https://github.com/neurolabusc/dcm_qa_agfa
4391 // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.8.html
4392 #define kMaxRemaps 16 //no vendor uses more than 5 private creator groups
4393 //we need to keep track of multiple remappings, e.g. issue 437 2005,0014->2005,0012; 2005,0015->2005,0011
4394 int nRemaps = 0;
4395 uint32_t privateCreatorMasks[kMaxRemaps]; //0 -> none
4396 uint32_t privateCreatorRemaps[kMaxRemaps]; //0 -> none
4397 #endif
4398 double TE = 0.0; //most recent echo time recorded
4399 float temporalResolutionMS = 0.0;
4400 float MRImageDynamicScanBeginTime = 0.0;
4401 bool is2005140FSQ = false;
4402 bool overlayOK = true;
4403 int userData12GE = 0;
4404 int overlayRows = 0;
4405 int overlayCols = 0;
4406 bool isNeologica = false;
4407 bool isTriggerSynced = false;
4408 bool isProspectiveSynced = false;
4409 bool isDICOMANON = false; //issue383
4410 bool isMATLAB = false; //issue383
4411 bool isASL = false;
4412 //double contentTime = 0.0;
4413 int echoTrainLengthPhil = 0;
4414 int philMRImageDiffBValueNumber = 0;
4415 int philMRImageDiffVolumeNumber = -1;
4416 int sqDepth = 0;
4417 int acquisitionTimesGE_UIH = 0;
4418 int sqDepth00189114 = -1;
4419 bool hasDwiDirectionality = false;
4420 //float sliceLocation = INFINITY; //useless since this tag is optional
4421 //int numFirstPatientPosition = 0;
4422 int nDimIndxVal = -1; //tracks Philips kDimensionIndexValues
4423 int locationsInAcquisitionGE = 0;
4424 int PETImageIndex = 0;
4425 int inStackPositionNumber = 0;
4426 bool isKludgeIssue533 = false;
4427 uint32_t dimensionIndexPointer[MAX_NUMBER_OF_DIMENSIONS];
4428 size_t dimensionIndexPointerCounter = 0;
4429 int maxInStackPositionNumber = 0;
4430 int temporalPositionIndex = 0;
4431 int maxTemporalPositionIndex = 0;
4432 //int temporalPositionIdentifier = 0;
4433 int locationsInAcquisitionPhilips = 0;
4434 int imagesInAcquisition = 0;
4435 //int sumSliceNumberMrPhilips = 0;
4436 int sliceNumberMrPhilips = 0;
4437 int volumeNumber = -1;
4438 int gradientOrientationNumberPhilips = -1;
4439 int numberOfFrames = 0;
4440 //int MRImageGradientOrientationNumber = 0;
4441 //int minGradNum = kMaxDTI4D + 1;
4442 //int maxGradNum = -1;
4443 int numberOfDynamicScans = 0;
4444 //int mRSeriesAcquisitionNumber = 0;
4445 uint32_t lLength;
4446 uint32_t groupElement;
4447 long lPos = 0;
4448 bool isPhilipsDerived = false;
4449 //bool isPhilipsDiffusion = false;
4450 if (isPart10prefix) { //for part 10 files, skip preamble and prefix
4451 lPos = 128 + 4; //4-byte signature starts at 128
4452 groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
4453 if (groupElement != kStart)
4454 printMessage("DICOM appears corrupt: first group:element should be 0x0002:0x0000 '%s'\n", fname);
4455 } else { //no isPart10prefix - need to work out if this is explicit VR!
4456 if (isVerbose > 1)
4457 printMessage("DICOM preamble and prefix missing: this is not a valid DICOM image.\n");
4458 //See Toshiba Aquilion images from https://www.aliza-dicom-viewer.com/download/datasets
4459 lLength = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24);
4460 if (lLength > fileLen) {
4461 if (isVerbose > 1)
4462 printMessage("Guessing this is an explicit VR image.\n");
4463 d.isExplicitVR = true;
4464 }
4465 }
4466 char vr[2];
4467 //float intenScalePhilips = 0.0;
4468 char seriesTimeTxt[kDICOMStr] = "";
4469 char acquisitionDateTimeTxt[kDICOMStr] = "";
4470 char imageType1st[kDICOMStr] = "";
4471 bool isEncapsulatedData = false;
4472 int multiBandFactor = 0;
4473 int frequencyRows = 0;
4474 int numberOfImagesInMosaic = 0;
4475 int encapsulatedDataFragments = 0;
4476 int encapsulatedDataFragmentStart = 0; //position of first FFFE,E000 for compressed images
4477 int encapsulatedDataImageStart = 0; //position of 7FE0,0010 for compressed images (where actual image start should be start of first fragment)
4478 bool isOrient = false;
4479 //bool isDcm4Che = false;
4480 bool isMoCo = false;
4481 bool isPaletteColor = false;
4482 bool isInterpolated = false;
4483 bool isIconImageSequence = false;
4484 int sqDepthIcon = -1;
4485 bool isSwitchToImplicitVR = false;
4486 bool isSwitchToBigEndian = false;
4487 bool isAtFirstPatientPosition = false; //for 3d and 4d files: flag is true for slices at same position as first slice
4488 bool isMosaic = false;
4489 bool isGEfieldMap = false; //issue501
4490 int patientPositionNum = 0;
4491 float B0Philips = -1.0;
4492 float vRLPhilips = 0.0;
4493 float vAPPhilips = 0.0;
4494 float vFHPhilips = 0.0;
4495 bool isPhase = false;
4496 bool isReal = false;
4497 bool isImaginary = false;
4498 bool isMagnitude = false;
4499 d.seriesNum = -1;
4500 //start issue 372:
4501 vec3 sliceV; //cross-product of kOrientation 0020,0037
4502 sliceV.v[0] = NAN;
4503 float sliceMM[kMaxSlice2D];
4504 int nSliceMM = 0;
4505 float minSliceMM = INFINITY;
4506 float maxSliceMM = -INFINITY;
4507 float minDynamicScanBeginTime = INFINITY;
4508 float maxDynamicScanBeginTime = -INFINITY;
4509 float minPatientPosition[4] = {NAN, NAN, NAN, NAN};
4510 float maxPatientPosition[4] = {NAN, NAN, NAN, NAN};
4511 //end issue 372
4512 //float frameAcquisitionDuration = 0.0; //issue369
4513 float patientPositionPrivate[4] = {NAN, NAN, NAN, NAN};
4514 float patientPosition[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D
4515 //float patientPositionPublic[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D
4516 float patientPositionEndPhilips[4] = {NAN, NAN, NAN, NAN};
4517 float patientPositionStartPhilips[4] = {NAN, NAN, NAN, NAN};
4518 //struct TDTI philDTI[kMaxDTI4D];
4519 //for (int i = 0; i < kMaxDTI4D; i++)
4520 // philDTI[i].V[0] = -1;
4521 //array for storing DimensionIndexValues
4522 int numDimensionIndexValues = 0;
4523 #ifdef USING_R
4524 // Allocating a large array on the stack, as below, vexes valgrind and may cause overflow
4525 std::vector<TDCMdim> dcmDim(kMaxSlice2D);
4526 #else
4527 TDCMdim dcmDim[kMaxSlice2D];
4528 #endif
4529 for (int i = 0; i < kMaxSlice2D; i++) {
4530 dcmDim[i].diskPos = i;
4531 for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++)
4532 dcmDim[i].dimIdx[j] = 0;
4533 }
4534 //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html
4535 //The array nestPos tracks explicit lengths for Data Element Tag of Value (FFFE,E000)
4536 //a delimiter (fffe,e000) can have an explicit length, in which case there is no delimiter (fffe,e00d)
4537 // fffe,e000 can provide explicit lengths, to demonstrate ./dcmconv +ti ex.DCM im.DCM
4538 #define kMaxNestPost 128
4539 int nNestPos = 0;
4540 size_t nestPos[kMaxNestPost];
4541 while ((d.imageStart == 0) && ((lPos + 8 + lFileOffset) < fileLen)) {
4542 #ifndef myLoadWholeFileToReadHeader //read one segment at a time
4543 if ((size_t)(lPos + 128) > MaxBufferSz) { //avoid overreading the file
4544 lFileOffset = lFileOffset + lPos;
4545 if ((lFileOffset + MaxBufferSz) > (size_t)fileLen)
4546 MaxBufferSz = fileLen - lFileOffset;
4547 fseek(file, lFileOffset, SEEK_SET);
4548 size_t sz = fread(buffer, 1, MaxBufferSz, file);
4549 if (sz < MaxBufferSz) {
4550 printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname);
4551 fclose(file);
4552 return d;
4553 }
4554 lPos = 0;
4555 }
4556 #endif
4557 if (d.isLittleEndian)
4558 groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
4559 else
4560 groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24);
4561 if ((isSwitchToBigEndian) && ((groupElement & 0xFFFF) != 2)) {
4562 isSwitchToBigEndian = false;
4563 d.isLittleEndian = false;
4564 groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24);
4565 } //transfer syntax requests switching endian after group 0002
4566 if ((isSwitchToImplicitVR) && ((groupElement & 0xFFFF) != 2)) {
4567 isSwitchToImplicitVR = false;
4568 d.isExplicitVR = false;
4569 } //transfer syntax requests switching VR after group 0001
4570 //uint32_t group = (groupElement & 0xFFFF);
4571 lPos += 4;
4572 //issue409 - icons can have their own sub-sections... keep reading until we get to the icon image?
4573 //if ((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) isIconImageSequence = false;
4574 //if (groupElement == kItemTag) sqDepth++;
4575 bool unNest = false;
4576 while ((nNestPos > 0) && (nestPos[nNestPos] <= (lFileOffset + lPos))) {
4577 nNestPos--;
4578 sqDepth--;
4579 unNest = true;
4580 if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415
4581 sqDepthIcon = -1;
4582 isIconImageSequence = false;
4583 }
4584 }
4585 if (groupElement == kItemDelimitationTag) { //end of item with undefined length
4586 sqDepth--;
4587 unNest = true;
4588 if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415
4589 sqDepthIcon = -1;
4590 isIconImageSequence = false;
4591 }
4592 }
4593 if (unNest) {
4594 is2005140FSQ = false;
4595 if (sqDepth < 0)
4596 sqDepth = 0; //should not happen, but protect for faulty anonymization
4597 //if we leave the folder MREchoSequence 0018,9114
4598 if ((nDimIndxVal > 0) && ((d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_BRUKER) || (d.manufacturer == kMANUFACTURER_PHILIPS)) && (sqDepth00189114 >= sqDepth)) {
4599 sqDepth00189114 = -1; //triggered
4600 //printf("slice %d---> 0020,9157 = %d %d %d\n", inStackPositionNumber, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2]);
4601 // d.aslFlags = kASL_FLAG_PHILIPS_LABEL; kASL_FLAG_PHILIPS_LABEL
4602 if ((nDimIndxVal > 1) && (volumeNumber > 0) && (inStackPositionNumber > 0) && (d.aslFlags == kASL_FLAG_PHILIPS_LABEL) || (d.aslFlags == kASL_FLAG_PHILIPS_CONTROL)) {
4603 isKludgeIssue533 = true;
4604 for (int i = 0; i < nDimIndxVal; i++)
4605 d.dimensionIndexValues[i] = 0;
4606 int phase = d.phaseNumber;
4607 if (d.phaseNumber < 0) phase = 0; //if not set: we are saving as UINT
4608 d.dimensionIndexValues[0] = inStackPositionNumber; //dim[3] slice changes fastest
4609 d.dimensionIndexValues[1] = phase; //dim[4] successive volumes are phase
4610 d.dimensionIndexValues[2] = d.aslFlags == kASL_FLAG_PHILIPS_LABEL; //dim[5] Control/Label
4611 d.dimensionIndexValues[3] = volumeNumber; //dim[6] Repeat changes slowest
4612 nDimIndxVal = 4; //slice < phase < control/label < volume
4613 //printf("slice %d phase %d control/label %d repeat %d\n", inStackPositionNumber, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL, volumeNumber);
4614 }
4615 int ndim = nDimIndxVal;
4616 if (inStackPositionNumber > 0) {
4617 //for images without SliceNumberMrPhilips (2001,100A)
4618 int sliceNumber = inStackPositionNumber;
4619 //printf("slice %d \n", sliceNumber);
4620 if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) {
4621 for (int k = 0; k < 4; k++)
4622 patientPositionStartPhilips[k] = patientPosition[k];
4623 } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) {
4624 for (int k = 0; k < 4; k++)
4625 patientPositionStartPhilips[k] = patientPositionPrivate[k];
4626 }
4627 if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPosition[1]))) {
4628 for (int k = 0; k < 4; k++)
4629 patientPositionEndPhilips[k] = patientPosition[k];
4630 } else if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPositionPrivate[1]))) {
4631 for (int k = 0; k < 4; k++)
4632 patientPositionEndPhilips[k] = patientPositionPrivate[k];
4633 }
4634 patientPosition[1] = NAN;
4635 patientPositionPrivate[1] = NAN;
4636 }
4637 inStackPositionNumber = 0;
4638 if (numDimensionIndexValues >= kMaxSlice2D) {
4639 printError("Too many slices to track dimensions. Only up to %d are supported\n", kMaxSlice2D);
4640 break;
4641 }
4642 uint32_t dimensionIndexOrder[MAX_NUMBER_OF_DIMENSIONS];
4643 for (size_t i = 0; i < nDimIndxVal; i++)
4644 dimensionIndexOrder[i] = i;
4645 // Bruker Enhanced MR IOD: reorder dimensions to ensure InStackPositionNumber corresponds to the first one
4646 // This will ensure correct ordering of slices in 4D datasets
4647 //Canon and Bruker reverse dimensionIndexItem order relative to Philips: new versions introduce compareTDCMdimRev
4648 //printf("%d: %d %d %d %d\n", ndim, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2], d.dimensionIndexValues[3]);
4649 if ((philMRImageDiffVolumeNumber > 0) && (sliceNumberMrPhilips > 0)) { //issue546: 2005,1596 provides temporal order
4650 dcmDim[numDimensionIndexValues].dimIdx[0] = 1;
4651 dcmDim[numDimensionIndexValues].dimIdx[1] = sliceNumberMrPhilips;
4652 dcmDim[numDimensionIndexValues].dimIdx[2] = philMRImageDiffVolumeNumber;
4653 } else {
4654 for (int i = 0; i < ndim; i++)
4655 dcmDim[numDimensionIndexValues].dimIdx[i] = d.dimensionIndexValues[dimensionIndexOrder[i]];
4656 }
4657 dcmDim[numDimensionIndexValues].TE = TE;
4658 dcmDim[numDimensionIndexValues].intenScale = d.intenScale;
4659 dcmDim[numDimensionIndexValues].intenIntercept = d.intenIntercept;
4660 dcmDim[numDimensionIndexValues].isPhase = isPhase;
4661 dcmDim[numDimensionIndexValues].isReal = isReal;
4662 dcmDim[numDimensionIndexValues].isImaginary = isImaginary;
4663 dcmDim[numDimensionIndexValues].intenScalePhilips = d.intenScalePhilips;
4664 dcmDim[numDimensionIndexValues].RWVScale = d.RWVScale;
4665 dcmDim[numDimensionIndexValues].RWVIntercept = d.RWVIntercept;
4666 //printf("%d %d %g????\n", isTriggerSynced, isProspectiveSynced, d.triggerDelayTime);
4667 //TODO533: isKludgeIssue533 alias Philips ASL as FrameDuration?
4668 //if ((d.triggerDelayTime > 0.0) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.aslFlags != kASL_FLAG_NONE))
4669 //printf(">>>%g\n", d.triggerDelayTime);
4670 //if ((isASL) || (d.aslFlags != kASL_FLAG_NONE)) d.triggerDelayTime = 0.0; //see dcm_qa_philips_asl
4671 //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408
4672 // d.triggerDelayTime = 0.0;
4673 if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime) )
4674 dcmDim[numDimensionIndexValues].triggerDelayTime = 0.0; //issue395
4675 else
4676 dcmDim[numDimensionIndexValues].triggerDelayTime = d.triggerDelayTime;
4677 dcmDim[numDimensionIndexValues].V[0] = -1.0;
4678 #ifdef MY_DEBUG
4679 if (numDimensionIndexValues < 19) {
4680 printMessage("dimensionIndexValues0020x9157[%d] = [", numDimensionIndexValues);
4681 for (int i = 0; i < ndim; i++)
4682 printMessage("%d ", d.dimensionIndexValues[i]);
4683 printMessage("]\n");
4684 //printMessage("B0= %g num=%d\n", B0Philips, gradNum);
4685 } else
4686 return d;
4687 #endif
4688 //next: add diffusion if reported
4689 if (B0Philips >= 0.0) { //diffusion parameters
4690 // Philips does not always provide 2005,1413 (MRImageGradientOrientationNumber) and sometimes after dimensionIndexValues
4691 /*int gradNum = 0;
4692 for (int i = 0; i < ndim; i++)
4693 if (d.dimensionIndexValues[i] > 0) gradNum = d.dimensionIndexValues[i];
4694 if (gradNum <= 0) break;
4695 With Philips 51.0 both ADC and B=0 are saved as same direction, though they vary in another dimension
4696 (0018,9075) CS [ISOTROPIC]
4697 (0020,9157) UL 1\2\1\33 << ADC MAP
4698 (0018,9075) CS [NONE]
4699 (0020,9157) UL 1\1\2\33
4700 next two lines attempt to skip ADC maps
4701 we could also increment gradNum for ADC if we wanted...
4702 */
4703 if (isPhilipsDerived) {
4704 //gradNum ++;
4705 B0Philips = 2000.0;
4706 vRLPhilips = 0.0;
4707 vAPPhilips = 0.0;
4708 vFHPhilips = 0.0;
4709 }
4710 if (B0Philips == 0.0) {
4711 //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips);
4712 vRLPhilips = 0.0;
4713 vAPPhilips = 0.0;
4714 vFHPhilips = 0.0;
4715 }
4716 //if ((MRImageGradientOrientationNumber > 0) && ((gradNum != MRImageGradientOrientationNumber)) break;
4717 /*if (gradNum < minGradNum) minGradNum = gradNum;
4718 if (gradNum >= maxGradNum) maxGradNum = gradNum;
4719 if (gradNum >= kMaxDTI4D) {
4720 printError("Number of DTI gradients exceeds 'kMaxDTI4D (%d).\n", kMaxDTI4D);
4721 } else {
4722 gradNum = gradNum - 1; //index from 0
4723 philDTI[gradNum].V[0] = B0Philips;
4724 philDTI[gradNum].V[1] = vRLPhilips;
4725 philDTI[gradNum].V[2] = vAPPhilips;
4726 philDTI[gradNum].V[3] = vFHPhilips;
4727 }*/
4728 dcmDim[numDimensionIndexValues].V[0] = B0Philips;
4729 dcmDim[numDimensionIndexValues].V[1] = vRLPhilips;
4730 dcmDim[numDimensionIndexValues].V[2] = vAPPhilips;
4731 dcmDim[numDimensionIndexValues].V[3] = vFHPhilips;
4732 isPhilipsDerived = false;
4733 //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips);
4734 //!!! 16032018 : next line as well as definition of B0Philips may need to be set to zero if Philips omits DiffusionBValue tag for B=0
4735 B0Philips = -1.0; //Philips may skip reporting B-values for B=0 volumes, so zero these
4736 vRLPhilips = 0.0;
4737 vAPPhilips = 0.0;
4738 vFHPhilips = 0.0;
4739 //MRImageGradientOrientationNumber = 0;
4740 } //diffusion parameters
4741 numDimensionIndexValues++;
4742 nDimIndxVal = -1; //we need DimensionIndexValues
4743 } //record dimensionIndexValues slice information
4744 } //groupElement == kItemDelimitationTag : delimit item exits folder
4745 if (groupElement == kItemTag) {
4746 uint32_t slen = dcmInt(4, &buffer[lPos], d.isLittleEndian);
4747 uint32_t kUndefinedLen = 0xFFFFFFFF;
4748 if (slen != kUndefinedLen) {
4749 nNestPos++;
4750 if (nNestPos >= kMaxNestPost)
4751 nNestPos = kMaxNestPost - 1;
4752 nestPos[nNestPos] = slen + lFileOffset + lPos;
4753 }
4754 lLength = 4;
4755 sqDepth++;
4756 //return d;
4757 } else if (((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) && (!isEncapsulatedData)) {
4758 vr[0] = 'N';
4759 vr[1] = 'A';
4760 lLength = 4;
4761 } else if (d.isExplicitVR) {
4762 vr[0] = buffer[lPos];
4763 vr[1] = buffer[lPos + 1];
4764 if (buffer[lPos + 1] < 'A') { //implicit vr with 32-bit length
4765 if (d.isLittleEndian)
4766 lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
4767 else
4768 lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24);
4769 lPos += 4;
4770 } 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'))
4771 //for example of UC/UR/UV/OD/OF/OL/OV/SV see VR conformance test https://www.aliza-dicom-viewer.com/download/datasets
4772 lPos = lPos + 4; //skip 2 byte VR string and 2 reserved bytes = 4 bytes
4773 if (d.isLittleEndian)
4774 lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
4775 else
4776 lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24);
4777 lPos = lPos + 4; //skip 4 byte length
4778 } else if ((buffer[lPos] == 'S') && (buffer[lPos + 1] == 'Q')) {
4779 lLength = 8; //Sequence Tag
4780 //printMessage(" !!!SQ\t%04x,%04x\n", groupElement & 65535,groupElement>>16);
4781 } else { //explicit VR with 16-bit length
4782 if ((d.isLittleEndian))
4783 lLength = buffer[lPos + 2] | (buffer[lPos + 3] << 8);
4784 else
4785 lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8);
4786 lPos += 4; //skip 2 byte VR string and 2 length bytes = 4 bytes
4787 }
4788 } else { //implicit VR
4789 vr[0] = 'U';
4790 vr[1] = 'N';
4791 if (d.isLittleEndian)
4792 lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24);
4793 else
4794 lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24);
4795 lPos += 4; //we have loaded the 32-bit length
4796 if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144
4797 vr[0] = 'S';
4798 vr[1] = 'Q';
4799 lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced
4800 }
4801 if ((d.manufacturer != kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144
4802 vr[0] = 'S';
4803 vr[1] = 'Q';
4804 lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced
4805 }
4806 } //if explicit else implicit VR
4807 if (lLength == 0xFFFFFFFF) {
4808 lLength = 8; //SQ (Sequences) use 0xFFFFFFFF [4294967295] to denote unknown length
4809 //09032018 - do not count these as SQs: Horos does not count even groups
4810 //uint32_t special = dcmInt(4,&buffer[lPos],d.isLittleEndian);
4811 //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html
4812 //if (special != ksqDelim) {
4813 vr[0] = 'S';
4814 vr[1] = 'Q';
4815 //}
4816 }
4817 if ((groupElement == kItemTag) && (isEncapsulatedData)) { //use this to find image fragment for compressed datasets, e.g. JPEG transfer syntax
4818 d.imageBytes = dcmInt(4, &buffer[lPos], d.isLittleEndian);
4819 lPos = lPos + 4;
4820 lLength = d.imageBytes;
4821 if (d.imageBytes > 128) {
4822 /*if (encapsulatedDataFragments < kMaxDTI4D) {
4823 dti4D->fragmentOffset[encapsulatedDataFragments] = (int)lPos + (int)lFileOffset;
4824 dti4D->fragmentLength[encapsulatedDataFragments] = lLength;
4825 }*/
4826 encapsulatedDataFragments++;
4827 if (encapsulatedDataFragmentStart == 0)
4828 encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset;
4829 }
4830 }
4831 if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028))
4832 groupElement = kUnused; //ignore icon dimensions
4833 #ifdef salvageAgfa //issue435
4834 //Handle remapping using integers, and slower but simpler approach is with strings:
4835 // https://github.com/pydicom/pydicom/blob/master/pydicom/_private_dict.py
4836 if (((groupElement & 65535) % 2) == 0)
4837 goto skipRemap; //remap odd (private) groups
4838 //printf("tag %04x,%04x\n", groupElement & 65535, groupElement >> 16);
4839 if (((groupElement >> 16) >= 0x10) && ((groupElement >> 16) <= 0xFF)) { //tags (gggg,0010-00FF) may define new remapping
4840 //if remapping tag
4841 //first: see if this remapping overwrites existing tag
4842 uint32_t privateCreatorMask = 0; //0 -> none
4843 uint32_t privateCreatorRemap = 0; //0 -> none
4844 privateCreatorMask = (groupElement & 65535) + ((groupElement & 0xFFFF0000) << 8);
4845 if (nRemaps > 0) {
4846 int j = 0;
4847 for (int i = 0; i < nRemaps; i++) //remove duplicate remapping
4848 //copy all remaps except exact match
4849 if (privateCreatorMasks[i] != privateCreatorMask) {
4850 privateCreatorMasks[j] = privateCreatorMasks[i];
4851 privateCreatorRemaps[j] = privateCreatorRemaps[i];
4852 j++;
4853 }
4854 nRemaps = j;
4855 }
4856 //see if this is known private vendor tag
4857 privateCreatorRemap = 0;
4858 char privateCreator[kDICOMStr];
4859 dcmStr(lLength, &buffer[lPos], privateCreator);
4860 //next lines determine remapping, append as needed
4861 //Siemens https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl
4862 if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL)
4863 privateCreatorRemap = 0x0019 + (0x1000 << 16);
4864 if (strstr(privateCreator, "SIEMENS MR SDS 01") != NULL)
4865 privateCreatorRemap = 0x0021 + (0x1000 << 16);
4866 if (strstr(privateCreator, "SIEMENS MR SDI 02") != NULL)
4867 privateCreatorRemap = 0x0021 + (0x1100 << 16);
4868 if (strstr(privateCreator, "SIEMENS CSA HEADER") != NULL)
4869 privateCreatorRemap = 0x0029 + (0x1000 << 16);
4870 if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL)
4871 privateCreatorRemap = 0x0051 + (0x1000 << 16);
4872 //GE https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/gems.tpl
4873 if (strstr(privateCreator, "GEMS_ACQU_01") != NULL)
4874 privateCreatorRemap = 0x0019 + (0x1000 << 16);
4875 if (strstr(privateCreator, "GEMS_RELA_01") != NULL)
4876 privateCreatorRemap = 0x0021 + (0x1000 << 16);
4877 if (strstr(privateCreator, "GEMS_SERS_01") != NULL)
4878 privateCreatorRemap = 0x0025 + (0x1000 << 16);
4879 if (strstr(privateCreator, "GEMS_PARM_01") != NULL)
4880 privateCreatorRemap = 0x0043 + (0x1000 << 16);
4881 //ELSCINT https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/elscint.tpl
4882 int grp = (groupElement & 65535);
4883 if ((grp == 0x07a1) && (strstr(privateCreator, "ELSCINT1") != NULL))
4884 privateCreatorRemap = 0x07a1 + (0x1000 << 16);
4885 if ((grp == 0x07a3) && (strstr(privateCreator, "ELSCINT1") != NULL))
4886 privateCreatorRemap = 0x07a3 + (0x1000 << 16);
4887 //Philips https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/philips.tpl
4888 if (strstr(privateCreator, "PHILIPS IMAGING DD 001") != NULL)
4889 privateCreatorRemap = 0x2001 + (0x1000 << 16);
4890 if (strstr(privateCreator, "Philips Imaging DD 001") != NULL)
4891 privateCreatorRemap = 0x2001 + (0x1000 << 16);
4892 if (strstr(privateCreator, "PHILIPS MR IMAGING DD 001") != NULL)
4893 privateCreatorRemap = 0x2005 + (0x1000 << 16);
4894 if (strstr(privateCreator, "Philips MR Imaging DD 001") != NULL)
4895 privateCreatorRemap = 0x2005 + (0x1000 << 16);
4896 if (strstr(privateCreator, "PHILIPS MR IMAGING DD 005") != NULL)
4897 privateCreatorRemap = 0x2005 + (0x1400 << 16);
4898 if (strstr(privateCreator, "Philips MR Imaging DD 005") != NULL)
4899 privateCreatorRemap = 0x2005 + (0x1400 << 16);
4900 //UIH https://github.com/neurolabusc/dcm_qa_uih
4901 if (strstr(privateCreator, "Image Private Header") != NULL)
4902 privateCreatorRemap = 0x0065 + (0x1000 << 16);
4903 //sanity check: group should match
4904 if (grp != (privateCreatorRemap & 65535))
4905 privateCreatorRemap = 0;
4906 if (privateCreatorRemap == 0)
4907 goto skipRemap; //this is not a known private group
4908 if (privateCreatorRemap == privateCreatorMask)
4909 goto skipRemap; //the remapping and mask are identical 2005,1000 -> 2005,1000
4910 if ((nRemaps + 1) >= kMaxRemaps)
4911 goto skipRemap; //all slots full (should never happen)
4912 //add new remapping
4913 privateCreatorMasks[nRemaps] = privateCreatorMask;
4914 privateCreatorRemaps[nRemaps] = privateCreatorRemap;
4915 //printf("new remapping %04x,%04x -> %04x,%04x\n", privateCreatorMask & 65535, privateCreatorMask >> 16, privateCreatorRemap & 65535, privateCreatorRemap >> 16);
4916 if (isVerbose > 1)
4917 printMessage("new remapping (%d) %04x,%02xxy -> %04x,%02xxy\n", nRemaps, privateCreatorMask & 65535, privateCreatorMask >> 24, privateCreatorRemap & 65535, privateCreatorRemap >> 24);
4918 nRemaps += 1;
4919 //for (int i = 0; i < nRemaps; i++)
4920 // printf(" %d = %04x,%02xxy -> %04x,%02xxy\n", i, privateCreatorMasks[i] & 65535, privateCreatorMasks[i] >> 24, privateCreatorRemaps[i] & 65535, privateCreatorRemaps[i] >> 24);
4921 goto skipRemap;
4922 }
4923 if (nRemaps < 1)
4924 goto skipRemap;
4925 {
4926 uint32_t remappedGroupElement = 0;
4927 for (int i = 0; i < nRemaps; i++)
4928 if ((groupElement & 0xFF00FFFF) == (privateCreatorMasks[i] & 0xFF00FFFF))
4929 remappedGroupElement = privateCreatorRemaps[i] + (groupElement & 0x00FF0000);
4930 if (remappedGroupElement == 0)
4931 goto skipRemap;
4932 if (isVerbose > 1)
4933 printMessage("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16);
4934 groupElement = remappedGroupElement;
4935 }
4936 skipRemap:
4937 #endif // salvageAgfa
4938 if ((lLength % 2) != 0) { //https://www.nitrc.org/forum/forum.php?thread_id=11827&forum_id=4703
4939 printMessage("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535, groupElement >> 16, lLength, fname);
4940 //proper to return here, but we can carry on as a hail mary
4941 // d.isValid = false;
4942 //return d;
4943 }
4944 switch (groupElement) {
4945 case kMediaStorageSOPClassUID: {
4946 char mediaUID[kDICOMStr];
4947 dcmStr(lLength, &buffer[lPos], mediaUID);
4948 //Philips "XX_" files
4949 //see https://github.com/rordenlab/dcm2niix/issues/328
4950 if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66") != NULL)
4951 d.isRawDataStorage = true;
4952 if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.1") != NULL)
4953 d.isRawDataStorage = true; //Private MR Spectrum Storage
4954 if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.2") != NULL)
4955 d.isRawDataStorage = true; //Private MR Series Data Storage
4956 if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.4") != NULL)
4957 d.isRawDataStorage = true; //Private MR Examcard Storage
4958 if (d.isRawDataStorage)
4959 d.isDerived = true;
4960 if (d.isRawDataStorage)
4961 printMessage("Skipping non-image DICOM: %s\n", fname);
4962 //Philips "PS_" files
4963 if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.11.1") != NULL)
4964 d.isGrayscaleSoftcopyPresentationState = true;
4965 if (d.isGrayscaleSoftcopyPresentationState)
4966 d.isDerived = true;
4967 break;
4968 }
4969 case kMediaStorageSOPInstanceUID: { // 0002, 0003
4970 //char SOPInstanceUID[kDICOMStr];
4971 dcmStr(lLength, &buffer[lPos], d.instanceUID);
4972 //printMessage(">>%s\n", d.seriesInstanceUID);
4973 d.instanceUidCrc = mz_crc32X((unsigned char *)&d.instanceUID, strlen(d.instanceUID));
4974 break;
4975 }
4976 case kTransferSyntax: {
4977 char transferSyntax[kDICOMStr];
4978 strcpy(transferSyntax, "");
4979 dcmStr(lLength, &buffer[lPos], transferSyntax);
4980 if (strcmp(transferSyntax, "1.2.840.10008.1.2.1") == 0)
4981 ; //default isExplicitVR=true; //d.isLittleEndian=true
4982 else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.50") == 0) {
4983 d.compressionScheme = kCompress50;
4984 //printMessage("Lossy JPEG: please decompress with Osirix or dcmdjpg. %s\n", transferSyntax);
4985 //d.imageStart = 1; //abort as invalid (imageStart MUST be >128)
4986 } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.51") == 0) {
4987 d.compressionScheme = kCompress50;
4988 } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.57") == 0) {
4989 //d.isCompressed = true;
4990 //https://www.medicalconnections.co.uk/kb/Transfer_Syntax should be SOF = 0xC3
4991 d.compressionScheme = kCompressC3;
4992 //printMessage("Ancient JPEG-lossless (SOF type 0xc3): please check conversion\n");
4993 } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.70") == 0) {
4994 d.compressionScheme = kCompressC3;
4995 } else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.4.80") == 0) || (strcmp(transferSyntax, "1.2.840.10008.1.2.4.81") == 0)) {
4996 #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1)
4997 d.compressionScheme = kCompressJPEGLS;
4998 #else
4999 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);
5000 d.imageStart = 1; //abort as invalid (imageStart MUST be >128)
5001 #endif
5002 } else if (strcmp(transferSyntax, "1.3.46.670589.33.1.4.1") == 0) {
5003 d.compressionScheme = kCompressPMSCT_RLE1;
5004 //printMessage("Unsupported transfer syntax '%s' (decode with rle2img)\n",transferSyntax);
5005 //d.imageStart = 1; //abort as invalid (imageStart MUST be >128)
5006 } else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.90") == 0)) {
5007 d.compressionScheme = kCompressYes;
5008 //printMessage("JPEG2000 Lossless support is new: please validate conversion\n");
5009 } else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.1.99") == 0)) {
5010 //n.b. Deflate compression applied applies to the encoding of the **entire** DICOM Data Set, not just image data
5011 // see https://www.medicalconnections.co.uk/kb/Transfer-Syntax/
5012 //#ifndef myDisableZLib
5013 //d.compressionScheme = kCompressDeflate;
5014 //#else
5015 printWarning("Unsupported transfer syntax '%s' (inflate files with 'dcmconv +te gz.dcm raw.dcm' or 'gdcmconv -w gz.dcm raw.dcm)'\n", transferSyntax);
5016 d.imageStart = 1; //abort as invalid (imageStart MUST be >128)
5017 //#endif
5018 } else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.91") == 0)) {
5019 d.compressionScheme = kCompressYes;
5020 //printMessage("JPEG2000 support is new: please validate conversion\n");
5021 } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.5") == 0)
5022 d.compressionScheme = kCompressRLE; //run length
5023 else if (strcmp(transferSyntax, "1.2.840.10008.1.2.2") == 0)
5024 isSwitchToBigEndian = true; //isExplicitVR=true;
5025 else if (strcmp(transferSyntax, "1.2.840.10008.1.2") == 0)
5026 isSwitchToImplicitVR = true; //d.isLittleEndian=true
5027 else {
5028 if (lLength < 1) //"1.2.840.10008.1.2"
5029 printWarning("Missing transfer syntax: assuming default (1.2.840.10008.1.2)\n");
5030 else {
5031 printWarning("Unsupported transfer syntax '%s' (see www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage)\n", transferSyntax);
5032 d.imageStart = 1; //abort as invalid (imageStart MUST be >128)
5033 }
5034 }
5035 break;
5036 } //{} provide scope for variable 'transferSyntax
5037 case kImplementationVersionName: {
5038 char impTxt[kDICOMStr];
5039 dcmStr(lLength, &buffer[lPos], impTxt);
5040 int slen = (int)strlen(impTxt);
5041 if ((slen > 5) && (strstr(impTxt, "MATLAB") != NULL))
5042 isMATLAB = true;
5043 if ((slen < 5) || (strstr(impTxt, "XA10A") == NULL))
5044 break;
5045 d.isXA10A = true;
5046 break;
5047 }
5048 case kSourceApplicationEntityTitle: {
5049 char saeTxt[kDICOMStr];
5050 dcmStr(lLength, &buffer[lPos], saeTxt);
5051 int slen = (int)strlen(saeTxt);
5052 if ((slen < 5) || (strstr(saeTxt, "oasis") == NULL))
5053 break;
5054 d.isSegamiOasis = true;
5055 break;
5056 }
5057 case kDirectoryRecordSequence: {
5058 d.isRawDataStorage = true;
5059 break;
5060 }
5061 case kImageTypeTag: {
5062 bool is1st = strlen(d.imageType) == 0;
5063 dcmStr(lLength, &buffer[lPos], d.imageType, false); //<-distinguish spaces from pathdelim: [ORIGINAL\PHASE MAP\FFE] should return "PHASE MAP" not "PHASE_MAP"
5064 int slen;
5065 slen = (int)strlen(d.imageType);
5066 if (slen > 1) {
5067 for (int i = 0; i < slen; i++)
5068 if (d.imageType[i] == '\\')
5069 d.imageType[i] = '_';
5070 }
5071 if (is1st)
5072 strcpy(imageType1st, d.imageType);
5073 if ((slen > 5) && strstr(d.imageType, "_MOCO_")) {
5074 //d.isDerived = true; //this would have 'i- y' skip MoCo images
5075 isMoCo = true;
5076 }
5077 if ((slen > 5) && strstr(d.imageType, "B0") && strstr(d.imageType, "MAP"))
5078 d.isRealIsPhaseMapHz = true;
5079 if ((slen > 5) && strstr(d.imageType, "_ADC_"))
5080 d.isDerived = true;
5081 if ((slen > 5) && strstr(d.imageType, "_TRACEW_"))
5082 d.isDerived = true;
5083 if ((slen > 5) && strstr(d.imageType, "_TRACE_"))
5084 d.isDerived = true;
5085 if ((slen > 5) && strstr(d.imageType, "_FA_"))
5086 d.isDerived = true;
5087 if ((slen > 12) && strstr(d.imageType, "_DIFFUSION_"))
5088 d.isDiffusion = true;
5089 //if (strcmp(transferSyntax, "ORIGINAL_PRIMARY_M_ND_MOSAIC") == 0)
5090 if ((slen > 5) && !strcmp(d.imageType + slen - 6, "MOSAIC"))
5091 isMosaic = true;
5092 //const char* prefix = "MOSAIC";
5093 const char *pos = strstr(d.imageType, "MOSAIC");
5094 //const char p = (const char *) d.imageType;
5095 //p = (const char) strstr(d.imageType, "MOSAIC");
5096 //const char* p = strstr(d.imageType, "MOSAIC");
5097 if (pos != NULL)
5098 isMosaic = true;
5099 //isNonImage 0008,0008 = DERIVED,CSAPARALLEL,POSDISP
5100 // sometime ComplexImageComponent 0008,9208 is missing - see ADNI data
5101 // attempt to detect non-images, see https://github.com/scitran/data/blob/a516fdc39d75a6e4ac75d0e179e18f3a5fc3c0af/scitran/data/medimg/dcm/mr/siemens.py
5102 //For Philips combinations see Table 3-28 Table 3-28: Valid combinations of Image Type applied values
5103 // 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
5104 if ((slen > 3) && (strstr(d.imageType, "_R_") != NULL)) {
5105 d.isHasReal = true;
5106 isReal = true;
5107 }
5108 if ((slen > 3) && (strstr(d.imageType, "_M_") != NULL)) {
5109 d.isHasMagnitude = true;
5110 isMagnitude = true;
5111 }
5112 if ((slen > 3) && (strstr(d.imageType, "_I_") != NULL)) {
5113 d.isHasImaginary = true;
5114 isImaginary = true;
5115 }
5116 if ((slen > 3) && (strstr(d.imageType, "_P_") != NULL)) {
5117 d.isHasPhase = true;
5118 isPhase = true;
5119 }
5120 if ((slen > 6) && (strstr(d.imageType, "_REAL_") != NULL)) {
5121 d.isHasReal = true;
5122 isReal = true;
5123 }
5124 if ((slen > 11) && (strstr(d.imageType, "_MAGNITUDE_") != NULL)) {
5125 d.isHasMagnitude = true;
5126 isMagnitude = true;
5127 }
5128 if ((slen > 11) && (strstr(d.imageType, "_IMAGINARY_") != NULL)) {
5129 d.isHasImaginary = true;
5130 isImaginary = true;
5131 }
5132 if ((slen > 6) && (strstr(d.imageType, "PHASE") != NULL)) {
5133 d.isHasPhase = true;
5134 isPhase = true;
5135 }
5136 if ((slen > 6) && (strstr(d.imageType, "DERIVED") != NULL))
5137 d.isDerived = true;
5138 //if((slen > 4) && (strstr(typestr, "DIS2D") != NULL) )
5139 // d.isNonImage = true;
5140 //not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image
5141 break;
5142 }
5143 case kAcquisitionDate:
5144 char acquisitionDateTxt[kDICOMStr];
5145 dcmStr(lLength, &buffer[lPos], acquisitionDateTxt);
5146 d.acquisitionDate = atof(acquisitionDateTxt);
5147 break;
5148 case kAcquisitionDateTime:
5149 //char acquisitionDateTimeTxt[kDICOMStr];
5150 dcmStr(lLength, &buffer[lPos], acquisitionDateTimeTxt);
5151 //printMessage("%s\n",acquisitionDateTimeTxt);
5152 break;
5153 case kStudyDate:
5154 dcmStr(lLength, &buffer[lPos], d.studyDate);
5155 if (((int)strlen(d.studyDate) > 7) && (strstr(d.studyDate, "19000101") != NULL))
5156 isNeologica = true;
5157 break;
5158 case kModality:
5159 if (lLength < 2)
5160 break;
5161 if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'R'))
5162 d.modality = kMODALITY_CR;
5163 else if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'T'))
5164 d.modality = kMODALITY_CT;
5165 if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'R'))
5166 d.modality = kMODALITY_MR;
5167 if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'T'))
5168 d.modality = kMODALITY_PT;
5169 if ((buffer[lPos] == 'U') && (toupper(buffer[lPos + 1]) == 'S'))
5170 d.modality = kMODALITY_US;
5171 break;
5172 case kManufacturer:
5173 if (d.manufacturer == kMANUFACTURER_UNKNOWN)
5174 d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]);
5175 volDiffusion.manufacturer = d.manufacturer;
5176 break;
5177 case kInstitutionName:
5178 dcmStr(lLength, &buffer[lPos], d.institutionName);
5179 break;
5180 case kInstitutionAddress: //VR is "ST": 1024 chars maximum
5181 dcmStr(lLength, &buffer[lPos], d.institutionAddress);
5182 break;
5183 case kReferringPhysicianName:
5184 dcmStr(lLength, &buffer[lPos], d.referringPhysicianName);
5185 break;
5186 case kComplexImageComponent:
5187 if (is2005140FSQ)
5188 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
5189 if (lLength < 2)
5190 break;
5191 //issue 256: Philips files report real ComplexImageComponent but Magnitude ImageType https://github.com/rordenlab/dcm2niix/issues/256
5192 isPhase = false;
5193 isReal = false;
5194 isImaginary = false;
5195 isMagnitude = false;
5196 //see Table C.8-85 http://dicom.nema.org/medical/Dicom/2017c/output/chtml/part03/sect_C.8.13.3.html
5197 if ((buffer[lPos] == 'R') && (toupper(buffer[lPos + 1]) == 'E'))
5198 isReal = true;
5199 if ((buffer[lPos] == 'I') && (toupper(buffer[lPos + 1]) == 'M'))
5200 isImaginary = true;
5201 if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'H'))
5202 isPhase = true;
5203 if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'A'))
5204 isMagnitude = true;
5205 //not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image
5206 if (isPhase)
5207 d.isHasPhase = true;
5208 if (isReal)
5209 d.isHasReal = true;
5210 if (isImaginary)
5211 d.isHasImaginary = true;
5212 if (isMagnitude)
5213 d.isHasMagnitude = true;
5214 break;
5215 case kAcquisitionContrast:
5216 char acqContrast[kDICOMStr];
5217 dcmStr(lLength, &buffer[lPos], acqContrast);
5218 if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "DIFFUSION") != NULL))
5219 d.isDiffusion = true;
5220 if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "PERFUSION") != NULL))
5221 isASL = true; //see series 301 of dcm_qa_philips_asl
5222 break;
5223 case kAcquisitionTime: {
5224 char acquisitionTimeTxt[kDICOMStr];
5225 dcmStr(lLength, &buffer[lPos], acquisitionTimeTxt);
5226 d.acquisitionTime = atof(acquisitionTimeTxt);
5227 if (d.manufacturer != kMANUFACTURER_UIH)
5228 break;
5229 //UIH slice timing- do not use for Siemens as Siemens de-identification can corrupt this field https://github.com/rordenlab/dcm2niix/issues/236
5230 d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.acquisitionTime;
5231 acquisitionTimesGE_UIH++;
5232 break;
5233 }
5234 case kSeriesTime:
5235 dcmStr(lLength, &buffer[lPos], seriesTimeTxt);
5236 break;
5237 case kStudyTime:
5238 if (strlen(d.studyTime) < 2)
5239 dcmStr(lLength, &buffer[lPos], d.studyTime);
5240 break;
5241 case kPatientName:
5242 dcmStr(lLength, &buffer[lPos], d.patientName);
5243 break;
5244 case kAnatomicalOrientationType: {
5245 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
5246 dcmStr(lLength, &buffer[lPos], aotTxt);
5247 int slen = (int)strlen(aotTxt);
5248 if ((slen < 9) || (strstr(aotTxt, "QUADRUPED") == NULL))
5249 break;
5250 printError("Anatomical Orientation Type (0010,2210) is QUADRUPED: rotate coordinates accordingly\n");
5251 break;
5252 }
5253 case kDeidentificationMethod: { //issue 383
5254 char anonTxt[kDICOMStr];
5255 dcmStr(lLength, &buffer[lPos], anonTxt);
5256 int slen = (int)strlen(anonTxt);
5257 if ((slen < 10) || (strstr(anonTxt, "DICOMANON") == NULL))
5258 break;
5259 isDICOMANON = true;
5260 printWarning("Matlab DICOMANON can scramble SeriesInstanceUID (0020,000e) and remove crucial data (see issue 383). \n");
5261 break;
5262 }
5263 case kPatientID:
5264 if (strlen(d.patientID) > 1)
5265 break;
5266 dcmStr(lLength, &buffer[lPos], d.patientID);
5267 break;
5268 case kAccessionNumber:
5269 dcmStr(lLength, &buffer[lPos], d.accessionNumber);
5270 break;
5271 case kPatientBirthDate:
5272 dcmStr(lLength, &buffer[lPos], d.patientBirthDate);
5273 break;
5274 case kPatientSex: {
5275 //must be M,F,O: http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.2.html
5276 char patientSex = toupper(buffer[lPos]);
5277 if ((patientSex == 'M') || (patientSex == 'F') || (patientSex == 'O'))
5278 d.patientSex = patientSex;
5279 break;
5280 }
5281 case kPatientAge:
5282 dcmStr(lLength, &buffer[lPos], d.patientAge);
5283 break;
5284 case kPatientWeight:
5285 d.patientWeight = dcmStrFloat(lLength, &buffer[lPos]);
5286 break;
5287 case kStationName:
5288 dcmStr(lLength, &buffer[lPos], d.stationName);
5289 break;
5290 case kSeriesDescription:
5291 dcmStr(lLength, &buffer[lPos], d.seriesDescription);
5292 break;
5293 case kInstitutionalDepartmentName:
5294 dcmStr(lLength, &buffer[lPos], d.institutionalDepartmentName);
5295 break;
5296 case kManufacturersModelName:
5297 dcmStr(lLength, &buffer[lPos], d.manufacturersModelName);
5298 break;
5299 case kDerivationDescription: {
5300 //strcmp(transferSyntax, "1.2.840.10008.1.2")
5301 char derivationDescription[kDICOMStr];
5302 dcmStr(lLength, &buffer[lPos], derivationDescription); //strcasecmp, strcmp
5303 if (strcasecmp(derivationDescription, "MEDCOM_RESAMPLED") == 0)
5304 d.isResampled = true;
5305 break;
5306 }
5307 case kDeviceSerialNumber: {
5308 dcmStr(lLength, &buffer[lPos], d.deviceSerialNumber);
5309 break;
5310 }
5311 case kSoftwareVersions: {
5312 dcmStr(lLength, &buffer[lPos], d.softwareVersions);
5313 int slen = (int)strlen(d.softwareVersions);
5314 if ((slen > 4) && (strstr(d.softwareVersions, "XA11") != NULL))
5315 d.isXA10A = true;
5316 if ((slen > 4) && (strstr(d.softwareVersions, "XA20") != NULL))
5317 d.isXA10A = true;
5318 if ((slen > 4) && (strstr(d.softwareVersions, "XA30") != NULL))
5319 d.isXA10A = true;
5320 if ((slen < 5) || (strstr(d.softwareVersions, "XA10") == NULL))
5321 break;
5322 d.isXA10A = true;
5323 break;
5324 }
5325 case kProtocolName: {
5326 dcmStr(lLength, &buffer[lPos], d.protocolName);
5327 break;
5328 }
5329 case kPatientOrient:
5330 dcmStr(lLength, &buffer[lPos], d.patientOrient);
5331 break;
5332 case kInversionRecovery: // CS [YES],[NO]
5333 if (lLength < 2)
5334 break;
5335 if (toupper(buffer[lPos]) == 'Y')
5336 d.isIR = true;
5337 break;
5338 case kSpoiling: // CS 0018,9016
5339 if (lLength < 2)
5340 break;
5341 char sTxt[kDICOMStr];
5342 dcmStr(lLength, &buffer[lPos], sTxt);
5343 if ((strstr(sTxt, "RF") != NULL) && (strstr(sTxt, "RF") != NULL))
5344 d.spoiling = kSPOILING_RF_AND_GRADIENT;
5345 else if (strstr(sTxt, "RF") != NULL)
5346 d.spoiling = kSPOILING_RF;
5347 else if (strstr(sTxt, "GRADIENT") != NULL)
5348 d.spoiling = kSPOILING_GRADIENT;
5349 else if (strstr(sTxt, "NONE") != NULL)
5350 d.spoiling = kSPOILING_NONE;
5351 break;
5352 case kEchoPlanarPulseSequence: // CS [YES],[NO]
5353 if (lLength < 2)
5354 break;
5355 if (toupper(buffer[lPos]) == 'Y')
5356 d.isEPI = true;
5357 break;
5358 case kMagnetizationTransferAttribute: //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE'
5359 if (lLength < 2)
5360 break; //https://github.com/bids-standard/bids-specification/pull/681
5361 if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos+1]) == 'F')) // OFF_RESONANCE
5362 d.mtState = 1; //TRUE
5363 if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos+1]) == 'N')) // ON_RESONANCE and NONE
5364 d.mtState = 0; //FALSE
5365 if ((toupper(buffer[lPos]) == 'N') && (toupper(buffer[lPos+1]) == 'O')) // ON_RESONANCE and NONE
5366 d.mtState = 0; //FALSE
5367 break;
5368 case kRectilinearPhaseEncodeReordering: { //'CS' [REVERSE_LINEAR],[LINEAR],[CENTRIC],[REVERSE_CENTRIC]
5369 if (d.manufacturer != kMANUFACTURER_GE)
5370 break; //only found in GE software beginning with RX27
5371 if (lLength < 2)
5372 break;
5373 if (toupper(buffer[lPos]) == 'L')
5374 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
5375 if (toupper(buffer[lPos]) == 'R')
5376 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
5377 break;
5378 }
5379 case kPartialFourierDirection: { //'CS' PHASE FREQUENCY SLICE_SELECT COMBINATION
5380 if (lLength < 2)
5381 break;
5382 if (toupper(buffer[lPos]) == 'P')
5383 d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_PHASE;
5384 if (toupper(buffer[lPos]) == 'F')
5385 d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_FREQUENCY;
5386 if (toupper(buffer[lPos]) == 'S')
5387 d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT;
5388 if (toupper(buffer[lPos]) == 'C')
5389 d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION;
5390 break;
5391 }
5392 case kCardiacSynchronizationTechnique:
5393 if (toupper(buffer[lPos]) == 'P')
5394 isProspectiveSynced = true;
5395 break;
5396 case kParallelReductionFactorInPlane:
5397 if (d.manufacturer == kMANUFACTURER_SIEMENS)
5398 break;
5399 d.accelFactPE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
5400 break;
5401 case kAcquisitionDuration:
5402 //n.b. used differently by different vendors https://github.com/rordenlab/dcm2niix/issues/225
5403 d.acquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
5404 break;
5405 //in theory, 0018,9074 could provide XA10 slice time information, but scrambled by XA10 de-identification: better to use 0021,1104
5406 //case kFrameAcquisitionDateTime: {
5407 // //(0018,9074) DT [20190621095516.140000] YYYYMMDDHHMMSS
5408 // //see https://github.com/rordenlab/dcm2niix/issues/303
5409 // char dateTime[kDICOMStr];
5410 // dcmStr(lLength, &buffer[lPos], dateTime);
5411 // printf("%s\tkFrameAcquisitionDateTime\n", dateTime);
5412 //}
5413 case kDiffusionDirectionality: { // 0018, 9075
5414 set_directionality0018_9075(&volDiffusion, (&buffer[lPos]));
5415 if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 10))
5416 break;
5417 char dir[kDICOMStr];
5418 dcmStr(lLength, &buffer[lPos], dir);
5419 if (strcmp(dir, "ISOTROPIC") == 0)
5420 isPhilipsDerived = true;
5421 break;
5422 }
5423 case kParallelAcquisitionTechnique: //CS
5424 dcmStr(lLength, &buffer[lPos], d.parallelAcquisitionTechnique);
5425 break;
5426 case kInversionTimes: { //issue 380
5427 if ((lLength < 8) || ((lLength % 8) != 0))
5428 break;
5429 d.TI = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
5430 break;
5431 }
5432 case kPartialFourier: //(0018,9081) CS [YES],[NO]
5433 if (lLength < 2)
5434 break;
5435 if (toupper(buffer[lPos]) == 'Y')
5436 d.isPartialFourier = true;
5437 break;
5438 case kMREchoSequence:
5439 if (d.manufacturer != kMANUFACTURER_BRUKER)
5440 break;
5441 if (sqDepth == 0)
5442 sqDepth = 1; //should not happen, in case faulty anonymization
5443 sqDepth00189114 = sqDepth - 1;
5444 break;
5445 case kMRAcquisitionPhaseEncodingStepsInPlane:
5446 d.phaseEncodingLines = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5447 break;
5448 case kNumberOfImagesInMosaic:
5449 if (d.manufacturer == kMANUFACTURER_SIEMENS)
5450 numberOfImagesInMosaic = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5451 break;
5452 case kSeriesPlaneGE: //SS 2=Axi, 4=Sag, 8=Cor, 16=Obl, 256=3plane
5453 if (d.manufacturer != kMANUFACTURER_GE)
5454 break;
5455 if (dcmInt(lLength, &buffer[lPos], d.isLittleEndian) == 256)
5456 d.isLocalizer = true;
5457 break;
5458 case kDwellTime:
5459 d.dwellTime = dcmStrInt(lLength, &buffer[lPos]);
5460 break;
5461 case kDiffusion_bValueSiemens:
5462 if (d.manufacturer != kMANUFACTURER_SIEMENS)
5463 break;
5464 //issue409
5465 B0Philips = dcmStrInt(lLength, &buffer[lPos]);
5466 set_bVal(&volDiffusion, B0Philips);
5467 break;
5468 case kSliceTimeSiemens: { //Array of FD (64-bit double)
5469 if (d.manufacturer != kMANUFACTURER_SIEMENS)
5470 break;
5471 if ((lLength < 8) || ((lLength % 8) != 0))
5472 break;
5473 int nSlicesTimes = lLength / 8;
5474 if (nSlicesTimes > kMaxEPI3D)
5475 break;
5476 d.CSA.mosaicSlices = nSlicesTimes;
5477 //printf(">>>> %d\n", nSlicesTimes);
5478 //issue 296: for images de-identified to remove readCSAImageHeader
5479 for (int z = 0; z < nSlicesTimes; z++)
5480 d.CSA.sliceTiming[z] = dcmFloatDouble(8, &buffer[lPos + (z * 8)], d.isLittleEndian);
5481 //for (int z = 0; z < nSlicesTimes; z++)
5482 // printf("%d>>>%g\n", z+1, d.CSA.sliceTiming[z]);
5483 checkSliceTimes(&d.CSA, nSlicesTimes, isVerbose, d.is3DAcq);
5484 //d.CSA.dtiV[0] = dcmStrInt(lLength, &buffer[lPos]);
5485 //d.CSA.numDti = 1;
5486 break;
5487 }
5488 case kDiffusionGradientDirectionSiemens: {
5489 if (d.manufacturer != kMANUFACTURER_SIEMENS)
5490 break;
5491 float v[4];
5492 dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian);
5493 //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v);
5494 //printf(">>>%g %g %g\n", v[0], v[1], v[2]);
5495 d.CSA.dtiV[1] = v[0];
5496 d.CSA.dtiV[2] = v[1];
5497 d.CSA.dtiV[3] = v[2];
5498 break;
5499 }
5500 case kNumberOfDiffusionDirectionGE: {
5501 if (d.manufacturer != kMANUFACTURER_GE)
5502 break;
5503 float f = dcmStrFloat(lLength, &buffer[lPos]);
5504 d.numberOfDiffusionDirectionGE = round(f);
5505 break;
5506 }
5507 case kLastScanLoc:
5508 d.lastScanLoc = dcmStrFloat(lLength, &buffer[lPos]);
5509 break;
5510 //GE bug: multiple echos can create identical instance numbers
5511 // in theory, one could detect as kRawDataRunNumberGE varies
5512 // sliceN of echoE will have the same value for all timepoints
5513 // this value does not appear indexed
5514 // different echoes record same echo time.
5515 // use multiEchoSortGEDICOM.py to salvage
5516 case kRawDataRunNumberGE:
5517 if (d.manufacturer != kMANUFACTURER_GE)
5518 break;
5519 d.rawDataRunNumber = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5520 break;
5521 case kMaxEchoNumGE:
5522 if (d.manufacturer != kMANUFACTURER_GE)
5523 break;
5524 d.maxEchoNumGE = round(dcmStrFloat(lLength, &buffer[lPos]));
5525 break;
5526 case kUserData12GE: {
5527 if (d.manufacturer != kMANUFACTURER_GE)
5528 break;
5529 userData12GE = round(dcmStrFloat(lLength, &buffer[lPos]));
5530 //printf("%d<<<<\n", userData12GE);
5531 break; }
5532 case kDiffusionDirectionGEX:
5533 if (d.manufacturer == kMANUFACTURER_GE)
5534 set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 0);
5535 break;
5536 case kDiffusionDirectionGEY:
5537 if (d.manufacturer == kMANUFACTURER_GE)
5538 set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 1);
5539 break;
5540 case kDiffusionDirectionGEZ:
5541 if (d.manufacturer == kMANUFACTURER_GE)
5542 set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 2);
5543 break;
5544 case kPulseSequenceNameGE: { //LO 'epi'/'epiRT'
5545 if (d.manufacturer != kMANUFACTURER_GE)
5546 break;
5547 char epiStr[kDICOMStr];
5548 dcmStr(lLength, &buffer[lPos], epiStr);
5549 if (strstr(epiStr, "epi_pepolar") != NULL) {
5550 d.epiVersionGE = kGE_EPI_PEPOLAR_FWD; //n.b. combine with 0019,10B3
5551 } else if (strstr(epiStr, "epi2") != NULL) {
5552 d.epiVersionGE = kGE_EPI_EPI2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2
5553 } else if (strstr(epiStr, "epiRT") != NULL) {
5554 d.epiVersionGE = kGE_EPI_EPIRT; //-1 = not epi, 0 = epi, 1 = epiRT
5555 } else if (strstr(epiStr, "epi") != NULL) {
5556 d.epiVersionGE = kGE_EPI_EPI; //-1 = not epi, 0 = epi, 1 = epiRT
5557 }
5558 if (strcmp(epiStr, "3db0map") == 0) {
5559 isGEfieldMap = true; //issue501
5560 }
5561 break;
5562 }
5563 case kInternalPulseSequenceNameGE: { //LO 'EPI'(gradient echo)/'EPI2'(spin echo):
5564 if (d.manufacturer != kMANUFACTURER_GE)
5565 break;
5566 char epiStr[kDICOMStr];
5567 dcmStr(lLength, &buffer[lPos], epiStr);
5568 if ((d.epiVersionGE < kGE_EPI_PEPOLAR_FWD) && (strcmp(epiStr, "EPI") == 0)) {
5569 d.internalepiVersionGE = 1; //-1 = not EPI, 1 = EPI, 2 = EPI2
5570 if (d.epiVersionGE != 1) { // 1 = epiRT by kEpiRTGroupDelayGE or kPulseSequenceNameGE
5571 d.epiVersionGE = 0; // 0 = epi (multi-phase epi)
5572 }
5573 }
5574 if (strcmp(epiStr, "EPI2") == 0) {
5575 d.internalepiVersionGE = 2; //-1 = not epi, 1 = EPI, 2 = EPI2
5576 }
5577 if (strcmp(epiStr, "B0map") == 0) {
5578 isGEfieldMap = true; //issue501
5579 }
5580 break;
5581 }
5582 case kBandwidthPerPixelPhaseEncode:
5583 d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
5584 break;
5585 case kStudyInstanceUID: // 0020,000D
5586 dcmStr(lLength, &buffer[lPos], d.studyInstanceUID);
5587 break;
5588 case kSeriesInstanceUID: // 0020,000E
5589 dcmStr(lLength, &buffer[lPos], d.seriesInstanceUID);
5590 //printMessage(">>%s\n", d.seriesInstanceUID);
5591 d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID));
5592 break;
5593 case kImagePositionPatient: {
5594 if (is2005140FSQ) {
5595 dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPositionPrivate[0]);
5596 break;
5597 }
5598 patientPositionNum++;
5599 isAtFirstPatientPosition = true;
5600 //char dx[kDICOMStr];
5601 //dcmStr(lLength, &buffer[lPos], dx);
5602 //printMessage("*%s*", dx);
5603 dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPosition[0]); //slice position
5604 if (isnan(d.patientPosition[1])) {
5605 //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPosition[0]); //slice position
5606 for (int k = 0; k < 4; k++)
5607 d.patientPosition[k] = patientPosition[k];
5608 } else {
5609 //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPositionLast[0]); //slice direction for 4D
5610 for (int k = 0; k < 4; k++)
5611 d.patientPositionLast[k] = patientPosition[k];
5612 if ((isFloatDiff(d.patientPositionLast[1], d.patientPosition[1])) ||
5613 (isFloatDiff(d.patientPositionLast[2], d.patientPosition[2])) ||
5614 (isFloatDiff(d.patientPositionLast[3], d.patientPosition[3]))) {
5615 isAtFirstPatientPosition = false; //this slice is not at position of 1st slice
5616 //if (d.patientPositionSequentialRepeats == 0) //this is the first slice with different position
5617 // d.patientPositionSequentialRepeats = patientPositionNum-1;
5618 } //if different position from 1st slice in file
5619 } //if not first slice in file
5620 set_isAtFirstPatientPosition_tvd(&volDiffusion, isAtFirstPatientPosition);
5621 //if (isAtFirstPatientPosition) numFirstPatientPosition++;
5622 if (isVerbose > 0) //verbose > 1 will report full DICOM tag
5623 printMessage(" Patient Position 0020,0032 (#,@,X,Y,Z)\t%d\t%ld\t%g\t%g\t%g\n", patientPositionNum, lPos, patientPosition[1], patientPosition[2], patientPosition[3]);
5624 if ((isOrient) && (nSliceMM < kMaxSlice2D)) {
5625 vec3 pos = setVec3(patientPosition[1], patientPosition[2], patientPosition[3]);
5626 sliceMM[nSliceMM] = dotProduct(pos, sliceV);
5627 if (sliceMM[nSliceMM] < minSliceMM) {
5628 minSliceMM = sliceMM[nSliceMM];
5629 for (int k = 0; k < 4; k++)
5630 minPatientPosition[k] = patientPosition[k];
5631 }
5632 if (sliceMM[nSliceMM] > maxSliceMM) {
5633 maxSliceMM = sliceMM[nSliceMM];
5634 for (int k = 0; k < 4; k++)
5635 maxPatientPosition[k] = patientPosition[k];
5636 }
5637 nSliceMM++;
5638 }
5639 break;
5640 }
5641 case kInPlanePhaseEncodingDirection:
5642 d.phaseEncodingRC = toupper(buffer[lPos]); //first character is either 'R'ow or 'C'ol
5643 break;
5644 case kSAR:
5645 d.SAR = dcmStrFloat(lLength, &buffer[lPos]);
5646 break;
5647 case kStudyID:
5648 dcmStr(lLength, &buffer[lPos], d.studyID);
5649 break;
5650 case kSeriesNum:
5651 d.seriesNum = dcmStrInt(lLength, &buffer[lPos]);
5652 break;
5653 case kAcquNum:
5654 d.acquNum = dcmStrInt(lLength, &buffer[lPos]);
5655 break;
5656 case kImageNum:
5657 //Enhanced Philips also uses this in once per file SQ 0008,1111
5658 //Enhanced Philips also uses this once per slice in SQ 2005,140f
5659 if (d.imageNum < 1)
5660 d.imageNum = dcmStrInt(lLength, &buffer[lPos]); //Philips renames each image again in 2001,9000, which can lead to duplicates
5661 break;
5662 case kInStackPositionNumber:
5663 if ((d.manufacturer != kMANUFACTURER_CANON) && (d.manufacturer != kMANUFACTURER_HITACHI) && (d.manufacturer != kMANUFACTURER_UNKNOWN) && (d.manufacturer != kMANUFACTURER_PHILIPS) && (d.manufacturer != kMANUFACTURER_BRUKER))
5664 break;
5665 inStackPositionNumber = dcmInt(4, &buffer[lPos], d.isLittleEndian);
5666 //if (inStackPositionNumber == 1) numInStackPositionNumber1 ++;
5667 //printf("<%d>\n",inStackPositionNumber);
5668 if (inStackPositionNumber > maxInStackPositionNumber)
5669 maxInStackPositionNumber = inStackPositionNumber;
5670 break;
5671 case kTemporalPositionIndex:
5672 temporalPositionIndex = dcmInt(4, &buffer[lPos], d.isLittleEndian);
5673 if (temporalPositionIndex > maxTemporalPositionIndex)
5674 maxTemporalPositionIndex = temporalPositionIndex;
5675 break;
5676 case kDimensionIndexPointer:
5677 dimensionIndexPointer[dimensionIndexPointerCounter++] = dcmAttributeTag(&buffer[lPos], d.isLittleEndian);
5678 break;
5679 case kFrameContentSequence:
5680 //if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //see https://github.com/rordenlab/dcm2niix/issues/241
5681 if (sqDepth == 0)
5682 sqDepth = 1; //should not happen, in case faulty anonymization
5683 sqDepth00189114 = sqDepth - 1;
5684 break;
5685 case kTriggerDelayTime: { //0x0020+uint32_t(0x9153<< 16 ) //FD
5686 if (prefs->isIgnoreTriggerTimes)
5687 break; //issue499
5688 if (d.manufacturer != kMANUFACTURER_PHILIPS)
5689 break;
5690 //if (isVerbose < 2) break;
5691 double trigger = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
5692 d.triggerDelayTime = trigger;
5693 if (isSameFloatGE(d.triggerDelayTime, 0.0))
5694 d.triggerDelayTime = 0.0; //double to single
5695 break;
5696 }
5697 case kDimensionIndexValues: { // kImageNum is not enough for 4D series from Philips 5.*.
5698 if (lLength < 4)
5699 break;
5700 nDimIndxVal = lLength / 4;
5701 if (nDimIndxVal > MAX_NUMBER_OF_DIMENSIONS) {
5702 printError("%d is too many dimensions. Only up to %d are supported\n", nDimIndxVal, MAX_NUMBER_OF_DIMENSIONS);
5703 nDimIndxVal = MAX_NUMBER_OF_DIMENSIONS; // Truncate
5704 }
5705 dcmMultiLongs(4 * nDimIndxVal, &buffer[lPos], nDimIndxVal, d.dimensionIndexValues, d.isLittleEndian);
5706 break;
5707 }
5708 case kPhotometricInterpretation: {
5709 char interp[kDICOMStr];
5710 dcmStr(lLength, &buffer[lPos], interp);
5711 if (strcmp(interp, "PALETTE_COLOR") == 0)
5712 isPaletteColor = true;
5713 //printError("Photometric Interpretation 'PALETTE COLOR' not supported\n");
5714 break;
5715 }
5716 case kPlanarRGB:
5717 d.isPlanarRGB = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5718 break;
5719 case kDim3:
5720 d.xyzDim[3] = dcmStrInt(lLength, &buffer[lPos]);
5721 numberOfFrames = d.xyzDim[3];
5722 break;
5723 case kSamplesPerPixel:
5724 d.samplesPerPixel = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5725 break;
5726 case kDim2:
5727 d.xyzDim[2] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5728 break;
5729 case kDim1:
5730 d.xyzDim[1] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5731 break;
5732 //order is Row,Column e.g. YX
5733 case kXYSpacing: {
5734 float yx[3];
5735 dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, yx);
5736 d.xyzMM[1] = yx[2];
5737 d.xyzMM[2] = yx[1];
5738 break;
5739 }
5740 case kImageComments:
5741 dcmStr(lLength, &buffer[lPos], d.imageComments, true);
5742 break;
5743 //group 21: siemens
5744 //g21
5745 case kPATModeText: { //e.g. Siemens iPAT x2 listed as "p2"
5746 char accelStr[kDICOMStr];
5747 dcmStr(lLength, &buffer[lPos], accelStr);
5748 char *ptr;
5749 dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return ""
5750 d.accelFactPE = (float)strtof(accelStr, &ptr);
5751 if (*ptr != '\0')
5752 d.accelFactPE = 0.0;
5753 //between slice accel
5754 dcmStr(lLength, &buffer[lPos], accelStr);
5755 dcmStrDigitsOnlyKey('s', accelStr); //e.g. if "p2s4" return "4", if "p2" return ""
5756 multiBandFactor = (int)strtol(accelStr, &ptr, 10);
5757 if (*ptr != '\0')
5758 multiBandFactor = 0.0;
5759 //printMessage("p%gs%d\n", d.accelFactPE, multiBandFactor);
5760 break;
5761 }
5762 case kTimeAfterStart:
5763 //0021,1104 see https://github.com/rordenlab/dcm2niix/issues/303
5764 // 0021,1104 6@159630 DS 4.635
5765 // 0021,1104 2@161164 DS 0
5766 if (d.manufacturer != kMANUFACTURER_SIEMENS)
5767 break;
5768 if (acquisitionTimesGE_UIH >= kMaxEPI3D)
5769 break;
5770 d.CSA.sliceTiming[acquisitionTimesGE_UIH] = dcmStrFloat(lLength, &buffer[lPos]);
5771 d.CSA.sliceTiming[acquisitionTimesGE_UIH] *= 1000.0; //convert sec to msec
5772 //printf("x\t%d\t%g\tkTimeAfterStart\n", acquisitionTimesGE_UIH, d.CSA.sliceTiming[acquisitionTimesGE_UIH]);
5773 acquisitionTimesGE_UIH++;
5774 break;
5775 case kPhaseEncodingDirectionPositiveSiemens: {
5776 if (d.manufacturer != kMANUFACTURER_SIEMENS)
5777 break;
5778 int ph = dcmStrInt(lLength, &buffer[lPos]);
5779 if (ph == 0)
5780 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
5781 if (ph == 1)
5782 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
5783 break;
5784 }
5785 //case kRealDwellTime : //https://github.com/rordenlab/dcm2niix/issues/240
5786 // if (d.manufacturer != kMANUFACTURER_SIEMENS) break;
5787 // d.dwellTime = dcmStrInt(lLength, &buffer[lPos]);
5788 // break;
5789 case kBandwidthPerPixelPhaseEncode21:
5790 if (d.manufacturer != kMANUFACTURER_SIEMENS)
5791 break;
5792 d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
5793 break;
5794 case kCoilElements:
5795 if (d.manufacturer != kMANUFACTURER_SIEMENS)
5796 break;
5797 dcmStr(lLength, &buffer[lPos], d.coilElements);
5798 break;
5799 //group 21: GE
5800 case kLocationsInAcquisitionGE:
5801 locationsInAcquisitionGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5802 break;
5803 case kRTIA_timer:
5804 if (d.manufacturer != kMANUFACTURER_GE)
5805 break;
5806 //see dicm2nii slice timing from 0021,105E DS RTIA_timer
5807 d.rtia_timerGE = dcmStrFloat(lLength, &buffer[lPos]); //RefAcqTimes = t/10; end % in ms
5808 //printf("%s\t%g\n", fname, d.rtia_timerGE);
5809 break;
5810 case kProtocolDataBlockGE:
5811 if (d.manufacturer != kMANUFACTURER_GE)
5812 break;
5813 d.protocolBlockLengthGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5814 d.protocolBlockStartGE = (int)lPos + (int)lFileOffset + 4;
5815 //printError("ProtocolDataBlockGE %d @ %d\n", d.protocolBlockLengthGE, d.protocolBlockStartGE);
5816 break;
5817 case kNumberOfExcitations: //FL
5818 if (d.manufacturer != kMANUFACTURER_GE)
5819 break;
5820 d.numberOfExcitations = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
5821 break;
5822 case kNumberOfArms: //FL
5823 if (d.manufacturer != kMANUFACTURER_GE)
5824 break;
5825 d.numberOfArms = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
5826 break;
5827 case kNumberOfPointsPerArm: //FL
5828 if (d.manufacturer != kMANUFACTURER_GE)
5829 break;
5830 d.numberOfPointsPerArm = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
5831 break;
5832 case kDoseCalibrationFactor:
5833 d.doseCalibrationFactor = dcmStrFloat(lLength, &buffer[lPos]);
5834 break;
5835 case kPETImageIndex:
5836 PETImageIndex = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5837 break;
5838 case kPEDirectionDisplayedUIH:
5839 if (d.manufacturer != kMANUFACTURER_UIH)
5840 break;
5841 dcmStr(lLength, &buffer[lPos], d.phaseEncodingDirectionDisplayedUIH);
5842 break;
5843 case kDiffusion_bValueUIH: {
5844 if (d.manufacturer != kMANUFACTURER_UIH)
5845 break;
5846 float v[4];
5847 dcmMultiFloatDouble(lLength, &buffer[lPos], 1, v, d.isLittleEndian);
5848 B0Philips = v[0];
5849 set_bVal(&volDiffusion, v[0]);
5850 break;
5851 }
5852 case kParallelInformationUIH: { //SENSE factor (0065,100d) SH [F:2S]
5853 if (d.manufacturer != kMANUFACTURER_UIH)
5854 break;
5855 char accelStr[kDICOMStr];
5856 dcmStr(lLength, &buffer[lPos], accelStr);
5857 //char *ptr;
5858 dcmStrDigitsDotOnlyKey(':', accelStr); //e.g. if "p2s4" return "2", if "s4" return ""
5859 d.accelFactPE = atof(accelStr);
5860 break;
5861 }
5862 case kNumberOfImagesInGridUIH:
5863 if (d.manufacturer != kMANUFACTURER_UIH)
5864 break;
5865 d.numberOfImagesInGridUIH = dcmStrFloat(lLength, &buffer[lPos]);
5866 d.CSA.mosaicSlices = d.numberOfImagesInGridUIH;
5867 break;
5868 case kPhaseEncodingDirectionPositiveUIH: {
5869 if (d.manufacturer != kMANUFACTURER_UIH)
5870 break;
5871 int ph = dcmStrInt(lLength, &buffer[lPos]);
5872 if (ph == 1)
5873 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
5874 if (ph == 0)
5875 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
5876 break;
5877 }
5878 case kDiffusionGradientDirectionUIH: {
5879 if (d.manufacturer != kMANUFACTURER_UIH)
5880 break;
5881 float v[4];
5882 dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian);
5883 //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v);
5884 //printf(">>>%g %g %g\n", v[0], v[1], v[2]);
5885 d.CSA.dtiV[1] = v[0];
5886 d.CSA.dtiV[2] = v[1];
5887 d.CSA.dtiV[3] = v[2];
5888 //vRLPhilips = v[0];
5889 //vAPPhilips = v[1];
5890 //vFHPhilips = v[2];
5891 break;
5892 }
5893 case kBitsAllocated:
5894 d.bitsAllocated = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5895 break;
5896 case kBitsStored:
5897 d.bitsStored = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5898 break;
5899 case kIsSigned: //http://dicomiseasy.blogspot.com/2012/08/chapter-12-pixel-data.html
5900 d.isSigned = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5901 break;
5902 case kPixelPaddingValue:
5903 // According to the DICOM standard, this can be either unsigned (US) or signed (SS). Currently this
5904 // is used only in nii_saveNII3Dtilt() which only allows DT_INT16, so treat it as signed.
5905 d.pixelPaddingValue = (float)(short)dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
5906 break;
5907 case kFloatPixelPaddingValue:
5908 d.pixelPaddingValue = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
5909 break;
5910 case kTR:
5911 d.TR = dcmStrFloat(lLength, &buffer[lPos]);
5912 break;
5913 case kTE:
5914 TE = dcmStrFloat(lLength, &buffer[lPos]);
5915 if (d.TE <= 0.0)
5916 d.TE = TE;
5917 break;
5918 case kNumberOfAverages:
5919 d.numberOfAverages = dcmStrFloat(lLength, &buffer[lPos]);
5920 break;
5921 case kImagingFrequency:
5922 d.imagingFrequency = dcmStrFloat(lLength, &buffer[lPos]);
5923 break;
5924 case kTriggerTime: {
5925 if (prefs->isIgnoreTriggerTimes)
5926 break; //issue499
5927 //untested method to detect slice timing for GE PSD “epi” with multiphase option
5928 // will not work for current PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics)
5929 if ((d.manufacturer != kMANUFACTURER_GE) && (d.manufacturer != kMANUFACTURER_PHILIPS))
5930 break; //issue384
5931 d.triggerDelayTime = dcmStrFloat(lLength, &buffer[lPos]); //???? issue 336
5932 if (d.manufacturer != kMANUFACTURER_GE)
5933 break;
5934 d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.triggerDelayTime;
5935 //printf("%g\n", d.CSA.sliceTiming[acquisitionTimesGE_UIH]);
5936 acquisitionTimesGE_UIH++;
5937 break;
5938 }
5939 case kRadionuclideTotalDose:
5940 d.radionuclideTotalDose = dcmStrFloat(lLength, &buffer[lPos]);
5941 break;
5942 case kEffectiveTE: {
5943 TE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
5944 if (d.TE <= 0.0)
5945 d.TE = TE;
5946 break;
5947 }
5948 case kTI:
5949 d.TI = dcmStrFloat(lLength, &buffer[lPos]);
5950 break;
5951 case kEchoNum:
5952 d.echoNum = dcmStrInt(lLength, &buffer[lPos]);
5953 break;
5954 case kMagneticFieldStrength:
5955 d.fieldStrength = dcmStrFloat(lLength, &buffer[lPos]);
5956 break;
5957 case kZSpacing:
5958 d.zSpacing = dcmStrFloat(lLength, &buffer[lPos]);
5959 break;
5960 case kPhaseEncodingSteps:
5961 d.phaseEncodingSteps = dcmStrInt(lLength, &buffer[lPos]);
5962 break;
5963 case kEchoTrainLength:
5964 d.echoTrainLength = dcmStrInt(lLength, &buffer[lPos]);
5965 break;
5966 case kPercentSampling:
5967 d.percentSampling = dcmStrFloat(lLength, &buffer[lPos]);
5968 case kPhaseFieldofView:
5969 d.phaseFieldofView = dcmStrFloat(lLength, &buffer[lPos]);
5970 break;
5971 case kPixelBandwidth:
5972 d.pixelBandwidth = dcmStrFloat(lLength, &buffer[lPos]);
5973 //printWarning(" PixelBandwidth (0018,0095)====> %g @%d\n", d.pixelBandwidth, lPos);
5974 break;
5975 case kAcquisitionMatrix:
5976 if (lLength == 8) {
5977 uint16_t acquisitionMatrix[4];
5978 dcmMultiShorts(lLength, &buffer[lPos], 4, &acquisitionMatrix[0], d.isLittleEndian); //slice position
5979 //phaseEncodingLines stored in either image columns or rows
5980 if (acquisitionMatrix[3] > 0)
5981 d.phaseEncodingLines = acquisitionMatrix[3];
5982 if (acquisitionMatrix[2] > 0)
5983 d.phaseEncodingLines = acquisitionMatrix[2];
5984 if (acquisitionMatrix[1] > 0)
5985 frequencyRows = acquisitionMatrix[1];
5986 if (acquisitionMatrix[0] > 0)
5987 frequencyRows = acquisitionMatrix[0];
5988 }
5989 break;
5990 case kFlipAngle:
5991 d.flipAngle = dcmStrFloat(lLength, &buffer[lPos]);
5992 break;
5993 case kRadionuclideHalfLife:
5994 d.radionuclideHalfLife = dcmStrFloat(lLength, &buffer[lPos]);
5995 break;
5996 case kRadionuclidePositronFraction:
5997 d.radionuclidePositronFraction = dcmStrFloat(lLength, &buffer[lPos]);
5998 break;
5999 case kGantryTilt:
6000 d.gantryTilt = dcmStrFloat(lLength, &buffer[lPos]);
6001 break;
6002 case kXRayTimeMS: //IS
6003 d.exposureTimeMs = dcmStrInt(lLength, &buffer[lPos]);;
6004 break;
6005 case kXRayTubeCurrent: //IS
6006 d.xRayTubeCurrent = dcmStrInt(lLength, &buffer[lPos]);;
6007 break;
6008 case kXRayExposure: //CTs do not have echo times, we use this field to detect different exposures: https://github.com/neurolabusc/dcm2niix/pull/48
6009 if (d.TE == 0) { // for CT we will use exposure (0018,1152) whereas for MR we use echo time (0018,0081)
6010 d.isXRay = true;
6011 d.TE = dcmStrFloat(lLength, &buffer[lPos]);
6012 }
6013 break;
6014 case kConvolutionKernel: //CS
6015 dcmStr(lLength, &buffer[lPos], d.convolutionKernel);
6016 break;
6017 case kFrameDuration:
6018 d.frameDuration = dcmStrInt(lLength, &buffer[lPos]);
6019 break;
6020 case kReceiveCoilName:
6021 dcmStr(lLength, &buffer[lPos], d.coilName);
6022 if (strlen(d.coilName) < 1)
6023 break;
6024 d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName));
6025 break;
6026 case kSlope:
6027 d.intenScale = dcmStrFloat(lLength, &buffer[lPos]);
6028 break;
6029 //case kSpectroscopyDataPointColumns :
6030 // d.xyzDim[4] = dcmInt(4,&buffer[lPos],d.isLittleEndian);
6031 // break;
6032 case kPhilipsSlope:
6033 if ((lLength == 4) && (d.manufacturer == kMANUFACTURER_PHILIPS))
6034 d.intenScalePhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6035 break;
6036 case kMRImageDynamicScanBeginTime: { //FL
6037 if (lLength != 4)
6038 break;
6039 MRImageDynamicScanBeginTime = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6040 if (MRImageDynamicScanBeginTime < minDynamicScanBeginTime)
6041 minDynamicScanBeginTime = MRImageDynamicScanBeginTime;
6042 if (MRImageDynamicScanBeginTime > maxDynamicScanBeginTime)
6043 maxDynamicScanBeginTime = MRImageDynamicScanBeginTime;
6044 break;
6045 }
6046 case kIntercept:
6047 d.intenIntercept = dcmStrFloat(lLength, &buffer[lPos]);
6048 break;
6049 case kRadiopharmaceutical:
6050 dcmStr(lLength, &buffer[lPos], d.radiopharmaceutical);
6051 break;
6052 case kZThick:
6053 d.xyzMM[3] = dcmStrFloat(lLength, &buffer[lPos]);
6054 d.zThick = d.xyzMM[3];
6055 break;
6056 case kAcquisitionMatrixText21:
6057 //fall through to kAcquisitionMatrixText
6058 case kAcquisitionMatrixText: {
6059 if (d.manufacturer == kMANUFACTURER_SIEMENS) {
6060 char matStr[kDICOMStr];
6061 dcmStr(lLength, &buffer[lPos], matStr);
6062 char *pPosition = strchr(matStr, 'I');
6063 if (pPosition != NULL)
6064 isInterpolated = true;
6065 }
6066 break;
6067 }
6068 case kImageOrientationText: //issue522
6069 if (d.manufacturer != kMANUFACTURER_SIEMENS)
6070 break;
6071 dcmStr(lLength, &buffer[lPos], d.imageOrientationText);
6072 break;
6073 case kCoilSiemens: {
6074 if (d.manufacturer == kMANUFACTURER_SIEMENS) {
6075 //see if image from single coil "H12" or an array "HEA;HEP"
6076 //char coilStr[kDICOMStr];
6077 //int coilNum;
6078 dcmStr(lLength, &buffer[lPos], d.coilName);
6079 if (strlen(d.coilName) < 1)
6080 break;
6081 //printf("-->%s\n", coilStr);
6082 //d.coilName = coilStr;
6083 //if (coilStr[0] == 'C') break; //kludge as Nova 32-channel defaults to "C:A32" https://github.com/rordenlab/dcm2niix/issues/187
6084 //char *ptr;
6085 //dcmStrDigitsOnly(coilStr);
6086 //coilNum = (int)strtol(coilStr, &ptr, 10);
6087 d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName));
6088 //printf("%d:%s\n", d.coilNum, coilStr);
6089 //if (*ptr != '\0')
6090 // d.coilNum = 0;
6091 }
6092 break;
6093 }
6094 case kImaPATModeText: { //e.g. Siemens iPAT x2 listed as "p2"
6095 char accelStr[kDICOMStr];
6096 dcmStr(lLength, &buffer[lPos], accelStr);
6097 char *ptr;
6098 dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return ""
6099 d.accelFactPE = (float)strtof(accelStr, &ptr);
6100 if (*ptr != '\0')
6101 d.accelFactPE = 0.0;
6102 //between slice accel
6103 dcmStr(lLength, &buffer[lPos], accelStr);
6104 dcmStrDigitsOnlyKey('s', accelStr); //e.g. if "p2s4" return "4", if "p2" return ""
6105 multiBandFactor = (int)strtol(accelStr, &ptr, 10);
6106 if (*ptr != '\0')
6107 multiBandFactor = 0.0;
6108 break;
6109 }
6110 case kLocationsInAcquisition:
6111 d.locationsInAcquisition = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6112 break;
6113 case kUnitsPT: //CS
6114 dcmStr(lLength, &buffer[lPos], d.unitsPT);
6115 break;
6116 case kAttenuationCorrectionMethod: //LO
6117 dcmStr(lLength, &buffer[lPos], d.attenuationCorrectionMethod);
6118 break;
6119 case kDecayCorrection: //CS
6120 dcmStr(lLength, &buffer[lPos], d.decayCorrection);
6121 break;
6122 case kReconstructionMethod: //LO
6123 dcmStr(lLength, &buffer[lPos], d.reconstructionMethod);
6124 break;
6125 case kDecayFactor:
6126 d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]);
6127 break;
6128 case kIconImageSequence:
6129 isIconImageSequence = true;
6130 if (sqDepthIcon < 0)
6131 sqDepthIcon = sqDepth;
6132 break;
6133 //case kMRSeriesAcquisitionNumber: // 0x2001+(0x107B << 16 ) //IS
6134 // mRSeriesAcquisitionNumber = dcmStrInt(lLength, &buffer[lPos]);
6135 // break;
6136 case kNumberOfDynamicScans:
6137 //~d.numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]);
6138 numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]);
6139 break;
6140 case kMRAcquisitionType: //detect 3D acquisition: we can reorient these without worrying about slice time correct or BVEC/BVAL orientation
6141 if (lLength > 1)
6142 d.is2DAcq = (buffer[lPos] == '2') && (toupper(buffer[lPos + 1]) == 'D');
6143 if (lLength > 1)
6144 d.is3DAcq = (buffer[lPos] == '3') && (toupper(buffer[lPos + 1]) == 'D');
6145 //dcmStr(lLength, &buffer[lPos], d.mrAcquisitionType);
6146 break;
6147 case kBodyPartExamined: {
6148 dcmStr(lLength, &buffer[lPos], d.bodyPartExamined);
6149 break;
6150 }
6151 case kScanningSequence: {
6152 dcmStr(lLength, &buffer[lPos], d.scanningSequence);
6153 //According to the DICOM standard 0018,9018 is REQUIRED for EPI raw data
6154 // http://dicom.nema.org/MEDICAL/Dicom/2015c/output/chtml/part03/sect_C.8.13.4.html
6155 //In practice, this is not the case for all vendors
6156 //Fortunately, the combination of 0018,0020 and 0018,9018 appears to reliably detect EPI data
6157 //Siemens (pre-XA) omits 0018,9018, but reports [EP] for 0018,0020 (regardless of SE/GR)
6158 //Siemens (XA) reports 0018,9018 but omits 0018,0020
6159 //Canon/Toshiba omits 0018,9018, but reports [SE\EP];[GR\EP] for 0018,0020
6160 //GE omits 0018,9018, but reports [EP\GR];[EP\SE] for 0018,0020
6161 //Philips reports 0018,9018, but reports [SE];[GR] for 0018,0020
6162 if ((lLength > 1) && (strstr(d.scanningSequence, "IR") != NULL))
6163 d.isIR = true;
6164 if ((lLength > 1) && (strstr(d.scanningSequence, "EP") != NULL))
6165 d.isEPI = true;
6166 break; //warp
6167 }
6168 case kSequenceVariant21:
6169 if (d.manufacturer != kMANUFACTURER_SIEMENS)
6170 break; //see GE dataset in dcm_qa_nih
6171 //fall through...
6172 case kSequenceVariant: {
6173 dcmStr(lLength, &buffer[lPos], d.sequenceVariant);
6174 break;
6175 }
6176 case kScanOptions:
6177 dcmStr(lLength, &buffer[lPos], d.scanOptions);
6178 if ((lLength > 1) && (strstr(d.scanOptions, "PFF") != NULL))
6179 d.isPartialFourier = true; //e.g. GE does not populate (0018,9081)
6180 break;
6181 case kSequenceName: {
6182 dcmStr(lLength, &buffer[lPos], d.sequenceName);
6183 break;
6184 }
6185 case kMRfMRIStatusIndicationPhilips: {//fmri volume number
6186 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6187 break;
6188 int i = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6189 if (i > 0) //only if positive value, see Magdeburg_2014 sample data from dcm_qa_philips Philips MR 51.0
6190 volumeNumber = i;
6191 break;
6192 }
6193 case kMRAcquisitionTypePhilips: //kMRAcquisitionType
6194 if (lLength > 1)
6195 d.is3DAcq = (buffer[lPos] == '3') && (toupper(buffer[lPos + 1]) == 'D');
6196 break;
6197 case kAngulationRL:
6198 d.angulation[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6199 break;
6200 case kAngulationAP:
6201 d.angulation[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6202 break;
6203 case kAngulationFH:
6204 d.angulation[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6205 break;
6206 case kMRStackOffcentreRL:
6207 d.stackOffcentre[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6208 break;
6209 case kMRStackOffcentreAP:
6210 d.stackOffcentre[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6211 break;
6212 case kMRStackOffcentreFH:
6213 d.stackOffcentre[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6214 break;
6215 case kSliceOrient: {
6216 char orientStr[kDICOMStr];
6217 orientStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr
6218 dcmStr(lLength, &buffer[lPos], orientStr);
6219 if (toupper(orientStr[0]) == 'S')
6220 d.sliceOrient = kSliceOrientSag; //sagittal
6221 else if (toupper(orientStr[0]) == 'C')
6222 d.sliceOrient = kSliceOrientCor; //coronal
6223 else
6224 d.sliceOrient = kSliceOrientTra; //transverse (axial)
6225 break;
6226 }
6227 case kElscintIcon:
6228 printWarning("Assuming icon SQ 07a3,10ce.\n");
6229 isIconImageSequence = true;
6230 if (sqDepthIcon < 0)
6231 sqDepthIcon = sqDepth;
6232 break;
6233 case kPMSCT_RLE1:
6234 //https://groups.google.com/forum/#!topic/comp.protocols.dicom/8HuP_aNy9Pc
6235 //https://discourse.slicer.org/t/fail-to-load-pet-ct-gemini/8158/3
6236 // d.compressionScheme = kCompressPMSCT_RLE1; //force RLE
6237 if (d.compressionScheme != kCompressPMSCT_RLE1)
6238 break;
6239 d.imageStart = (int)lPos + (int)lFileOffset;
6240 d.imageBytes = lLength;
6241 break;
6242 case kPrivateCreator: {
6243 if (d.manufacturer != kMANUFACTURER_UNKNOWN)
6244 break;
6245 d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]);
6246 volDiffusion.manufacturer = d.manufacturer;
6247 break;
6248 }
6249 case kDiffusion_bValuePhilips:
6250 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6251 break;
6252 B0Philips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6253 set_bVal(&volDiffusion, B0Philips);
6254 break;
6255 case kPhaseNumber: //IS issue529
6256 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6257 break;
6258 d.phaseNumber = dcmStrInt(lLength, &buffer[lPos]); //see dcm_qa_philips_asl
6259 break;
6260 case kCardiacSync: //CS [TRIGGERED],[NO]
6261 if (lLength < 2)
6262 break;
6263 if (toupper(buffer[lPos]) != 'N')
6264 isTriggerSynced = true;
6265 break;
6266 case kDiffusion_bValue: // 0018,9087
6267 if (d.manufacturer == kMANUFACTURER_UNKNOWN) {
6268 d.manufacturer = kMANUFACTURER_PHILIPS;
6269 printWarning("Found 0018,9087 but manufacturer (0008,0070) unknown: assuming Philips.\n");
6270 }
6271 // Note that this is ahead of kImagePositionPatient (0020,0032), so
6272 // isAtFirstPatientPosition is not necessarily set yet.
6273 // Philips uses this tag too, at least as of 5.1, but they also
6274 // use kDiffusionBFactor (see above), and we do not want to
6275 // double count. More importantly, with Philips this tag
6276 // (sometimes?) gets repeated in a nested sequence with the
6277 // value *unset*!
6278 // GE started using this tag in 27, and annoyingly, NOT including
6279 // the b value if it is 0 for the slice.
6280 //if((d.manufacturer != kMANUFACTURER_PHILIPS) || !is2005140FSQ){
6281 // d.CSA.numDti++;
6282 // if (d.CSA.numDti == 2) { //First time we know that this is a 4D DTI dataset
6283 // dti4D->S[0].V[0] = d.CSA.dtiV[0];
6284 // dti4D->S[0].V[1] = d.CSA.dtiV[1];
6285 // dti4D->S[0].V[2] = d.CSA.dtiV[2];
6286 // dti4D->S[0].V[3] = d.CSA.dtiV[3];
6287 //}
6288 B0Philips = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6289 //d.CSA.dtiV[0] = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6290 set_bVal(&volDiffusion, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian));
6291 // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D))
6292 // dti4D->S[d.CSA.numDti-1].V[0] = d.CSA.dtiV[0];
6293 //}
6294 break;
6295 case kDiffusionOrientation: // 0018, 9089
6296 // Note that this is ahead of kImagePositionPatient (0020,0032), so
6297 // isAtFirstPatientPosition is not necessarily set yet.
6298 // Philips uses this tag too, at least as of 5.1, but they also
6299 // use kDiffusionDirectionRL, etc., and we do not want to double
6300 // count. More importantly, with Philips this tag (sometimes?)
6301 // gets repeated in a nested sequence with the value *unset*!
6302 // if (((d.manufacturer == kMANUFACTURER_SIEMENS) ||
6303 // ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) &&
6304 // (isAtFirstPatientPosition || isnan(d.patientPosition[1])))
6305 //if((d.manufacturer == kMANUFACTURER_SIEMENS) || ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ))
6306 if ((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_HITACHI) || (d.manufacturer == kMANUFACTURER_SIEMENS) || (d.manufacturer == kMANUFACTURER_PHILIPS)) {
6307 //for kMANUFACTURER_HITACHI see https://nciphub.org/groups/qindicom/wiki/StandardcompliantenhancedmultiframeDWI
6308 float v[4];
6309 //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v);
6310 //dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian);
6311 dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian);
6312 vRLPhilips = v[0];
6313 vAPPhilips = v[1];
6314 vFHPhilips = v[2];
6315 //printMessage("><>< 0018,9089:\t%g\t%g\t%g\n", v[0], v[1], v[2]);
6316 //https://github.com/rordenlab/dcm2niix/issues/256
6317 //d.CSA.dtiV[1] = v[0];
6318 //d.CSA.dtiV[2] = v[1];
6319 //d.CSA.dtiV[3] = v[2];
6320 //printMessage("><>< 0018,9089: DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]);
6321 hasDwiDirectionality = true;
6322 d.isBVecWorldCoordinates = true; //e.g. Canon saved image space coordinates in Comments, world space in 0018, 9089
6323 set_orientation0018_9089(&volDiffusion, lLength, &buffer[lPos], d.isLittleEndian);
6324 }
6325 break;
6326 case kImagingFrequency2:
6327 d.imagingFrequency = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6328 break;
6329 case kParallelReductionFactorOutOfPlane:
6330 if (d.manufacturer == kMANUFACTURER_SIEMENS)
6331 break;
6332 d.accelFactOOP = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6333 break;
6334 //case kFrameAcquisitionDuration :
6335 // frameAcquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //issue369
6336 // break;
6337 case kDiffusionBValueXX: {
6338 if (!(d.manufacturer == kMANUFACTURER_BRUKER))
6339 break; //other manufacturers provide bvec directly, rather than bmatrix
6340 double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6341 set_bMatrix(&volDiffusion, bMat, 0);
6342 break;
6343 }
6344 case kDiffusionBValueXY: {
6345 if (!(d.manufacturer == kMANUFACTURER_BRUKER))
6346 break; //other manufacturers provide bvec directly, rather than bmatrix
6347 double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6348 set_bMatrix(&volDiffusion, bMat, 1);
6349 break;
6350 }
6351 case kDiffusionBValueXZ: {
6352 if (!(d.manufacturer == kMANUFACTURER_BRUKER))
6353 break; //other manufacturers provide bvec directly, rather than bmatrix
6354 double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6355 set_bMatrix(&volDiffusion, bMat, 2);
6356 break;
6357 }
6358 case kDiffusionBValueYY: {
6359 if (!(d.manufacturer == kMANUFACTURER_BRUKER))
6360 break; //other manufacturers provide bvec directly, rather than bmatrix
6361 double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6362 set_bMatrix(&volDiffusion, bMat, 3);
6363 break;
6364 }
6365 case kDiffusionBValueYZ: {
6366 if (!(d.manufacturer == kMANUFACTURER_BRUKER))
6367 break; //other manufacturers provide bvec directly, rather than bmatrix
6368 double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6369 set_bMatrix(&volDiffusion, bMat, 4);
6370 break;
6371 }
6372 case kDiffusionBValueZZ: {
6373 if (!(d.manufacturer == kMANUFACTURER_BRUKER))
6374 break; //other manufacturers provide bvec directly, rather than bmatrix
6375 double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6376 set_bMatrix(&volDiffusion, bMat, 5);
6377 d.isVectorFromBMatrix = true;
6378 break;
6379 }
6380 case kSliceNumberMrPhilips: {
6381 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6382 break;
6383 sliceNumberMrPhilips = dcmStrInt(lLength, &buffer[lPos]);
6384 int sliceNumber = sliceNumberMrPhilips;
6385 //use public patientPosition if it exists - fall back to private patient position
6386 if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) {
6387 for (int k = 0; k < 4; k++)
6388 patientPositionStartPhilips[k] = patientPosition[k];
6389 } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) {
6390 for (int k = 0; k < 4; k++)
6391 patientPositionStartPhilips[k] = patientPositionPrivate[k];
6392 }
6393 if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPosition[1]))) {
6394 for (int k = 0; k < 4; k++)
6395 patientPositionEndPhilips[k] = patientPosition[k];
6396 } else if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPositionPrivate[1]))) {
6397 for (int k = 0; k < 4; k++)
6398 patientPositionEndPhilips[k] = patientPositionPrivate[k];
6399 }
6400 break;
6401 }
6402 case kEPIFactorPhilips:
6403 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6404 break;
6405 echoTrainLengthPhil = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6406 break;
6407 case kPrepulseDelay: //FL
6408 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6409 break;
6410 d.TI = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6411 break;
6412 case kPrepulseType: //CS [INV]
6413 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6414 break;
6415 if (lLength < 3)
6416 break;
6417 if ((toupper(buffer[lPos]) != 'I') && (toupper(buffer[lPos + 1]) != 'N') && (toupper(buffer[lPos + 2]) != 'V'))
6418 d.isIR = true;
6419 break;
6420 case kRespirationSync: //CS [TRIGGERED],[NO]
6421 if (lLength < 2)
6422 break;
6423 if (toupper(buffer[lPos]) != 'N')
6424 isTriggerSynced = true;
6425 break;
6426 case kNumberOfSlicesMrPhilips:
6427 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6428 break;
6429 locationsInAcquisitionPhilips = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6430 //printMessage("====> locationsInAcquisitionPhilips\t%d\n", locationsInAcquisitionPhilips);
6431 break;
6432 case kPartialMatrixScannedPhilips:
6433 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6434 break;
6435 if (lLength < 2)
6436 break;
6437 if (toupper(buffer[lPos]) == 'Y')
6438 d.isPartialFourier = true;
6439 break;
6440 case kWaterFatShiftPhilips:
6441 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6442 break;
6443 d.waterFatShift = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6444 break;
6445 case kDiffusionDirectionRL:
6446 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6447 break;
6448 vRLPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6449 set_diffusion_directionPhilips(&volDiffusion, vRLPhilips, 0);
6450 break;
6451 case kDiffusionDirectionAP:
6452 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6453 break;
6454 vAPPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6455 set_diffusion_directionPhilips(&volDiffusion, vAPPhilips, 1);
6456 break;
6457 case kDiffusionDirectionFH:
6458 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6459 break;
6460 vFHPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6461 set_diffusion_directionPhilips(&volDiffusion, vFHPhilips, 2);
6462 break;
6463 case kPrivatePerFrameSq:
6464 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6465 break;
6466 //if ((vr[0] == 'S') && (vr[1] == 'Q')) break;
6467 //if (!is2005140FSQwarned)
6468 // printWarning("expected VR of 2005,140F to be 'SQ' (prior DICOM->DICOM conversion error?)\n");
6469 is2005140FSQ = true;
6470 //is2005140FSQwarned = true;
6471 break;
6472 case kMRImageGradientOrientationNumber :
6473 if (d.manufacturer == kMANUFACTURER_PHILIPS)
6474 gradientOrientationNumberPhilips = dcmStrInt(lLength, &buffer[lPos]);
6475 break;
6476 case kMRImageLabelType : //CS ??? LBL CTL
6477 if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 2)) break;
6478 //TODO529: issue529 for ASL LBL/CTL "LABEL"
6479 //if (toupper(buffer[lPos]) == 'L') isLabel = true;
6480 if (toupper(buffer[lPos]) == 'L') d.aslFlags = kASL_FLAG_PHILIPS_LABEL;
6481 if (toupper(buffer[lPos]) == 'C') d.aslFlags = kASL_FLAG_PHILIPS_CONTROL;
6482 break;
6483 case kMRImageDiffBValueNumber:
6484 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6485 break;
6486 philMRImageDiffBValueNumber = dcmStrInt(lLength, &buffer[lPos]);
6487 break;
6488 case kMRImageDiffVolumeNumber:
6489 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6490 break;
6491 philMRImageDiffVolumeNumber = dcmStrInt(lLength, &buffer[lPos]);
6492 break;
6493 case kWaveformSq:
6494 d.imageStart = 1; //abort!!!
6495 printMessage("Skipping DICOM (audio not image) '%s'\n", fname);
6496 break;
6497 case kSpectroscopyData: //kSpectroscopyDataPointColumns
6498 printMessage("Skipping Spectroscopy DICOM '%s'\n", fname);
6499 d.imageStart = (int)lPos + (int)lFileOffset;
6500 break;
6501 case kCSAImageHeaderInfo:
6502 if ((lPos + lLength) > fileLen)
6503 break;
6504 readCSAImageHeader(&buffer[lPos], lLength, &d.CSA, isVerbose, d.is3DAcq); //, dti4D);
6505 if (!d.isHasPhase)
6506 d.isHasPhase = d.CSA.isPhaseMap;
6507 break;
6508 case kCSASeriesHeaderInfo:
6509 if ((lPos + lLength) > fileLen)
6510 break;
6511 d.CSA.SeriesHeader_offset = (int)lPos;
6512 d.CSA.SeriesHeader_length = lLength;
6513 break;
6514 case kRealWorldIntercept:
6515 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6516 break;
6517 d.RWVIntercept = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6518 if (isSameFloat(0.0, d.intenIntercept)) //give precedence to standard value
6519 d.intenIntercept = d.RWVIntercept;
6520 break;
6521 case kRealWorldSlope:
6522 if (d.manufacturer != kMANUFACTURER_PHILIPS)
6523 break;
6524 d.RWVScale = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian);
6525 if (d.RWVScale > 1.0E38)
6526 d.RWVScale = 0.0;
6527 else if (isSameFloat(1.0, d.intenScale)) //give precedence to standard value
6528 d.intenScale = d.RWVScale;
6529 break;
6530 case kUserDefineDataGE: { //0043,102A
6531 if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 128))
6532 break;
6533 #define MY_DEBUG_GE // <- uncomment this to use following code to infer GE phase encoding direction
6534 #ifdef MY_DEBUG_GE
6535 int isVerboseX = isVerbose; //for debugging only - in standard release we will enable user defined "isVerbose"
6536 //int isVerboseX = 2;
6537 if (isVerboseX > 1)
6538 printMessage(" UserDefineDataGE file offset/length %ld %u\n", lFileOffset + lPos, lLength);
6539 if (lLength < 916) { //minimum size is hdr_offset=0, read 0x0394
6540 printMessage(" GE header too small to be valid (A)\n");
6541 break;
6542 }
6543 //debug code to export binary data
6544 /*
6545 char str[kDICOMStr];
6546 sprintf(str, "%s_ge.bin",fname);
6547 FILE *pFile = fopen(str, "wb");
6548 fwrite(&buffer[lPos], 1, lLength, pFile);
6549 fclose (pFile);
6550 */
6551 if ((size_t)(lPos + lLength) > MaxBufferSz) {
6552 //we could re-read the buffer in this case, however in practice GE headers are concise so we never see this issue
6553 printMessage(" GE header overflows buffer\n");
6554 break;
6555 }
6556 uint16_t hdr_offset = dcmInt(2, &buffer[lPos + 24], true);
6557 if (isVerboseX > 1)
6558 printMessage(" header offset: %d\n", hdr_offset);
6559 if (lLength < (hdr_offset + 916)) { //minimum size is hdr_offset=0, read 0x0394
6560 printMessage(" GE header too small to be valid (B)\n");
6561 break;
6562 }
6563 //size_t hdr = lPos+hdr_offset;
6564 float version = dcmFloat(4, &buffer[lPos + hdr_offset], true);
6565 if (isVerboseX > 1)
6566 printMessage(" version %g\n", version);
6567 if (version < 5.0 || version > 40.0) {
6568 //printMessage(" GE header file format incorrect %g\n", version);
6569 break;
6570 }
6571 //char const *hdr = &buffer[lPos + hdr_offset];
6572 char *hdr = (char *)&buffer[lPos + hdr_offset];
6573 int epi_chk_off = 0x003a;
6574 int pepolar_off = 0x0030;
6575 int kydir_off = 0x0394;
6576 if (version >= 25.002) {
6577 hdr += 0x004c;
6578 kydir_off -= 0x008c;
6579 }
6580 //int seqOrInter =dcmInt(2,(unsigned char*)(hdr + pepolar_off-638),true);
6581 //int seqOrInter2 =dcmInt(2,(unsigned char*)(hdr + kydir_off-638),true);
6582 //printf("%d %d<<<\n", seqOrInter,seqOrInter2);
6583 //check if EPI
6584 if (true) {
6585 //int check = *(short const *)(hdr + epi_chk_off) & 0x800;
6586 int check = dcmInt(2, (unsigned char *)hdr + epi_chk_off, true) & 0x800;
6587 if (check == 0) {
6588 if (isVerboseX > 1)
6589 printMessage("%s: Warning: Data is not EPI\n", fname);
6590 break;
6591 }
6592 }
6593 //Check for PE polarity
6594 // int flag1 = *(short const *)(hdr + pepolar_off) & 0x0004;
6595 //Check for ky direction (view order)
6596 // int flag2 = *(int const *)(hdr + kydir_off);
6597 int phasePolarityFlag = dcmInt(2, (unsigned char *)hdr + pepolar_off, true) & 0x0004;
6598 //Check for ky direction (view order)
6599 int sliceOrderFlag = dcmInt(2, (unsigned char *)hdr + kydir_off, true);
6600 if (isVerboseX > 1)
6601 printMessage(" GE phasePolarity/sliceOrder flags %d %d\n", phasePolarityFlag, sliceOrderFlag);
6602 if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_FLIPPED)
6603 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
6604 if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED)
6605 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
6606 if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) {
6607 //https://cfmriweb.ucsd.edu/Howto/3T/operatingtips.html
6608 if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED)
6609 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
6610 else
6611 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
6612 }
6613 //if (sliceOrderFlag == kGE_SLICE_ORDER_TOP_DOWN)
6614 // d.sliceOrderGE = kGE_SLICE_ORDER_TOP_DOWN;
6615 //if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP)
6616 // d.sliceOrderGE = kGE_SLICE_ORDER_BOTTOM_UP;
6617 #endif
6618 break;
6619 }
6620 case kEffectiveEchoSpacingGE:
6621 if (d.manufacturer == kMANUFACTURER_GE)
6622 d.effectiveEchoSpacingGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6623 break;
6624 case kImageTypeGE: { //0/1/2/3 for magnitude/phase/real/imaginary
6625 if (d.manufacturer != kMANUFACTURER_GE)
6626 break;
6627 int dt = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6628 if (dt == 0)
6629 d.isHasMagnitude = true;
6630 if (dt == 1)
6631 d.isHasPhase = true;
6632 if (dt == 2)
6633 d.isHasReal = true;
6634 if (dt == 3)
6635 d.isHasImaginary = true;
6636 break;
6637 }
6638 case kDiffusion_bValueGE:
6639 if (d.manufacturer == kMANUFACTURER_GE) {
6640 d.CSA.dtiV[0] = (float)set_bValGE(&volDiffusion, lLength, &buffer[lPos]);
6641 d.CSA.numDti = 1;
6642 }
6643 break;
6644 case kEpiRTGroupDelayGE: //FL
6645 if (d.manufacturer != kMANUFACTURER_GE)
6646 break;
6647 d.groupDelay = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian);
6648 d.groupDelay *= 1000.0; //sec -> ms
6649 // If kEpiRTGroupDelayGE (0043,107C) exists, epiRT
6650 d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT
6651 break;
6652 case kAssetRFactorsGE: { //DS issue427GE
6653 if (d.manufacturer != kMANUFACTURER_GE)
6654 break;
6655 float PhaseSlice[3];
6656 dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, PhaseSlice);
6657 if (PhaseSlice[1] > 0.0)
6658 d.accelFactPE = 1.0f / PhaseSlice[1];
6659 if (PhaseSlice[2] > 0.0)
6660 d.accelFactOOP = 1.0f / PhaseSlice[2];
6661 break;
6662 }
6663 case kASLContrastTechniqueGE: { //CS
6664 if (d.manufacturer != kMANUFACTURER_GE)
6665 break;
6666 char st[kDICOMStr];
6667 //aslFlags
6668 dcmStr(lLength, &buffer[lPos], st);
6669 if (strstr(st, "PSEUDOCONTINUOUS") != NULL)
6670 d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PSEUDOCONTINUOUS);
6671 else if (strstr(st, "CONTINUOUS") != NULL)
6672 d.aslFlags = (d.aslFlags | kASL_FLAG_GE_CONTINUOUS);
6673 break;
6674 }
6675 case kASLLabelingTechniqueGE: { //LO issue427GE
6676 if (d.manufacturer != kMANUFACTURER_GE)
6677 break;
6678 char st[kDICOMStr];
6679 dcmStr(lLength, &buffer[lPos], st);
6680 if (strstr(st, "3D continuous") != NULL)
6681 d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DCASL);
6682 if (strstr(st, "3D pulsed continuous") != NULL)
6683 d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DPCASL);
6684 break;
6685 }
6686 case kDurationLabelPulseGE: { //IS
6687 if (d.manufacturer != kMANUFACTURER_GE)
6688 break;
6689 d.durationLabelPulseGE = dcmStrInt(lLength, &buffer[lPos]);
6690 break;
6691 }
6692 case kMultiBandGE: { //LO issue427GE
6693 if (d.manufacturer != kMANUFACTURER_GE)
6694 break;
6695 //LO array: Value 1 = Multiband factor, Value 2 = Slice FOV Shift Factor, Value 3 = Calibration method
6696 int mb = dcmStrInt(lLength, &buffer[lPos]);
6697 if (mb > 1)
6698 d.CSA.multiBandFactor = mb;
6699 break;
6700 }
6701 case kGeiisFlag:
6702 if ((lLength > 4) && (buffer[lPos] == 'G') && (buffer[lPos + 1] == 'E') && (buffer[lPos + 2] == 'I') && (buffer[lPos + 3] == 'I')) {
6703 //read a few digits, as bug is specific to GEIIS, while GEMS are fine
6704 printWarning("GEIIS violates the DICOM standard. Inspect results and admonish your vendor.\n");
6705 isIconImageSequence = true;
6706 if (sqDepthIcon < 0)
6707 sqDepthIcon = sqDepth;
6708 //geiisBug = true; //compressed thumbnails do not follow transfer syntax! GE should not re-use pulbic tags for these proprietary images http://sonca.kasshin.net/gdcm/Doc/GE_ImageThumbnails
6709 }
6710 break;
6711 case kStudyComments: {
6712 //char commentStr[kDICOMStr];
6713 //dcmStr(lLength, &buffer[lPos], commentStr);
6714 //printf(">> %s\n", commentStr);
6715 break;
6716 }
6717 case kProcedureStepDescription:
6718 dcmStr(lLength, &buffer[lPos], d.procedureStepDescription);
6719 break;
6720 case kOrientationACR: //use in emergency if kOrientation is not present!
6721 if (!isOrient)
6722 dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient);
6723 break;
6724 case kOrientation: {
6725 if (isOrient) { //already read orient - read for this slice to see if it varies (localizer)
6726 float orient[7];
6727 dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, orient);
6728 if ((!isSameFloatGE(d.orient[1], orient[1]) || !isSameFloatGE(d.orient[2], orient[2]) || !isSameFloatGE(d.orient[3], orient[3]) ||
6729 !isSameFloatGE(d.orient[4], orient[4]) || !isSameFloatGE(d.orient[5], orient[5]) || !isSameFloatGE(d.orient[6], orient[6]))) {
6730 if (!d.isLocalizer)
6731 printMessage("slice orientation varies (localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n",
6732 d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6],
6733 orient[1], orient[2], orient[3], orient[4], orient[5], orient[6]);
6734 d.isLocalizer = true;
6735 }
6736 }
6737 dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient);
6738 vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]);
6739 vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]);
6740 sliceV = crossProduct(readV, phaseV);
6741 //printf("sliceV %g %g %g\n", sliceV.v[0], sliceV.v[1], sliceV.v[2]);
6742 isOrient = true;
6743 break;
6744 }
6745 case kTemporalPosition: //fall through, both kSliceNumberMrPhilips (2001,100A) and kTemporalPosition are is
6746 volumeNumber = dcmStrInt(lLength, &buffer[lPos]);
6747 //temporalPositionIdentifier = volumeNumber;
6748 break;
6749 case kTemporalResolution:
6750 temporalResolutionMS = dcmStrFloat(lLength, &buffer[lPos]);
6751 break;
6752 case kImagesInAcquisition:
6753 imagesInAcquisition = dcmStrInt(lLength, &buffer[lPos]);
6754 break;
6755 //case kSliceLocation : //optional so useless, infer from image position patient (0020,0032) and image orientation (0020,0037)
6756 // sliceLocation = dcmStrFloat(lLength, &buffer[lPos]);
6757 // break;
6758 case kImageStart:
6759 //if ((!geiisBug) && (!isIconImageSequence)) //do not exit for proprietary thumbnails
6760 if (isIconImageSequence) {
6761 //20200116 see example from Tashrif Bilah that saves GEIIS thumbnails uncompressed
6762 // therefore, the next couple lines are not a perfect detection for GEIIS thumbnail icons
6763 //int imgBytes = (d.xyzDim[1] * d.xyzDim[2] * int(d.bitsAllocated / 8));
6764 //if (imgBytes == lLength)
6765 // isIconImageSequence = false;
6766 if ((isIconImageSequence) && (sqDepth < 1))
6767 printWarning("Assuming 7FE0,0010 refers to an icon not the main image\n");
6768 }
6769 if ((d.compressionScheme == kCompressNone) && (!isIconImageSequence)) //do not exit for proprietary thumbnails
6770 d.imageStart = (int)lPos + (int)lFileOffset;
6771 //geiisBug = false;
6772 //http://www.dclunie.com/medical-image-faq/html/part6.html
6773 //unlike raw data, Encapsulated data is stored as Fragments contained in Items that are the Value field of Pixel Data
6774 if ((d.compressionScheme != kCompressNone) && (!isIconImageSequence)) {
6775 isEncapsulatedData = true;
6776 encapsulatedDataImageStart = (int)lPos + (int)lFileOffset;
6777 lLength = 0;
6778 }
6779 isIconImageSequence = false;
6780 break;
6781 case kImageStartFloat:
6782 d.isFloat = true;
6783 if (!isIconImageSequence) //do not exit for proprietary thumbnails
6784 d.imageStart = (int)lPos + (int)lFileOffset;
6785 isIconImageSequence = false;
6786 break;
6787 case kImageStartDouble:
6788 printWarning("Double-precision DICOM conversion untested: please provide samples to developer\n");
6789 d.isFloat = true;
6790 if (!isIconImageSequence) //do not exit for proprietary thumbnails
6791 d.imageStart = (int)lPos + (int)lFileOffset;
6792 isIconImageSequence = false;
6793 break;
6794 } //switch/case for groupElement
6795 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
6796 //even group numbers 0x6000..0x601E
6797 int overlayN = ((groupElement & 0xFF) >> 1);
6798 //printf("%08x %d %d\n", groupElement, (groupElement & 0xFF), overlayN);
6799 int element = groupElement >> 16;
6800 switch (element) {
6801 case 0x0010: //US OverlayRows
6802 overlayRows = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6803 break;
6804 case 0x0011: //US OverlayColumns
6805 overlayCols = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6806 break;
6807 case 0x0050: { //SSx2! OverlayOrigin
6808 if (lLength != 4)
6809 break;
6810 int row = dcmInt(2, &buffer[lPos], d.isLittleEndian);
6811 int col = dcmInt(2, &buffer[lPos + 2], d.isLittleEndian);
6812 if ((row == 1) && (col == 1))
6813 break;
6814 printMessage("Unsupported overlay origin %d/%d\n", row, col);
6815 overlayOK = false;
6816 break;
6817 }
6818 case 0x0100: { //US OverlayBitsAllocated
6819 int bits = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6820 if (bits == 1)
6821 break;
6822 //old style Burned-In
6823 printMessage("Illegal/Obsolete DICOM: Overlay Bits Allocated must be 1, not %d\n", bits);
6824 overlayOK = false;
6825 break;
6826 }
6827 case 0x0102: { //US OverlayBitPosition
6828 int pos = dcmInt(lLength, &buffer[lPos], d.isLittleEndian);
6829 if (pos == 0)
6830 break;
6831 //old style Burned-In
6832 printMessage("Illegal/Obsolete DICOM: Overlay Bit Position shall be 0, not %d\n", pos);
6833 overlayOK = false;
6834 break;
6835 }
6836 case 0x3000: {
6837 d.overlayStart[overlayN] = (int)lPos + (int)lFileOffset;
6838 d.isHasOverlay = true;
6839 break;
6840 }
6841 }
6842 } //Group 60xx even values 0x6000..0x601E https://www.medicalconnections.co.uk/kb/Number-Of-Overlays-In-Image/
6843 #ifndef USING_R
6844 if (isVerbose > 1) {
6845 //dcm2niix i fast because it does not use a dictionary.
6846 // this is a very incomplete DICOM header report, and not a substitute for tools like dcmdump
6847 // the purpose is to see how dcm2niix has parsed the image for diagnostics
6848 // this section will report very little for implicit data
6849 //if (d.isHasReal) printf("r");else printf("m");
6850 char str[kDICOMStr];
6851 sprintf(str, "%*c%04x,%04x %u@%ld ", sqDepth + 1, ' ', groupElement & 65535, groupElement >> 16, lLength, lFileOffset + lPos);
6852 bool isStr = false;
6853 if (d.isExplicitVR) {
6854 sprintf(str, "%s%c%c ", str, vr[0], vr[1]);
6855 if ((vr[0] == 'F') && (vr[1] == 'D'))
6856 sprintf(str, "%s%g ", str, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian));
6857 if ((vr[0] == 'F') && (vr[1] == 'L'))
6858 sprintf(str, "%s%g ", str, dcmFloat(lLength, &buffer[lPos], d.isLittleEndian));
6859 if ((vr[0] == 'S') && (vr[1] == 'S'))
6860 sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
6861 if ((vr[0] == 'S') && (vr[1] == 'L'))
6862 sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
6863 if ((vr[0] == 'U') && (vr[1] == 'S'))
6864 sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
6865 if ((vr[0] == 'U') && (vr[1] == 'L'))
6866 sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian));
6867 if ((vr[0] == 'A') && (vr[1] == 'E'))
6868 isStr = true;
6869 if ((vr[0] == 'A') && (vr[1] == 'S'))
6870 isStr = true;
6871 if ((vr[0] == 'C') && (vr[1] == 'S'))
6872 isStr = true;
6873 if ((vr[0] == 'D') && (vr[1] == 'A'))
6874 isStr = true;
6875 if ((vr[0] == 'D') && (vr[1] == 'S'))
6876 isStr = true;
6877 if ((vr[0] == 'D') && (vr[1] == 'T'))
6878 isStr = true;
6879 if ((vr[0] == 'I') && (vr[1] == 'S'))
6880 isStr = true;
6881 if ((vr[0] == 'L') && (vr[1] == 'O'))
6882 isStr = true;
6883 if ((vr[0] == 'L') && (vr[1] == 'T'))
6884 isStr = true;
6885 //if ((vr[0]=='O') && (vr[1]=='B')) isStr = xxx;
6886 //if ((vr[0]=='O') && (vr[1]=='D')) isStr = xxx;
6887 //if ((vr[0]=='O') && (vr[1]=='F')) isStr = xxx;
6888 //if ((vr[0]=='O') && (vr[1]=='W')) isStr = xxx;
6889 if ((vr[0] == 'P') && (vr[1] == 'N'))
6890 isStr = true;
6891 if ((vr[0] == 'S') && (vr[1] == 'H'))
6892 isStr = true;
6893 if ((vr[0] == 'S') && (vr[1] == 'T'))
6894 isStr = true;
6895 if ((vr[0] == 'T') && (vr[1] == 'M'))
6896 isStr = true;
6897 if ((vr[0] == 'U') && (vr[1] == 'I'))
6898 isStr = true;
6899 if ((vr[0] == 'U') && (vr[1] == 'T'))
6900 isStr = true;
6901 } else
6902 isStr = (lLength > 12); //implicit encoding: not always true as binary vectors may exceed 12 bytes, but often true
6903 if (lLength > 128) {
6904 sprintf(str, "%s<%d bytes> ", str, lLength);
6905 printMessage("%s\n", str);
6906 } else if (isStr) { //if length is greater than 8 bytes (+4 hdr) the MIGHT be a string
6907 char tagStr[kDICOMStr];
6908 //tagStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr
6909 strcpy(tagStr, "");
6910 if (lLength > 0)
6911 dcmStr(lLength, &buffer[lPos], tagStr);
6912 if (strlen(tagStr) > 1) {
6913 for (size_t pos = 0; pos < strlen(tagStr); pos++)
6914 if ((tagStr[pos] == '<') || (tagStr[pos] == '>') || (tagStr[pos] == ':') || (tagStr[pos] == '"') || (tagStr[pos] == '\\') || (tagStr[pos] == '/') || (tagStr[pos] < 32) //issue398
6915 //|| (tagStr[pos] == '^') || (tagStr[pos] < 33)
6916 || (tagStr[pos] == '*') || (tagStr[pos] == '|') || (tagStr[pos] == '?'))
6917 tagStr[pos] = '_';
6918 }
6919 printMessage("%s %s\n", str, tagStr);
6920 } else
6921 printMessage("%s\n", str);
6922 //if (d.isExplicitVR) printMessage(" VR=%c%c\n", vr[0], vr[1]);
6923 } //printMessage(" tag=%04x,%04x length=%u pos=%ld %c%c nest=%d\n", groupElement & 65535,groupElement>>16, lLength, lPos,vr[0], vr[1], nest);
6924 #endif
6925 lPos = lPos + (lLength);
6926 } //while d.imageStart == 0
6927 free(buffer);
6928 if (d.bitsStored < 0)
6929 d.isValid = false;
6930 if (d.bitsStored == 1)
6931 printWarning("1-bit binary DICOMs not supported\n"); //maybe not valid - no examples to test
6932 //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]);
6933 //printMessage("><>< DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]);
6934 if (encapsulatedDataFragmentStart > 0) {
6935 if ((encapsulatedDataFragments > 1) && (encapsulatedDataFragments == numberOfFrames) && (encapsulatedDataFragments < kMaxDTI4D)) {
6936 printWarning("Compressed image stored as %d fragments: if conversion fails decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname);
6937 d.imageStart = encapsulatedDataFragmentStart;
6938 } else if (encapsulatedDataFragments > 1) {
6939 printError("Compressed image stored as %d fragments: decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname);
6940 } else {
6941 d.imageStart = encapsulatedDataFragmentStart;
6942 //dti4D->fragmentOffset[0] = -1;
6943 }
6944 } else if ((isEncapsulatedData) && (d.imageStart < 128)) {
6945 //http://www.dclunie.com/medical-image-faq/html/part6.html
6946 //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
6947 printWarning("DICOM violation (contact vendor): compressed image without image fragments, assuming image offset defined by 0x7FE0,x0010: %s\n", fname);
6948 d.imageStart = encapsulatedDataImageStart;
6949 }
6950 if ((d.manufacturer == kMANUFACTURER_GE) && (d.groupDelay > 0.0))
6951 d.TR += d.groupDelay; //Strangely, for GE the sample rate is (0018,0080) + ((0043,107c) * 1000.0)
6952 if ((d.modality == kMODALITY_PT) && (PETImageIndex > 0)) {
6953 d.imageNum = PETImageIndex; //https://github.com/rordenlab/dcm2niix/issues/184
6954 //printWarning("PET scan using 0054,1330 for image number %d\n", PETImageIndex);
6955 }
6956 if (d.isHasOverlay) {
6957 if ((overlayCols > 0) && (d.xyzDim[1] != overlayCols))
6958 overlayOK = false;
6959 if ((overlayRows > 0) && (d.xyzDim[2] != overlayRows))
6960 overlayOK = false;
6961 if (!overlayOK)
6962 d.isHasOverlay = false;
6963 }
6964 //Recent Philips images include DateTime (0008,002A) but not separate date and time (0008,0022 and 0008,0032)
6965 #define kYYYYMMDDlen 8 //how many characters to encode year,month,day in "YYYYDDMM" format
6966 if ((strlen(acquisitionDateTimeTxt) > (kYYYYMMDDlen + 5)) && (!isFloatDiff(d.acquisitionTime, 0.0f)) && (!isFloatDiff(d.acquisitionDate, 0.0f))) {
6967 // 20161117131643.80000 -> date 20161117 time 131643.80000
6968 //printMessage("acquisitionDateTime %s\n",acquisitionDateTimeTxt);
6969 char acquisitionDateTxt[kDICOMStr];
6970 memcpy(acquisitionDateTxt, acquisitionDateTimeTxt, kYYYYMMDDlen);
6971 acquisitionDateTxt[kYYYYMMDDlen] = '\0'; // IMPORTANT!
6972 d.acquisitionDate = atof(acquisitionDateTxt);
6973 char acquisitionTimeTxt[kDICOMStr];
6974 int timeLen = (int)strlen(acquisitionDateTimeTxt) - kYYYYMMDDlen;
6975 strncpy(acquisitionTimeTxt, &acquisitionDateTimeTxt[kYYYYMMDDlen], timeLen);
6976 acquisitionTimeTxt[timeLen] = '\0'; // IMPORTANT!
6977 d.acquisitionTime = atof(acquisitionTimeTxt);
6978 }
6979 d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime);
6980 //printMessage("slices in Acq %d %d %g %g\n",locationsInAcquisitionGE, d.locationsInAcquisition, d.xyzMM[3], d.zSpacing);
6981 if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.locationsInAcquisition == 0))
6982 d.locationsInAcquisition = locationsInAcquisitionPhilips;
6983 if ((d.manufacturer == kMANUFACTURER_GE) && (imagesInAcquisition > 0))
6984 d.locationsInAcquisition = imagesInAcquisition; //e.g. if 72 slices acquired but interpolated as 144
6985 if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition > 0) && (locationsInAcquisitionGE > 0) && (d.locationsInAcquisition != locationsInAcquisitionGE)) {
6986 if (isVerbose)
6987 printMessage("Check number of slices, discrepancy between tags (0020,1002; 0021,104F; 0054,0081) (%d vs %d) %s\n", locationsInAcquisitionGE, d.locationsInAcquisition, fname);
6988 /* SAH.start: Fix for ZIP2 */
6989 int zipFactor = (int)roundf(d.xyzMM[3] / d.zSpacing);
6990 if (zipFactor > 1) {
6991 d.interp3D = zipFactor;
6992 //printMessage("Issue 373: Check for ZIP2 Factor: %d SliceThickness+SliceGap: %f, SpacingBetweenSlices: %f \n", zipFactor, d.xyzMM[3], d.zSpacing);
6993 locationsInAcquisitionGE *= zipFactor; // Multiply number of slices by ZIP factor. Do this prior to checking for conflict below (?).
6994 }
6995 if (isGEfieldMap) { //issue501 : to do check zip factor
6996 //Volume 1) derived phase field map [Hz] and 2) magnitude volume.
6997 d.isDerived = (d.imageNum <= locationsInAcquisitionGE); //first volume
6998 d.isRealIsPhaseMapHz = d.isDerived;
6999 d.isHasReal = d.isDerived;
7000 }
7001 /* SAH.end */
7002 if (locationsInAcquisitionGE < d.locationsInAcquisition) {
7003 d.locationsInAcquisitionConflict = d.locationsInAcquisition;
7004 d.locationsInAcquisition = locationsInAcquisitionGE;
7005 } else
7006 d.locationsInAcquisitionConflict = locationsInAcquisitionGE;
7007 }
7008 if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition == 0))
7009 d.locationsInAcquisition = locationsInAcquisitionGE;
7010 if (d.zSpacing > 0.0)
7011 d.xyzMM[3] = d.zSpacing; //use zSpacing if provided: depending on vendor, kZThick may or may not include a slice gap
7012 //printMessage("patientPositions = %d XYZT = %d slicePerVol = %d numberOfDynamicScans %d\n",patientPositionNum,d.xyzDim[3], d.locationsInAcquisition, d.numberOfDynamicScans);
7013 if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (patientPositionNum > d.xyzDim[3])) {
7014 d.CSA.numDti = d.xyzDim[3]; //issue506
7015 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!
7016 }
7017 if ((d.imageStart > 144) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1))
7018 d.isValid = true;
7019 //if ((d.imageStart > 144) && (d.xyzDim[1] >= 1) && (d.xyzDim[2] >= 1) && (d.xyzDim[4] > 1)) //Spectroscopy
7020 // d.isValid = true;
7021 if ((d.xyzMM[1] > FLT_EPSILON) && (d.xyzMM[2] < FLT_EPSILON)) {
7022 printMessage("Please check voxel size\n");
7023 d.xyzMM[2] = d.xyzMM[1];
7024 }
7025 if ((d.xyzMM[2] > FLT_EPSILON) && (d.xyzMM[1] < FLT_EPSILON)) {
7026 printMessage("Please check voxel size\n");
7027 d.xyzMM[1] = d.xyzMM[2];
7028 }
7029 if ((d.xyzMM[3] < FLT_EPSILON)) {
7030 printMessage("Unable to determine slice thickness: please check voxel size\n");
7031 d.xyzMM[3] = 1.0;
7032 }
7033 //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]);
7034 //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);
7035 // printMessage("ser %ld\n", d.seriesNum);
7036 //int kEchoMult = 100; //For Siemens/GE Series 1,2,3... save 2nd echo as 201, 3rd as 301, etc
7037 //if (d.seriesNum > 100)
7038 // kEchoMult = 10; //For Philips data Saved as Series 101,201,301... save 2nd echo as 111, 3rd as 121, etc
7039 //if (coilNum > 0) //segment images with multiple coils
7040 // d.seriesNum = d.seriesNum + (100*coilNum);
7041 //if (d.echoNum > 1) //segment images with multiple echoes
7042 // d.seriesNum = d.seriesNum + (kEchoMult*d.echoNum);
7043 if (isPaletteColor) {
7044 d.isValid = false;
7045 d.isDerived = true; //to my knowledge, palette images always derived
7046 printWarning("Photometric Interpretation 'PALETTE COLOR' not supported\n");
7047 }
7048 if ((d.compressionScheme == kCompress50) && (d.bitsAllocated > 8)) {
7049 //dcmcjpg with +ee can create .51 syntax images that are 8,12,16,24-bit: we can only decode 8/24-bit
7050 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);
7051 d.isValid = false;
7052 }
7053 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)) {
7054 //n.b. in future check if frequency is in row or column direction (and same with phase)
7055 // >2 avoids detecting interpolated as mosaic, in future perhaps check "isInterpolated"
7056 numberOfImagesInMosaic = (d.xyzDim[1] / frequencyRows) * (d.xyzDim[2] / d.phaseEncodingLines);
7057 printWarning("Guessing this is a mosaic up to %d slices (issue 337).\n", numberOfImagesInMosaic);
7058 }
7059 if ((numberOfImagesInMosaic > 1) && (d.CSA.mosaicSlices < 1))
7060 d.CSA.mosaicSlices = numberOfImagesInMosaic;
7061 if (d.isXA10A)
7062 d.manufacturer = kMANUFACTURER_SIEMENS; //XA10A mosaics omit Manufacturer 0008,0070!
7063 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)) {
7064 d.CSA.mosaicSlices = (d.xyzDim[1] / d.phaseEncodingSteps) * (d.xyzDim[2] / d.phaseEncodingSteps);
7065 printWarning("Mosaic inferred without CSA header (check number of slices and spatial orientation)\n");
7066 }
7067 if ((d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1))
7068 d.CSA.mosaicSlices = -1; //mark as bogus DICOM
7069 if ((!d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) //See Erlangen Vida dataset - never reports "XA10" but mosaics have no attributes
7070 printWarning("0008,0008=MOSAIC but number of slices not specified: %s\n", fname);
7071 if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.dtiV[1] < -1.0) && (d.CSA.dtiV[2] < -1.0) && (d.CSA.dtiV[3] < -1.0))
7072 d.CSA.dtiV[0] = 0; //SiemensTrio-Syngo2004A reports B=0 images as having impossible b-vectors.
7073 if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1))
7074 strcpy(d.protocolName, d.seriesDescription);
7075 if ((strlen(d.protocolName) > 1) && (isMoCo))
7076 strcat(d.protocolName, "_MoCo"); //disambiguate MoCo https://github.com/neurolabusc/MRIcroGL/issues/31
7077 if ((strlen(d.protocolName) < 1) && (strlen(d.sequenceName) > 1) && (d.manufacturer != kMANUFACTURER_SIEMENS))
7078 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
7079 if (numberOfFrames == 0)
7080 numberOfFrames = d.xyzDim[3];
7081 if ((locationsInAcquisitionPhilips > 0) && ((d.xyzDim[3] % locationsInAcquisitionPhilips) == 0)) {
7082 d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips;
7083 d.xyzDim[3] = locationsInAcquisitionPhilips;
7084 } else if ((numberOfDynamicScans > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % numberOfDynamicScans) == 0)) {
7085 d.xyzDim[3] = d.xyzDim[3] / numberOfDynamicScans;
7086 d.xyzDim[4] = numberOfDynamicScans;
7087 }
7088 if ((maxInStackPositionNumber > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % maxInStackPositionNumber) == 0)) {
7089 d.xyzDim[4] = d.xyzDim[3] / maxInStackPositionNumber;
7090 d.xyzDim[3] = maxInStackPositionNumber;
7091 }
7092 if ((!isnan(patientPositionStartPhilips[1])) && (!isnan(patientPositionEndPhilips[1]))) {
7093 for (int k = 0; k < 4; k++) {
7094 d.patientPosition[k] = patientPositionStartPhilips[k];
7095 d.patientPositionLast[k] = patientPositionEndPhilips[k];
7096 }
7097 }
7098 if ((numberOfFrames > 1) && (locationsInAcquisitionPhilips > 0) && ((numberOfFrames % locationsInAcquisitionPhilips) != 0)) { //issue515
7099 printWarning("Number of frames (%d) not divisible by locations in acquisition (2001,1018) %d (issue 515)\n", numberOfFrames, locationsInAcquisitionPhilips);
7100 d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips;
7101 d.xyzDim[3] = locationsInAcquisitionPhilips;
7102 d.xyzDim[0] = numberOfFrames;
7103 }
7104 if ((B0Philips >= 0) && (d.CSA.numDti == 0)) {
7105 d.CSA.dtiV[0] = B0Philips;
7106 d.CSA.numDti = 1;
7107 } //issue409 Siemens XA saved as classic 2D not enhanced
7108 if (!isnan(patientPositionStartPhilips[1])) //for Philips data without
7109 for (int k = 0; k < 4; k++)
7110 d.patientPosition[k] = patientPositionStartPhilips[k];
7111 if (isVerbose) {
7112 printMessage("DICOM file: %s\n", fname);
7113 printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1], d.patientPosition[2], d.patientPosition[3]);
7114 if (!isnan(patientPositionEndPhilips[1]))
7115 printMessage(" patient position end (0020,0032)\t%g\t%g\t%g\n", patientPositionEndPhilips[1], patientPositionEndPhilips[2], patientPositionEndPhilips[3]);
7116 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]);
7117 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);
7118 if (d.CSA.dtiV[0] > 0)
7119 printMessage(" DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]);
7120 }
7121 if ((d.isValid) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1) && (d.imageStart < 132) && (!d.isRawDataStorage)) {
7122 //20190524: Philips MR 55.1 creates non-image files that report kDim1/kDim2 - we can detect them since 0008,0016 reports "RawDataStorage"
7123 //see https://neurostars.org/t/dcm2niix-error-from-philips-dicom-qsm-data-can-this-be-skipped/4883
7124 printError("Conversion aborted due to corrupt file: %s %dx%d %d\n", fname, d.xyzDim[1], d.xyzDim[2], d.imageStart);
7125 #ifdef USING_R
7126 Rf_error("Irrecoverable error during conversion");
7127 #else
7128 exit(kEXIT_CORRUPT_FILE_FOUND);
7129 #endif
7130 }
7131 if ((numberOfFrames > 1) && (numDimensionIndexValues == 0) && (numberOfFrames == nSliceMM)) { //issue 372
7132 fidx *objects = (fidx *)malloc(sizeof(struct fidx) * numberOfFrames);
7133 for (int i = 0; i < numberOfFrames; i++) {
7134 objects[i].value = sliceMM[i];
7135 objects[i].index = i;
7136 }
7137 qsort(objects, numberOfFrames, sizeof(struct fidx), fcmp);
7138 numDimensionIndexValues = numberOfFrames;
7139 for (int i = 0; i < numberOfFrames; i++) {
7140 // printf("%d > %g\n", objects[i].index, objects[i].value);
7141 dcmDim[objects[i].index].dimIdx[0] = i;
7142 }
7143 for (int i = 0; i < 4; i++) {
7144 d.patientPosition[i] = minPatientPosition[i];
7145 d.patientPositionLast[i] = maxPatientPosition[i];
7146 }
7147 //printf("%g -> %g\n", objects[0].value, objects[numberOfFrames-1].value);
7148 //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]);
7149 free(objects);
7150 } //issue 372
7151 if ((d.echoTrainLength == 0) && (echoTrainLengthPhil))
7152 d.echoTrainLength = echoTrainLengthPhil; //+1 ?? to convert "EPI factor" to echo train length, see issue 377
7153 if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (minDynamicScanBeginTime < maxDynamicScanBeginTime)) { //issue369
7154 float TR = 1000.0 * ((maxDynamicScanBeginTime - minDynamicScanBeginTime) / (d.xyzDim[4] - 1)); //-1 : fence post problem
7155 if (fabs(TR - d.TR) > 0.001) {
7156 printWarning("Assuming TR = %gms, not 0018,0080 = %gms (see issue 369)\n", TR, d.TR);
7157 d.TR = TR;
7158 }
7159 }
7160 // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]);
7161 //printf("%g %g\n", minDynamicScanBeginTime, maxDynamicScanBeginTime);
7162 //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (frameAcquisitionDuration > 0.0)) //issue369
7163 // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]);
7164 if (numDimensionIndexValues > 1)
7165 strcpy(d.imageType, imageType1st); //for multi-frame datasets, return name of book, not name of last chapter
7166 if ((numDimensionIndexValues > 1) && (numDimensionIndexValues == numberOfFrames)) {
7167 //Philips enhanced datasets can have custom slice orders and pack images with different TE, Phase/Magnitude/Etc.
7168 int maxVariableItem = 0;
7169 int nVariableItems = 0;
7170 if (true) { //
7171 int mn[MAX_NUMBER_OF_DIMENSIONS];
7172 int mx[MAX_NUMBER_OF_DIMENSIONS];
7173 for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) {
7174 mx[j] = dcmDim[0].dimIdx[j];
7175 mn[j] = mx[j];
7176 for (int i = 0; i < numDimensionIndexValues; i++) {
7177 if (mx[j] < dcmDim[i].dimIdx[j])
7178 mx[j] = dcmDim[i].dimIdx[j];
7179 if (mn[j] > dcmDim[i].dimIdx[j])
7180 mn[j] = dcmDim[i].dimIdx[j];
7181 }
7182 if (mx[j] != mn[j]) {
7183 maxVariableItem = j;
7184 nVariableItems++;
7185 }
7186 }
7187 if (isVerbose > 1) {
7188 printMessage(" DimensionIndexValues (0020,9157), dimensions with variability:\n");
7189 for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++)
7190 if (mn[i] != mx[i])
7191 printMessage(" Dimension %d Range: %d..%d\n", i, mn[i], mx[i]);
7192 }
7193 } //verbose > 1
7194 //see http://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.8.24.3.3.html
7195 //Philips puts spatial position as lower item than temporal position, the reverse is true for Bruker and Canon
7196 int stackPositionItem = 0;
7197 if (dimensionIndexPointerCounter > 0)
7198 for (size_t i = 0; i < dimensionIndexPointerCounter; i++)
7199 if (dimensionIndexPointer[i] == kInStackPositionNumber)
7200 stackPositionItem = i;
7201 if ((d.manufacturer == kMANUFACTURER_CANON) && (nVariableItems == 1) && (d.xyzDim[4] > 1)) {
7202 //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!
7203 printf("Invalid enhanced DICOM created by Canon: Only single dimension in DimensionIndexValues (0020,9157) varies, for 4D file (e.g. BOTH space and time should vary)\n");
7204 printf("%d %d\n", stackPositionItem, maxVariableItem);
7205 int stackTimeItem = 0;
7206 if (stackPositionItem == 0) {
7207 maxVariableItem++;
7208 stackTimeItem++; //e.g. slot 0 = space, slot 1 = time
7209 }
7210 int vol = 0;
7211 for (int i = 0; i < numDimensionIndexValues; i++) {
7212 if (1 == dcmDim[i].dimIdx[stackPositionItem])
7213 vol++;
7214 dcmDim[i].dimIdx[stackTimeItem] = vol;
7215 //printf("vol %d slice %d\n", dcmDim[i].dimIdx[stackTimeItem], dcmDim[i].dimIdx[stackPositionItem]);
7216 }
7217 } //Kuldge for corrupted CANON 0020,9157
7218 /* //issue533: this code fragment will replicate dicm2nii (reverse order of volume hierarchy) https://github.com/xiangruili/dicm2nii/blob/f918366731162e6895be6e5ee431444642e0e7f9/dicm2nii.m#L2205
7219 if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (stackPositionItem == 1) && ( maxVariableItem > 2)) {
7220 //replicate dicm2nii: s2.DimensionIndexValues([3:end 1])
7221 uint32_t tmp[MAX_NUMBER_OF_DIMENSIONS];
7222 for (int i = 0; i < numDimensionIndexValues; i++) {
7223 for (int j = 2; j <= maxVariableItem; j++)
7224 tmp[j] = dcmDim[i].dimIdx[j];
7225 for (int j = 2; j <= maxVariableItem; j++)
7226 dcmDim[i].dimIdx[j] = tmp[maxVariableItem - j + 2];
7227 }
7228 }*/
7229 if ((isKludgeIssue533) && (numDimensionIndexValues > 1))
7230 printWarning("Guessing temporal order for Philips enhanced DICOM ASL (issue 532).\n");
7231 //sort dimensions
7232 #ifdef USING_R
7233 if (stackPositionItem < maxVariableItem)
7234 std::sort(dcmDim.begin(), dcmDim.begin() + numberOfFrames, compareTDCMdim);
7235 else
7236 std::sort(dcmDim.begin(), dcmDim.begin() + numberOfFrames, compareTDCMdimRev);
7237 #else
7238 if (stackPositionItem < maxVariableItem)
7239 qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdim);
7240 else
7241 qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdimRev);
7242 #endif
7243 //for (int i = 0; i < numberOfFrames; i++)
7244 // printf("i %d diskPos= %d dimIdx= %d %d %d %d TE= %g\n", i, dcmDim[i].diskPos, dcmDim[i].dimIdx[0], dcmDim[i].dimIdx[1], dcmDim[i].dimIdx[2], dcmDim[i].dimIdx[3], dti4D->TE[i]);
7245 for (int i = 0; i < numberOfFrames; i++) {
7246 dti4D->sliceOrder[i] = dcmDim[i].diskPos;
7247 dti4D->intenScale[i] = dcmDim[i].intenScale;
7248 dti4D->intenIntercept[i] = dcmDim[i].intenIntercept;
7249 dti4D->intenScalePhilips[i] = dcmDim[i].intenScalePhilips;
7250 dti4D->RWVIntercept[i] = dcmDim[i].RWVIntercept;
7251 dti4D->RWVScale[i] = dcmDim[i].RWVScale;
7252 if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0])
7253 d.isScaleVariesEnh = true;
7254 if (dti4D->intenScale[i] != dti4D->intenScale[0])
7255 d.isScaleVariesEnh = true;
7256 if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0])
7257 d.isScaleVariesEnh = true;
7258 }
7259 if (!(d.manufacturer == kMANUFACTURER_BRUKER && d.isDiffusion) && (d.xyzDim[4] > 1) && (d.xyzDim[4] < kMaxDTI4D)) { //record variations in TE
7260 d.isScaleOrTEVaries = false;
7261 bool isTEvaries = false;
7262 bool isScaleVaries = false;
7263 //setting j = 1 in next few lines is a hack, just in case TE/scale/intercept listed AFTER dimensionIndexValues
7264 int j = 0;
7265 for (int i = 0; i < d.xyzDim[4]; i++) {
7266 int slice = j + (i * d.xyzDim[3]);
7267 //dti4D->gradDynVol[i] = 0; //only PAR/REC
7268 dti4D->TE[i] = dcmDim[slice].TE;
7269 dti4D->isPhase[i] = dcmDim[slice].isPhase;
7270 dti4D->isReal[i] = dcmDim[slice].isReal;
7271 dti4D->isImaginary[i] = dcmDim[slice].isImaginary;
7272 dti4D->triggerDelayTime[i] = dcmDim[slice].triggerDelayTime;
7273 dti4D->S[i].V[0] = dcmDim[slice].V[0];
7274 dti4D->S[i].V[1] = dcmDim[slice].V[1];
7275 dti4D->S[i].V[2] = dcmDim[slice].V[2];
7276 dti4D->S[i].V[3] = dcmDim[slice].V[3];
7277 //printf("te=\t%g\tscl=\t%g\tintercept=\t%g\n",dti4D->TE[i], dti4D->intenScale[i],dti4D->intenIntercept[i]);
7278 if ((!isSameFloatGE(dti4D->TE[i], 0.0)) && (dti4D->TE[i] != d.TE))
7279 isTEvaries = true;
7280 if (dti4D->isPhase[i] != isPhase)
7281 d.isScaleOrTEVaries = true;
7282 if (dti4D->triggerDelayTime[i] != d.triggerDelayTime)
7283 d.isScaleOrTEVaries = true;
7284 if (dti4D->isReal[i] != isReal)
7285 d.isScaleOrTEVaries = true;
7286 if (dti4D->isImaginary[i] != isImaginary)
7287 d.isScaleOrTEVaries = true;
7288 /*Philips can vary intensity scalings for separate slices within a volume!
7289 dti4D->intenScale[i] = dcmDim[slice].intenScale;
7290 dti4D->intenIntercept[i] = dcmDim[slice].intenIntercept;
7291 dti4D->intenScalePhilips[i] = dcmDim[slice].intenScalePhilips;
7292 dti4D->RWVIntercept[i] = dcmDim[slice].RWVIntercept;
7293 dti4D->RWVScale[i] = dcmDim[slice].RWVScale;
7294 if (dti4D->intenScale[i] != d.intenScale) isScaleVaries = true;
7295 if (dti4D->intenIntercept[i] != d.intenIntercept) isScaleVaries = true;*/
7296 }
7297 if ((isScaleVaries) || (isTEvaries))
7298 d.isScaleOrTEVaries = true;
7299 if (isTEvaries)
7300 d.isMultiEcho = true;
7301 //if echoVaries,count number of echoes
7302 /*int echoNum = 1;
7303 for (int i = 1; i < d.xyzDim[4]; i++) {
7304 if (dti4D->TE[i-1] != dti4D->TE[i])
7305 }*/
7306 if ((isVerbose) && (d.isScaleOrTEVaries)) {
7307 printMessage("Parameters vary across 3D volumes packed in single DICOM file:\n");
7308 for (int i = 0; i < d.xyzDim[4]; i++) {
7309 int slice = (i * d.xyzDim[3]);
7310 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]);
7311 }
7312 }
7313 }
7314 if ((d.xyzDim[3] == maxInStackPositionNumber) && (maxInStackPositionNumber > 1) && (d.zSpacing <= 0.0)) {
7315 float dx = sqrt(pow(d.patientPosition[1] - d.patientPositionLast[1], 2) +
7316 pow(d.patientPosition[2] - d.patientPositionLast[2], 2) +
7317 pow(d.patientPosition[3] - d.patientPositionLast[3], 2));
7318 dx = dx / (maxInStackPositionNumber - 1);
7319 if ((dx > 0.0) && (!isSameFloatGE(dx, d.xyzMM[3]))) //patientPosition has some rounding error
7320 d.xyzMM[3] = dx;
7321 } //d.zSpacing <= 0.0: Bruker does not populate 0018,0088 https://github.com/rordenlab/dcm2niix/issues/241
7322 } //if numDimensionIndexValues > 1 : enhanced DICOM
7323 if (d.CSA.numDti >= kMaxDTI4D) {
7324 printError("Unable to convert DTI [recompile with increased kMaxDTI4D] detected=%d, max = %d\n", d.CSA.numDti, kMaxDTI4D);
7325 d.CSA.numDti = 0;
7326 }
7327 if ((hasDwiDirectionality) && (d.CSA.numDti < 1))
7328 d.CSA.numDti = 1;
7329 if ((d.isValid) && (d.imageNum == 0)) { //Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers
7330 //TYPE 1 for MR image http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,0013)
7331 // Only type 2 for some other DICOMs! Therefore, generate warning not error
7332 printWarning("Instance number (0020,0013) not found: %s\n", fname);
7333 d.imageNum = abs((int)d.instanceUidCrc) % 2147483647; //INT_MAX;
7334 if (d.imageNum == 0)
7335 d.imageNum = 1; //https://github.com/rordenlab/dcm2niix/issues/341
7336 //d.imageNum = 1; //not set
7337 }
7338 if ((numDimensionIndexValues < 1) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.seriesNum > 99999) && (philMRImageDiffBValueNumber > 0)) {
7339 //Ugly kludge to distinguish Philips classic DICOM dti
7340 // images from a single sequence can have identical series number, instance number, gradient number
7341 // the ONLY way to distinguish slices is using the private tag MRImageDiffBValueNumber
7342 // confusingly, the same sequence can also generate MULTIPLE series numbers!
7343 // for examples see https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging
7344 d.seriesNum += (philMRImageDiffBValueNumber * 1000);
7345 }
7346 //if (contentTime != 0.0) && (numDimensionIndexValues < (MAX_NUMBER_OF_DIMENSIONS - 1)){
7347 // uint_32t timeCRC = mz_crc32X((unsigned char*) &contentTime, sizeof(double));
7348 //}
7349 //if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strcmp(d.sequenceName, "fldyn3d1")== 0)) {
7350 if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fldyn3d1") != NULL)) {
7351 //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252
7352 d.isStackableSeries = true;
7353 d.imageNum += (d.seriesNum * 1000);
7354 strcpy(d.seriesInstanceUID, d.studyInstanceUID);
7355 d.seriesUidCrc = mz_crc32X((unsigned char *)&d.protocolName, strlen(d.protocolName));
7356 }
7357 //TODO533: alias Philips ASL PLD as frameDuration? isKludgeIssue533
7358 //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408
7359 // d.triggerDelayTime = 0.0; //Philips ASL use "(0018,9037) CS [NONE]" but "(2001,1010) CS [TRIGGERED]", a situation not described in issue408
7360 if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime)) //issue395
7361 d.triggerDelayTime = 0.0;
7362 if (d.phaseNumber > 0) //Philips TurboQUASAR set this uniquely for each slice
7363 d.triggerDelayTime = 0.0;
7364 //printf("%d\t%g\t%g\t%g\n", d.imageNum, d.acquisitionTime, d.triggerDelayTime, MRImageDynamicScanBeginTime);
7365 if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strlen(seriesTimeTxt) > 1) && (d.isXA10A) && (d.xyzDim[3] == 1) && (d.xyzDim[4] < 2)) {
7366 //printWarning(">>Ignoring series number of XA data saved as classic DICOM (issue 394)\n");
7367 d.isStackableSeries = true;
7368 d.imageNum += (d.seriesNum * 1000);
7369 strcpy(d.seriesInstanceUID, seriesTimeTxt); // dest <- src
7370 d.seriesUidCrc = mz_crc32X((unsigned char *)&seriesTimeTxt, strlen(seriesTimeTxt));
7371 }
7372 if (((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON)) && (B0Philips > 0.0)) { //issue 388
7373 char txt[1024] = {""};
7374 sprintf(txt, "b=%d(", (int)round(B0Philips));
7375 if (strstr(d.imageComments, txt) != NULL) {
7376 //printf("%s>>>%s %g\n", txt, d.imageComments, B0Philips);
7377 int len = strlen(txt);
7378 strcpy(txt, (char *)&d.imageComments[len]);
7379 len = strlen(txt);
7380 for (int i = 0; i <= len; i++) {
7381 if ((txt[i] >= '0') && (txt[i] <= '9'))
7382 continue;
7383 if ((txt[i] == '.') || (txt[i] == '-'))
7384 continue;
7385 txt[i] = ' ';
7386 }
7387 float v[4];
7388 dcmMultiFloat(len, (char *)&txt[0], 3, &v[0]);
7389 d.CSA.dtiV[0] = B0Philips;
7390 #ifdef swizzleCanon //see issue422 and dcm_qa_canon
7391 d.CSA.dtiV[1] = v[2];
7392 d.CSA.dtiV[2] = v[1];
7393 d.CSA.dtiV[3] = -v[3];
7394 #else
7395 d.CSA.dtiV[1] = v[2];
7396 d.CSA.dtiV[2] = v[1];
7397 d.CSA.dtiV[3] = v[3];
7398 d.manufacturer = kMANUFACTURER_CANON;
7399 #endif
7400 //d.CSA.dtiV[1] = v[1];
7401 //d.CSA.dtiV[2] = v[2];
7402 //d.CSA.dtiV[3] = v[3];
7403 d.CSA.numDti = 1;
7404 }
7405 }
7406 if ((isDICOMANON) && (isMATLAB)) {
7407 //issue 383
7408 strcpy(d.seriesInstanceUID, d.studyDate);
7409 // This check is unlikely to be important in practice, but it silences a warning from GCC with -Wrestrict
7410 if (strlen(d.studyDate) < kDICOMStr) {
7411 strncat(d.seriesInstanceUID, d.studyTime, kDICOMStr - strlen(d.studyDate));
7412 }
7413 d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID));
7414 }
7415 if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fl2d1") != NULL)) {
7416 d.isLocalizer = true;
7417 }
7418 //detect pepolar https://github.com/nipy/heudiconv/issues/479
7419 if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 1))
7420 d.epiVersionGE = kGE_EPI_PEPOLAR_REV;
7421 if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 2))
7422 d.epiVersionGE = kGE_EPI_PEPOLAR_REV_FWD;
7423 if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 3))
7424 d.epiVersionGE = kGE_EPI_PEPOLAR_FWD_REV;
7425 if ((d.epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD) && (volumeNumber > 0) && ((volumeNumber % 2) == 1))
7426 d.epiVersionGE = kGE_EPI_PEPOLAR_REV_FWD_FLIP;
7427 if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV) && (volumeNumber > 0) && ((volumeNumber % 2) == 0))
7428 d.epiVersionGE = kGE_EPI_PEPOLAR_FWD_REV_FLIP;
7429 #ifndef myDisableGEPEPolarFlip //e.g. to disable patch for issue 532 "make CFLAGS=-DmyDisableGEPEPolarFlip"
7430 if ((d.epiVersionGE == kGE_EPI_PEPOLAR_REV) || (d.epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV_FLIP) || (d.epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD_FLIP)) {
7431 if (d.epiVersionGE != kGE_EPI_PEPOLAR_REV) d.seriesNum += 1000;
7432 if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED)
7433 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED;
7434 else if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED)
7435 d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED;
7436 }
7437 #endif
7438 //UIH 3D T1 scans report echo train length, which is interpreted as 3D EPI
7439 if ((d.manufacturer == kMANUFACTURER_UIH) && (strstr(d.sequenceName, "gre_fsp") != NULL))
7440 d.echoTrainLength = 0;
7441 //printf(">>%s\n", d.sequenceName); d.isValid = false;
7442 // Andrey Fedorov has requested keeping GE bvalues, see issue 264
7443 //if ((d.CSA.numDti > 0) && (d.manufacturer == kMANUFACTURER_GE) && (d.numberOfDiffusionDirectionGE < 1))
7444 // d.CSA.numDti = 0; //https://github.com/rordenlab/dcm2niix/issues/264
7445 if ((!d.isLocalizer) && (isInterpolated) && (d.imageNum <= 1))
7446 printWarning("interpolated protocol '%s' may be unsuitable for dwidenoise/mrdegibbs. %s\n", d.protocolName, fname);
7447 if (((numDimensionIndexValues + 3) < MAX_NUMBER_OF_DIMENSIONS) && (d.rawDataRunNumber > 0))
7448 d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 4] = d.rawDataRunNumber;
7449 if ((numDimensionIndexValues + 2) < MAX_NUMBER_OF_DIMENSIONS)
7450 d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 3] = d.instanceUidCrc;
7451 if ((numDimensionIndexValues + 1) < MAX_NUMBER_OF_DIMENSIONS)
7452 d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 2] = d.echoNum;
7453 if (numDimensionIndexValues < MAX_NUMBER_OF_DIMENSIONS) //https://github.com/rordenlab/dcm2niix/issues/221
7454 d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID));
7455 if ((d.isValid) && (d.seriesUidCrc == 0)) {
7456 if (d.seriesNum < 1)
7457 d.seriesUidCrc = 1; //no series information
7458 else
7459 d.seriesUidCrc = d.seriesNum; //file does not have Series UID, use series number instead
7460 }
7461 if (d.seriesNum < 1) //https://github.com/rordenlab/dcm2niix/issues/218
7462 d.seriesNum = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID));
7463 getFileName(d.imageBaseName, fname);
7464 if (multiBandFactor > d.CSA.multiBandFactor)
7465 d.CSA.multiBandFactor = multiBandFactor; //SMS reported in 0051,1011 but not CSA header
7466 #ifndef myLoadWholeFileToReadHeader
7467 fclose(file);
7468 #endif
7469 if ((temporalResolutionMS > 0.0) && (isSameFloatGE(d.TR, temporalResolutionMS))) {
7470 //do something profound
7471 //in practice 0020,0110 not used
7472 //https://github.com/bids-standard/bep001/blob/repetitiontime/Proposal_RepetitionTime.md
7473 }
7474 //issue 542
7475 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)) ) )
7476 printWarning("GE DWI vectors may have been removed by Neologica DICOM Anonymizer Pro (Issue 542)\n");
7477 //start: issue529 TODO JJJJ
7478 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)) ) )
7479 gradientOrientationNumberPhilips = kMaxDTI4D + 1; //Philips includes derived Trace/ADC images into raw DTI, these should be removed...
7480 //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);
7481 //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);
7482 //if (sliceNumberMrPhilips == 1) printf("%d\t%d\t%g\t%d\n", philMRImageDiffBValueNumber, gradientOrientationNumberPhilips, d.CSA.dtiV[0], philMRImageDiffVolumeNumber); //issue546
7483 d.phaseNumber = (d.phaseNumber > philMRImageDiffBValueNumber) ? d.phaseNumber : philMRImageDiffBValueNumber; //we need both BValueNumber(2005,1412) and GradientOrientationNumber(2005,1413) to resolve volumes: issue546
7484 d.rawDataRunNumber = (d.rawDataRunNumber > volumeNumber) ? d.rawDataRunNumber : volumeNumber;
7485 d.rawDataRunNumber = (d.rawDataRunNumber > gradientOrientationNumberPhilips) ? d.rawDataRunNumber : gradientOrientationNumberPhilips;
7486 if ((d.rawDataRunNumber < 0) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (nDimIndxVal > 1) && (d.dimensionIndexValues[nDimIndxVal - 1] > 0))
7487 d.rawDataRunNumber = d.dimensionIndexValues[nDimIndxVal - 1]; //Philips enhanced scans converted to classic with dcuncat
7488 if (philMRImageDiffVolumeNumber > 0) { //use 2005,1596 for Philips DWI >= R5.6
7489 d.rawDataRunNumber = philMRImageDiffVolumeNumber;
7490 d.phaseNumber = 0;
7491 }
7492 // d.rawDataRunNumber = (d.rawDataRunNumber > d.phaseNumber) ? d.rawDataRunNumber : d.phaseNumber; //will not work: conflict for MultiPhase ASL with multiple averages
7493 //end: issue529
7494 if (hasDwiDirectionality)
7495 d.isVectorFromBMatrix = false; //issue 265: Philips/Siemens have both directionality and bmatrix, Bruker only has bmatrix
7496 //printf("%s\t%s\t%s\t%s\t%s_%s\n",d.patientBirthDate, d.procedureStepDescription,d.patientName, fname, d.studyDate, d.studyTime);
7497 //d.isValid = false;
7498 //printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]);
7499 //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]);
7500 //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);
7501 //printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz);
7502 return d;
7503 } // readDICOM()
7504
7505 void setDefaultPrefs(struct TDCMprefs *prefs) {
7506 prefs->isVerbose = false;
7507 prefs->compressFlag = kCompressSupport;
7508 prefs->isIgnoreTriggerTimes = false;
7509 }
7510
7511 struct TDICOMdata readDICOMv(char *fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) {
7512 struct TDCMprefs prefs;
7513 setDefaultPrefs(&prefs);
7514 prefs.isVerbose = isVerbose;
7515 prefs.compressFlag = compressFlag;
7516 TDICOMdata ret = readDICOMx(fname, &prefs, dti4D);
7517 return ret;
7518 }
7519
7520 struct TDICOMdata readDICOM(char *fname) {
7521 struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); //unused
7522 TDICOMdata ret = readDICOMv(fname, false, kCompressSupport, dti4D);
7523 free(dti4D);
7524 return ret;
7525 } // readDICOM()
7526