7
{$Include ..\common\isgui.inc}
10
var kUseDateTimeForID: boolean = false;
15
kMaxDTIDir = 1024;//Maximum DTI directions
19
v1,v2,v3: double; //4=volume, eg time: some EC*T7 images
22
TDTIRA = array [1..kMaxDTIDir] of TDTI;//TDICOM;//unsigned 8-bit int
23
TOrder= array [1..kMaxOrderVal] of byte;
26
kDICOMStr = String[32];
28
XYZdim: array [1..4] of integer;
29
XYZori: array [1..3] of integer;
30
XYZmm: array [1..3] of double;
31
Orient: array [1..6] of double;
32
SignedData,SiemensDICOMDTICSA,SiemensDICOMDTI,FloatData,file4D,JPEGLossyCpt,JPEGLosslessCpt: boolean;
33
SecSinceMidnight,PatientPosX,PatientPosY,PatientPosZ,AngulationAP,AngulationFH,AngulationRL: double;
34
kV,TE, TR,IntenScale,IntenIntercept,location{,DTIv1,DTIv2,DTIv3}: single;
35
{Bval,}SlicesPer3DVol,SiemensInterleaved {0=no,1=yes,2=not defined},SiemensSlices,SiemensMosaicX,SiemensMosaicY,
36
nOrder,nDTIdir,AcquNum,ImageNum,SeriesNum,ImageStart,little_endian,Allocbits_per_pixel,SamplesPerPixel,
37
CSAImageHeaderInfoPos,CSAImageHeaderInfoSz,ManufacturerID,PlanarConfig, //ImplementationVersion,
39
CompressOffset,CompresssZ: integer;
41
PatientGender,PatientDoB,PatientPos,PatientName,ProtocolName,StudyDate,StudyTime,PhilipsSliceOrient,PhaseEncoding: kDICOMStr;
42
Filename: string[255];
44
Order: TOrder; //4D datasets
45
//OrderSlope,OrderIntercept: TOrderScaling; //4D datasets
46
end;//DICOMdata record
48
TDICOMRA = array [1..1] of DicomData;//TDICOM;//unsigned 8-bit int
49
TDICOMRAp = ^TDICOMRA;
50
TNIFTIhdr = packed record //Next: analyze Format Header structure
51
HdrSz : longint; //MUST BE 348
52
Data_Type: array [1..10] of char; //unused
53
db_name: array [1..18] of char; //unused
54
extents: longint; //unused
55
session_error: smallint; //unused
56
regular: char; ////unused: in Analyze 7.5 this must be 114
57
dim_info: byte; //MRI slice order
58
dim: array[0..7] of smallint; //Data array dimensions
59
intent_p1, intent_p2, intent_p3: single;
60
intent_code: smallint;
63
slice_start: smallint;
64
pixdim: array[0..7]of single;
66
scl_slope: single;//scaling slope
67
scl_inter: single;//scaling intercept
69
slice_code: byte; //e.g. ascending
70
xyzt_units: byte; //e.g. mm and sec
71
cal_max,cal_min: single; //unused
72
slice_duration: single; //time for one slice
73
toffset: single; //time axis to shift
74
glmax, glmin: longint; //UNUSED
75
descrip: array[1..80] of char;
76
aux_file: array[1..24] of char;
77
qform_code, sform_code: smallint;
78
quatern_b,quatern_c,quatern_d,
79
qoffset_x,qoffset_y,qoffset_z: single;
80
srow_x: array[0..3]of single;
81
srow_y: array[0..3]of single;
82
srow_z: array[0..3]of single;
83
intent_name: array[1..16] of char;
85
end; //TNIFTIhdr Header Structure
89
kDT_BINARY =1; // binary (1 bit/voxel)
90
kDT_UNSIGNED_CHAR =2; // unsigned char (8 bits/voxel)
91
kDT_SIGNED_SHORT =4; // signed short (16 bits/voxel)
92
kDT_SIGNED_INT =8; // signed int (32 bits/voxel)
93
kDT_FLOAT =16; // float (32 bits/voxel)
94
kDT_COMPLEX =32; // complex (64 bits/voxel)
95
kDT_DOUBLE =64; // double (64 bits/voxel)
96
kDT_RGB =128; // RGB triple (24 bits/voxel)
97
kDT_INT8 =256; // signed char (8 bits)
98
kDT_UINT16 =512; // unsigned short (16 bits)
99
kDT_UINT32 =768; // unsigned int (32 bits)
100
kDT_INT64 =1024; // long long (64 bits)
101
kDT_UINT64 =1280; // unsigned long long (64 bits)
102
kDT_FLOAT128 =1536; // long double (128 bits)
103
kDT_COMPLEX128 =1792; // double pair (128 bits)
104
kDT_COMPLEX256 =2048; // long double pair (256 bits)
106
kNIFTI_SLICE_SEQ_UNKNOWN = 0;
107
kNIFTI_SLICE_SEQ_INC = 1;
108
kNIFTI_SLICE_SEQ_DEC = 2;
109
kNIFTI_SLICE_ALT_INC = 3;
110
kNIFTI_SLICE_ALT_DEC = 4;
111
//xyzt_units values: note 3bit space and 3bit time packed into single byte
112
kNIFTI_UNITS_UNKNOWN = 0;
113
kNIFTI_UNITS_METER = 1;
115
kNIFTI_UNITS_MICRON = 3;
116
kNIFTI_UNITS_SEC = 8;
117
kNIFTI_UNITS_MSEC = 16;
118
kNIFTI_UNITS_USEC = 24;
119
kNIFTI_UNITS_HZ = 32;
120
kNIFTI_UNITS_PPM = 40;
121
//qform_code, sform_code values
122
kNIFTI_XFORM_UNKNOWN = 0;
123
kNIFTI_XFORM_SCANNER_ANAT = 1;//Scanner-based anatomical coordinates
124
kNIFTI_XFORM_ALIGNED_ANAT = 2; //Coordinates aligned to another file e.g. EPI coregistered to T1
125
kNIFTI_XFORM_TALAIRACH = 3; //Talairach-Tournoux Atlas; (0,0,0)=AC, etc.
126
kNIFTI_XFORM_MNI_152 = 4; //MNI 152 normalized coordinates
128
kNIFTI_MAGIC_SEPARATE_HDR = $0031696E;//$6E693100;
129
kNIFTI_MAGIC_EMBEDDED_HDR = $00312B6E;//$6E2B3100;
130
//byte-swapped magic values
131
kswapNIFTI_MAGIC_SEPARATE_HDR = $6E693100;
132
kswapNIFTI_MAGIC_EMBEDDED_HDR = $6E2B3100;
133
//Statistics Intention
134
kNIFTI_INTENT_NONE =0;
135
kNIFTI_INTENT_CORREL =2;
136
kNIFTI_INTENT_TTEST =3;
137
kNIFTI_INTENT_FTEST =4;
138
kNIFTI_INTENT_ZSCORE =5;
139
kNIFTI_INTENT_CHISQ =6;
140
kNIFTI_INTENT_BETA =7;
141
kNIFTI_INTENT_BINOM =8;
142
kNIFTI_INTENT_GAMMA =9;
143
kNIFTI_INTENT_POISSON =10;
144
kNIFTI_INTENT_NORMAL =11;
145
kNIFTI_INTENT_FTEST_NONC =12;
146
kNIFTI_INTENT_CHISQ_NONC =13;
147
kNIFTI_INTENT_LOGISTIC =14;
148
kNIFTI_INTENT_LAPLACE =15;
149
kNIFTI_INTENT_UNIFORM =16;
150
kNIFTI_INTENT_TTEST_NONC =17;
151
kNIFTI_INTENT_WEIBULL =18;
152
kNIFTI_INTENT_CHI =19;
153
kNIFTI_INTENT_INVGAUSS =20;
154
kNIFTI_INTENT_EXTVAL =21;
155
kNIFTI_INTENT_PVAL =22;
156
NIFTI_INTENT_LOGPVAL =23;
157
NIFTI_INTENT_LOG10PVAL =24;
158
kNIFTI_LAST_STATCODE = 24;//kNIFTI_INTENT_PVAL;
159
kNIFTI_INTENT_ESTIMATE =1001;
160
kNIFTI_FIRST_NONSTATCODE = kNIFTI_INTENT_ESTIMATE;
161
kNIFTI_INTENT_LABEL =1002;
162
kNIFTI_INTENT_NEURONAME =1003;
163
kNIFTI_INTENT_GENMATRIX =1004;
164
kNIFTI_INTENT_SYMMATRIX =1005;
165
kNIFTI_INTENT_DISPVECT =1006;
166
kNIFTI_INTENT_VECTOR =1007;
167
kNIFTI_INTENT_POINTSET =1008;
168
kNIFTI_INTENT_TRIANGLE =1009;
169
kNIFTI_INTENT_QUATERNION =1010;
172
kCR = chr (13);//PC EOLN
192
procedure PhilipsPrecise (lRS, lRI,lSS: single; var lSlope,lIntercept: single; Precise: boolean);
193
procedure clear_dicom_data (var lDicomdata:Dicomdata);
194
function DICOMinterslicedistance(var lDicomdata1,lDicomdata2:Dicomdata): single;//1392
195
function StudyDateTime (lInStudyDate, lInStudyTime: kDICOMStr): TDateTime;
196
function StudyDateTime2Str (lDateTime: TDateTime):string;
197
//function GetCSAImageHeaderInfoDTI (lFilename: string; lStart,lLength: integer; var lBval: integer; var ldti1,ldti2,ldti3: double): boolean;
198
//function GetCSAImageHeaderInfo (lFilename: string; lStart,lLength: integer; var lMosaicSlices,lMosaicX,lMosaicY: integer; var lv1,lv2,lv3: double): boolean;
199
procedure AplhaNumericStrDICOM (var lStr: kDICOMStr);
200
procedure PartialAcquisitionError;
201
function DICOMstr (i: integer; var lDICOMra: TDICOMrap;lOutname:string): string; overload;
202
function DICOMstr (i: integer; var lDICOMra: TDICOMrap): string; overload;
206
uses dicom,sysutils,define_types,dialogsx;
208
function YearsOld (lDICOM: DICOMdata): single;
214
if length (lDICOM.PatientDoB) < 8 then
218
dob := StudyDateTime (lDICOM.PatientDoB, lnoon);
219
result := (lDICOM.DateTime-dob)/365.2425;
224
function DICOMstr (i: integer; var lDICOMra: TDICOMrap;lOutname: string): string; overload;
228
if lOutname <> '' then
229
lS := kTab+'SuggestedOutput:'+lOutname
233
result := lDICOMra^[i].Filename
234
+kTab+'SeriesNum:'+kTab+inttostr(lDICOMra^[i].SeriesNum)
235
+kTab+'AcquNum:'+inttostr(lDICOMra^[i].AcquNum)
236
+kTab+'ImageNum:'+inttostr(lDICOMra^[i].ImageNum)
237
+kTab+'Name:'+lDICOMra^[i].PatientName
238
+kTab+'DoB:'+lDICOMra^[i].PatientDoB
239
+kTab+'Gender:'+lDICOMra^[i].PatientGender
240
+kTab+'DateTime:'+DateTimeToStr(lDICOMra^[i].DateTime)
241
+kTab+'Age(Years):'+floattostr(YearsOld(lDICOMra^[i]))
246
function DICOMstr (i: integer; var lDICOMra: TDICOMrap): string; overload;
248
result := DICOMstr (i, lDICOMra,'')
251
procedure PartialAcquisitionError;
253
Msg('* Potential partial acquisition or improper segmentation of files');
255
Msg('* Possible solution: check ''Collapse folders'' in Help/Preferences and select directory that contains all images in subfolders');
257
Msg('* Possible solution: use -c Y and use folder containing subdirectories as input');
258
Msg('* or change .ini file to read: CollapseFolders=1');
262
function PhilipsPreciseVal (lPV, lRS, lRI,lSS: single): single;
264
if (lRS*lSS) = 0 then //avoid divide by zero
267
result := (lPV * lRS + lRI) / (lRS * lSS);
270
procedure PhilipsPrecise (lRS, lRI,lSS: single; var lSlope,lIntercept: single; Precise: boolean);
274
//# === PIXEL VALUES =============================================================
275
//# PV = pixel value in REC file, FP = floating point value, DV = displayed value on console
276
//# RS = rescale slope, RI = rescale intercept, SS = scale slope
277
//# DV = PV * RS + RI FP = DV / (RS * SS)
278
if not Precise then begin //return DV not FP
285
l0 := PhilipsPreciseVal (0, lRS, lRI,lSS);
286
l1 := PhilipsPreciseVal (1, lRS, lRI,lSS);
287
if l0 = l1 then begin
297
function SecSinceMidnight(H,Min,S: integer): integer;
301
result := 3600*(H) + 60* Min + S;//H not H-1 as our clock runs from 0..23 not 1..24
304
function BogusDateTime: TDateTime;
306
result := EncodeDate(1989,3,23) + (SecSinceMidnight(12,0,0) / 86400);
309
function EncodeDateTime (Y,M,D,H,Min,S: integer): TDateTime;
313
result := EncodeDate(Y,M,D) + (SecSinceMidnight(H,Min,S) / 86400);
314
except //impossible date - set to cold fusion date
315
result := BogusDateTime;
319
procedure DecodeDateTime (lDateTime: TDateTime; var Y,M,D,H,Min,S: word);
324
DecodeDate(lDateTime, Y, M, D);
325
except //unable to decode date - use cold fusion values
330
Secs := round(Frac(lDateTime)*86400);
332
Min := (secs div 60) mod 60;
333
H := (secs div 3600);
336
function StudyDateTime2Str (lDateTime: TDateTime):string;
340
DecodeDateTime (lDateTime,Y,M,D,H,Min,S);
341
result := PadStr (Y, 4)+ PadStr (M, 2)+PadStr (D, 2)+'_'+PadStr (H, 2)+ PadStr (Min, 2)+PadStr (S, 2);
344
function StudyDateTime (lInStudyDate, lInStudyTime: kDICOMStr): TDateTime;
345
var lStr,lStudyDate, lStudyTime: string;
346
Y,M,D,H,Min,S: integer;
349
if (length(lInStudyDate) < 8){YYYYMMDD} or (length(lInStudyTime) < 6) {hhmmss} then
351
//next compress string, e.g. Elscint saves time as 16:54:21
353
for S := 1 to length (lInStudyDate) do
354
if lInStudyDate[S] in ['0'..'9'] then
355
lStudyDate := lStudyDate + lInStudyDate[S];
357
for S := 1 to length (lInStudyTime) do
358
if lInStudyTime[S] in ['0'..'9'] then
359
lStudyTime := lStudyTime + lInStudyTime[S];
361
if (length(lStudyDate) < 8){YYYYMMDD} or (length(lStudyTime) < 6) {hhmmss} then
363
lStr := lStudyDate[1]+lStudyDate[2]+lStudyDate[3]+lStudyDate[4];
365
lStr := lStudyDate[5]+lStudyDate[6];
367
lStr := lStudyDate[7]+lStudyDate[8];
369
lStr := lStudyTime[1]+lStudyTime[2];
371
lStr := lStudyTime[3]+lStudyTime[4];
372
Min := strtoint(lStr);
373
lStr := lStudyTime[5]+lStudyTime[6];
375
result := EncodeDateTime (Y,M,D,H,Min,S);
378
procedure AplhaNumericStrDICOM (var lStr: kDICOMStr);
383
if length(lStr) < 1 then exit;
386
for S := 1 to length (lStr) do
387
if lStr[S] in ['0'..'9','A'..'Z','a'..'z'] then
388
lOutStr := lOutStr+ lStr[S];
392
function GetCSAImageHeaderInfoRaw (lIsDTI: boolean; lFilename: string; lStart,lLength: integer; var li1,li2,li3: integer; var lf1,lf2,lf3: double): boolean;
393
//returns true if mosaic
394
//will return false for non-mosaics - even if the have DTI information!
395
//valid DTI signified by bval >= 0
403
lFloatRA: array [1..kMaxFloats] of double;
405
function Str2FloatLastNum ( lStr: string): boolean;
412
if (length(lStr) < 1) then
416
while (lP > 0) and ((lFStr = '') or (lStr[lP] in ['+','-','0'..'9','.','e','E'])) do begin
417
if lStr[lP] in ['+','-','0'..'9','.','e','E'] then
418
lFStr := lStr[lP]+lFStr;
424
lFloatRA[1] := strtofloat(lFStr);
425
except on EConvertError do
429
end; //function Str2Float
431
function NumarisPos (lStr: string; lStart: integer): integer; //read 16 bit short integer
433
lP,lLen,lMax,lMatch: integer;
436
lLen := length(lStr);
437
lMax := lLength-lLen;
438
if (lStart < 1) or (lMax < 1) or (lLen < 1) then
440
for lP := lStart to lMax do begin
442
while (lMatch < lLen) and (lStr[lMatch+1] = char( lByteRA[lP+lMatch]) ) do
444
if lMatch = lLen then begin
445
if (lP < lMax) and (char( lByteRA[lP+lMatch]) = '"') then begin
446
lMatch := 0;//We want DiffusionGradientDirection, but not "DiffusionGradientDirection"
453
end; //function NumarisPos
455
function Str2FloatNum ( lStr: string; lnFloats: integer): boolean;
461
if (length(lStr) < 1) or (lnFloats < 1) or (lnFloats > kMaxFloats) then
463
for lnF := 1 to lnFloats do
465
lStr := lStr + ' '; //terminator
469
while lP <= length(lStr) do begin
470
if lStr[lP] in ['+','-','0'..'9','.','e','E'] then
471
lFStr := lFStr + lStr[lP]
472
else if (lFStr <> '') then begin
475
lFloatRA[lnF] := strtofloat(lFStr);
476
except on EConvertError do
477
Msg('Unable to interpret '+lNumarisTag+ ' in '+extractfilename(lFilename));
479
if lnF = lnFloats then begin
487
end; //function Str2Float
489
function NumarisStr (lStr,lIDStr: string): string;
495
lP := NumarisPos(lStr,1);
497
if length(lIDstr) > 0 then begin
498
lP := NumarisPos(lIDstr,lP);
502
lI := lP + length(lStr);
504
While (lI < (lLength)) and (lByteRA^[lI] <> $CD) do begin
505
if char(lByteRA[lI]) in ['-','0'..'9','.','p','*'] then begin
506
result := result + char(lByteRA[lI]);
509
if lPrevNum then result := result + ' ';
516
function NumarisInt1 (lStr,lIDStr: string; var lI1: integer): boolean;
518
result := Str2FloatLastNum (NumarisStr(lStr,lIDStr));
519
if not result then exit;
520
lI1 := round(lFloatRA[1] );
523
function NumarisFloat3 (lStr,lIDStr: string; var lF1,lF2,lF3: double): boolean;
525
//showmessage(lStr+' '+NumarisStr(lStr,lIDStr));
526
result := Str2FloatNum (NumarisStr(lStr,lIDStr),3);
527
if not result then exit;
529
lF1 := (lFloatRA[1]);
530
lF2 := (lFloatRA[2]);
531
lF3 := (lFloatRA[3]);
532
end; //function NumarisFloat3
534
function NumarisInt2PStar (lStr,lIDStr: string; var lI1,lI2: integer): boolean;
536
lLen,lPos,lStarPos: integer;
537
lvStr,lpStarStr: string;
538
begin //a 96x96 mosaic is usually saved as '64*64', but in B13 you can see '96p*96' or '.95 96p*96'
540
lvStr := NumarisStr(lStr,lIDStr);
541
lLen := length(lvStr);
542
if lLen < 4 then exit;//not found
544
for lPos := 1 to (lLen-1) do
545
if (lvStr[lPos] = '*') then
547
if lStarPos = 0 then exit;
550
while (lPos >= 1) and ((lpStarStr = '') or (lvStr[lPos] in ['0'..'9'])) do begin
551
lpStarStr := lvStr[lPos] + lpStarStr;
554
lpStarStr := lpStarStr + ' ';
556
while (lPos < lLen) and ((lpStarStr = '') or (lvStr[lPos] in ['0'..'9'])) do begin
557
lpStarStr := lpStarStr+lvStr[lPos];
560
result := Str2FloatNum (lpStarStr,2);
562
if not result then exit;
563
lI1 := round(lFloatRA[1]);
564
lI2 := round(lFloatRA[2]);
565
//Msg(lvStr+' '+floattostr( lI1)+'x'+inttostr(lI2));
568
begin // GetCSAImageHeaderInfoRaw
570
if (lLength < 1) then
572
if FSize(lFilename) <= (lStart+lLength) then
574
li1 := -1; //impossible - should be >=0
577
lf1 := 0;//impossible, therefore not DTI - should be -1..1
578
lf2 := 0;//impossible, therefore not DTI
579
lf3 := 0;//impossible, therefore not DTI
580
GetMem(lByteRA,lLength);
581
AssignFile(lInFile, lFileName);
582
//Msg('fz '+lFilename);
583
FileMode := 0; //Set file access to read only
585
seek(lInFile,lStart);
586
BlockRead(lInFile, lByteRA^[1], lLength);
591
result := NumarisInt1 ('B_value','IS',li1);
592
//result := NumarisInt1 ('B_value','LO',li1);
593
if li1 > 0 then begin
594
NumarisFloat3('DiffusionGradientDirection','FD',lf1,lf2,lf3);
595
//vx(lf1,lf2,lf3,123);
597
end else begin //get mosaic info
598
//fx(lStart,lLength);
599
result := NumarisInt1 ('NumberOfImagesInMosaic','US',li1);
601
NumarisInt2pStar ('AcquisitionMatrixText','SH', li2,li3);
602
NumarisFloat3('SliceNormalVector','FD',lf1,lf2,lf3);
606
end;//GetCSAImageHeaderInfoRaw
608
function GetCSAImageHeaderInfoDTI (lFilename: string; lStart,lLength: integer; var lBval: integer; var ldti1,ldti2,ldti3: double): boolean;
610
li2,li3: integer; //not used
612
result := GetCSAImageHeaderInfoRaw (TRUE,lFilename, lStart,lLength, lBval,li2,li3, ldti1,ldti2,ldti3);
615
function GetCSAImageHeaderInfo (lFilename: string; lStart,lLength: integer; var lMosaicSlices,lMosaicX,lMosaicY: integer; var lv1,lv2,lv3: double): boolean;
617
result := GetCSAImageHeaderInfoRaw (FALSE,lFilename, lStart,lLength, lMosaicSlices,lMosaicX,lMosaicY, lv1,lv2,lv3);
621
procedure clear_dicom_data (var lDicomdata:Dicomdata);
625
with lDicomData do begin
626
lDicomData.CSAImageHeaderInfoPos := 0;
627
lDicomData.CSAImageHeaderInfoSz := 0;
630
DateTime := BogusDateTime;
633
//ImplementationVersion := 0;
640
PhilipsSliceOrient := 'NA';
641
PhaseEncoding := 'NA';
643
for lI := 1 to kMaxDTIdir do begin
648
(* DTI[lDICOMdata.nDTIdir].Bval := -1;
649
DTI[lDICOMdata.nDTIdir].v1 := 0;
650
DTI[lDICOMdata.nDTIdir].v2 := 0;
651
DTI[lDICOMdata.nDTIdir].v3 := 0; *)
653
SiemensDICOMDTI := true;
654
SiemensDICOMDTICSA := false;
656
PatientName := 'NO NAME';
657
PatientDoB := 'NO DOB';
658
PatientGender := 'NA';
659
//PatientID := 'NO ID';
662
SecSinceMidnight := 0;
670
//Rotate180deg := false;
673
//MinIntensitySet := false;
677
SiemensInterleaved := 2; //0=no,1=yes,2=undefined
687
PlanarConfig:= 0; //only used in RGB values
688
//runlengthencoding := false;
690
//CompressOffset := 0;
691
SamplesPerPixel := 1;
701
lDicomData.XYZori[1] := 0;
702
lDicomData.XYZori[2] := 0;
703
lDicomData.XYZori[3] := 0;
706
Allocbits_per_pixel := 16;//bits
707
//Storedbits_per_pixel:= Allocbits_per_pixel;
710
//Thickness:= 0;//1391
713
//ProtocolName := '';
715
PatientPosX := 0;//1392
716
PatientPosY := 0;//1392
717
PatientPosZ := 0;//1392
718
JPEGLossyCpt := false;
719
JPEGLosslessCpt := false;
726
function DICOMinterslicedistance(var lDicomdata1,lDicomdata2:Dicomdata): single;//1392
728
result := sqrt(sqr(lDICOMdata1.PatientPosX-lDICOMdata2.PatientPosX)
729
+sqr(lDICOMdata1.PatientPosY-lDICOMdata2.PatientPosY)
730
+sqr(lDICOMdata1.PatientPosZ-lDICOMdata2.PatientPosZ));