TPCCLIB
Loading...
Searching...
No Matches
dftsuv.c
Go to the documentation of this file.
1
7/******************************************************************************/
8#include "tpcclibConfig.h"
9/******************************************************************************/
10#include <stdio.h>
11#include <stdlib.h>
12#include <math.h>
13#include <string.h>
14#include <time.h>
15/******************************************************************************/
16#include "libtpcmisc.h"
17#include "libtpcmodel.h"
18#include "libtpccurveio.h"
19#include "libtpcmodext.h"
20/*****************************************************************************/
21
22/*****************************************************************************/
23static char *info[] = {
24 "Calculates standardized uptake values (SUV, DUR, DAR) or PID/L from PET",
25 "time-activity curves (TAC), injected radioligand dose and subject weight.",
26 "SUV is calculated as a mean value between specified sample times, but",
27 "optionally TACs can be saved in SUV units.",
28 "Information on SUV in:",
29 "https://www.turkupetcentre.net/petanalysis/model_suv.html",
30 " ",
31 "Usage: @P [Options] tacfile starttime endtime dose weight [resultfile]",
32 " ",
33 "Options:",
34 " -curve=<Filename for SUV curve>",
35 " Save regional SUVs from whole measurement time range.",
36 " -density=<Tissue density (g/ml)>",
37 " Calculate results per tissue mass instead of tissue volume.",
38 " -stdoptions", // List standard options like --help, -v, etc
39 " ",
40 "SUV calculation start and stop time must be entered in minutes;",
41 "set both to zero to use the whole time range from TAC data.",
42 " ",
43 "Injected dose must be given in units MBq at the time of injection.",
44 " ",
45 "Subject weight must be given in kg or liters.",
46 "Instead of SUV, the percentage of injected dose per tissue volume ",
47 "(PID/L, %i.d./L) is calculated, if the subject weight is set to 0 (or below).",
48 " ",
49 "TAC file must be correct for physical decay to the injection time.",
50 "If the units of radioactivity concentrations and sample times are not",
51 "specified inside the TAC file, the units are assumed to be in kBq/mL and min;",
52 "units can be specified by adding line(s) to the end of the TAC file,",
53 "for example:",
54 "# unit := Bq/cc",
55 "# time_unit := sec",
56 " ",
57 "Results are saved in result file format (.res) by default, but if",
58 "file name extension is set to .dft, .csv, .dat, or .html, results are saved",
59 "in those formats.",
60 " ",
61 "Example 1:",
62 "Calculate SUV40-60 and save SUV TAC with command",
63 " @P -curve=uia15suv.dat uia15.dat 40 60 330 77 uia15suv40-60.res",
64 "Example 2:",
65 "Calculate SUV from available time range with command",
66 " @P uia15.dat 0 0 330 77 uia15suv.res",
67 " ",
68 "See also: imgsuv, regfur, tactime, taccalc, tacunit, tac2suv, rescoll",
69 " ",
70 "Keywords: TAC, SUV, DUR, DAR, PID, dose, modelling",
71 0};
72/*****************************************************************************/
73
74/*****************************************************************************/
75/* Turn on the globbing of the command line, since it is disabled by default in
76 mingw-w64 (_dowildcard=0); in MinGW32 define _CRT_glob instead, if necessary;
77 In Unix&Linux wildcard command line processing is enabled by default. */
78/*
79#undef _CRT_glob
80#define _CRT_glob -1
81*/
82int _dowildcard = -1;
83/*****************************************************************************/
84
85/*****************************************************************************/
89int main(int argc, char **argv)
90{
91 int ai, help=0, version=0, verbose=1;
92 int ri, fi, ret;
93 char dfile[FILENAME_MAX], rfile[FILENAME_MAX], sfile[FILENAME_MAX];
94 char *cptr, tmp[128];
95 DFT dft, suv;
96 RES res;
97 double time1=-1.0, time2=-1.0, weight=nan(""), dose=-1.0, density=-1.0, f;
98 double t1, t2;
99
100
101 /*
102 * Get arguments
103 */
104 if(argc==1) {tpcPrintUsage(argv[0], info, stderr); return(1);}
105 dfile[0]=rfile[0]=sfile[0]=(char)0;
106 dftInit(&dft); dftInit(&suv); resInit(&res);
107 /* Get options */
108 for(ai=1; ai<argc; ai++) if(*argv[ai]=='-') {
109 cptr=argv[ai]+1; if(*cptr=='-') cptr++; if(cptr==NULL) continue;
110 if(tpcProcessStdOptions(argv[ai], &help, &version, &verbose)==0) continue;
111 if(strncasecmp(cptr, "CURVE=", 6)==0) {
112 cptr+=6; if(strlen(cptr)>0) {strlcpy(sfile, cptr, FILENAME_MAX); continue;}
113 } else if(strncasecmp(cptr, "C=", 2)==0) { // for compatibility, remove later
114 cptr+=2; if(strlen(cptr)>0) {strlcpy(sfile, cptr, FILENAME_MAX); continue;}
115 } else if(strncasecmp(cptr, "DENSITY=", 8)==0) {
116 cptr+=8; density=atof_dpi(cptr); if(density>0.0 && density<3.0) continue;
117 } else if(strncasecmp(cptr, "D=", 2)==0) { // for compatibility, remove later
118 cptr+=2; density=atof_dpi(cptr); if(density>0.0 && density<3.0) continue;
119 }
120 fprintf(stderr, "Error: invalid option '%s'.\n", argv[ai]);
121 return(1);
122 } else break;
123
124 /* Print help or version? */
125 if(help==2) {tpcHtmlUsage(argv[0], info, ""); return(0);}
126 if(help) {tpcPrintUsage(argv[0], info, stdout); return(0);}
127 if(version) {tpcPrintBuild(argv[0], stdout); return(0);}
128
129 /* Process other arguments, starting from the first non-option */
130 if(ai<argc) {strlcpy(dfile, argv[ai], FILENAME_MAX); ai++;}
131 if(ai<argc) {
132 ret=atof_with_check(argv[ai], &time1);
133 if(ret!=0 || !(time1>=0.0)) {
134 fprintf(stderr, "Error: invalid start time '%s'.\n", argv[ai]);
135 return(1);
136 }
137 ai++;
138 }
139 if(ai<argc) {
140 ret=atof_with_check(argv[ai], &time2);
141 if(ret!=0 || !(time2>=time1)) {
142 fprintf(stderr, "Error: invalid end time '%s'.\n", argv[ai]);
143 return(1);
144 }
145 ai++;
146 }
147 if(ai<argc) {
148 ret=atof_with_check(argv[ai], &dose);
149 if(ret!=0 || !(dose>0.0)) {
150 fprintf(stderr, "Error: invalid dose '%s'.\n", argv[ai]);
151 return(1);
152 }
153 ai++;
154 }
155 if(ai<argc) {
156 double v;
157 ret=atof_with_check(argv[ai], &v);
158 if(ret!=0 || !(v>=0.0)) {
159 fprintf(stderr, "Error: invalid weight '%s'.\n", argv[ai]);
160 return(1);
161 }
162 if(v>1.0E-100) weight=v;
163 ai++;
164 }
165 if(ai<argc) {strlcpy(rfile, argv[ai], FILENAME_MAX); ai++;}
166 if(ai<argc) {
167 fprintf(stderr, "Error: invalid argument '%s'.\n", argv[ai]);
168 return(1);
169 }
170
171#if(0)
172 /* Process other arguments, starting from the first non-option */
173 for(; ai<argc; ai++) {
174 if(!dfile[0]) {
175 strlcpy(dfile, argv[ai], FILENAME_MAX); continue;
176 } else if(time1<0) {
177 ret=atof_with_check(argv[ai], &time1);
178 if(ret==0 && time1>=0.0) continue;
179 } else if(time2<0) {
180 ret=atof_with_check(argv[ai], &time2);
181 if(ret==0 && time2>=time1) continue;
182 } else if(dose<0) {
183 ret=atof_with_check(argv[ai], &dose);
184 if(ret==0 && dose>0.0) continue;
185 } else if(isnan(weight)) {
186 ret=atof_with_check(argv[ai], &weight);
187 if(ret==0) continue;
188 } else if(!rfile[0]) {
189 strlcpy(rfile, argv[ai], FILENAME_MAX); continue;
190 }
191 fprintf(stderr, "Error: invalid argument '%s'.\n", argv[ai]); return(1);
192 }
193
194 /* Is something missing? */
195 if(!rfile[0]) {
196 fprintf(stderr, "Error: missing result file name.\n");
197 return(1);
198 }
199 if(weight<1.0E-100) weight=nan("");
200#endif
201
202 /* Is something missing? */
203 if(!(dose>0.0)) {
204 fprintf(stderr, "Error: missing dose.\n");
205 return(1);
206 }
207 if(!rfile[0] && !sfile[0]) {
208 fprintf(stderr, "Error: missing result file name.\n");
209 return(1);
210 }
211
212 /* In verbose mode print arguments and options */
213 if(verbose>1) {
214 printf("dfile := %s\n", dfile);
215 printf("rfile := %s\n", rfile);
216 printf("sfile := %s\n", sfile);
217 printf("Time range := %g-%g min\n", time1, time2);
218 printf("dose := %g MBq\n", dose);
219 if(!isnan(weight)) printf("weight := %g kg\n", weight);
220 printf("density := %g g/ml\n", density);
221 }
222
223
224 /*
225 * Read TAC data
226 */
227 if(verbose>1) printf("reading TAC data in %s\n", dfile);
228 if(dftRead(dfile, &dft)) {
229 fprintf(stderr, "Error in reading '%s': %s\n", dfile, dfterrmsg);
230 dftEmpty(&dft); return(2);
231 }
232 if(verbose>1) {
233 printf("tacNr := %d\n", dft.voiNr);
234 printf("sampleNr := %d\n", dft.frameNr);
235 }
236 /* Check if file contains NAs (missing values) */
237 if(dft_nr_of_NA(&dft)>0) {
238 fprintf(stderr, "Error: missing values in %s\n", dfile);
239 dftEmpty(&dft); return(2);
240 }
241 /* Sort data by increasing sample time */
242 dftSortByFrame(&dft);
243
244 /* Make sure that there is no overlap in frame times */
245 if(dft.timetype==DFT_TIME_STARTEND) {
246 if(verbose>2) fprintf(stdout, "checking frame overlap in %s\n", dfile);
247 ret=dftDeleteFrameOverlap(&dft);
248 if(ret) {
249 fprintf(stderr, "Error: %s has overlapping frame times.\n", dfile);
250 dftEmpty(&dft); return(2);
251 }
252 }
253
254 /* Check the radioactivity units */
255 if(petCunitId(dft.unit)==CUNIT_UNKNOWN) {
256 /* assuming kBq/mL */
257 } else if(petCunitId(dft.unit)==CUNIT_KBQ_PER_ML) {
258 /* ok as such */
259 } else {
260 /* Try to convert to kBq/mL */
261 ret=dftUnitConversion(&dft, CUNIT_KBQ_PER_ML);
262 if(ret!=0) {
263 fprintf(stderr, "Error: activity unit %s is not supported.\n", dft.unit);
264 dftEmpty(&dft); return(3);
265 }
266 if(verbose>0) printf("Data converted to %s\n", dft.unit);
267 }
268
269 /* Check the time units; convert start and end times if necessary */
270 if(time2>0.0) {
271 if(dft.timeunit==TUNIT_SEC) {
272 time1*=60.; time2*=60.;
273 if(verbose>0) fprintf(stderr, "Note: start and end times converted to seconds.\n");
274 } else if(dft.timeunit==TUNIT_UNKNOWN) {
275 fprintf(stderr, "Warning: unknown data time units.\n");
276 fprintf(stderr, "Warning: assuming that data and time range are in same units.\n");
277 }
278 }
279
280 /* Get the TAC start and end times */
281 if(dft.timetype==DFT_TIME_STARTEND) {
282 t1=dft.x1[0]; t2=dft.x2[dft.frameNr-1];
283 } else {
284 t1=dft.x[0]; t2=dft.x[dft.frameNr-1];
285 }
286 if(verbose>1) {
287 printf("tac_starttime := %g\n", t1);
288 printf("tac_endtime := %g\n", t2);
289 }
290 if(time2<1.0E-08) {
291 /* If user did not specify start and end times, then get those from data */
292 time1=t1; time2=t2;
293 } else {
294 /* otherwise check */
295 double allow_dt;
296 allow_dt=0.19*(t2-t1);
297 if(time2>t2+allow_dt) {
298 fprintf(stderr, "Error: time range %g-%g is outside measurement range %g-%g.\n",
299 time1, time2, t1, t2);
300 dftEmpty(&dft); return(3);
301 }
302 }
303
304
305 /*
306 * Calculate SUV or PID curve
307 */
308 f=dose;
309 if(!isnan(weight)) {
310 if(verbose>1) fprintf(stdout, "calculating SUV TAC\n");
311 f/=weight; /* Act / (Dose/Weight) */
312 } else {
313 if(verbose>1) fprintf(stdout, "calculating PID TAC\n");
314 /* Percent of injected dose / mL */
315 f*=1000.0/100.0; /* 100% * Act / (Dose*1000) */
316 /* per L instead of mL */
317 f/=1000.0;
318 }
319 if(density>0.0) f*=density; /* Act / Density */
320 if(verbose>2) printf("conversion factor := %g\n", f);
321 /* Calculate curve */
322 for(ri=0; ri<dft.voiNr; ri++)
323 for(fi=0; fi<dft.frameNr; fi++)
324 dft.voi[ri].y[fi]/=f;
325 /* Set calibration units */
326 if(weight>0) {
327 strcpy(dft.unit, "g/");
328 if(density>0) strcat(dft.unit, "g"); else strcat(dft.unit, "mL");
329 } else {
330 strcpy(dft.unit, "%i.d./");
331 if(density>0) strcat(dft.unit, "kg"); else strcat(dft.unit, "L");
332 }
333
334 /* Remove possible path from TAC file name, to be saved in results */
335 filenameRmPath(dfile);
336
337
338 /*
339 * Save the curves, if requested
340 */
341 if(sfile[0]) {
342 if(verbose>1) printf("saving converted TAC(s) in %s\n", sfile);
343 tpcProgramName(argv[0], 1, 0, tmp, 128);
344 if(isnan(weight)) f=0.0; else f=weight;
345 sprintf(dft.comments, "# %s: %s %g %g %g %g\n", tmp, dfile, time1, time2, dose, f);
346 if(dftWrite(&dft, sfile)) {
347 fprintf(stderr, "Error in writing '%s': %s\n", sfile, dfterrmsg);
348 dftEmpty(&dft);
349 return(11);
350 }
351 if(!isnan(weight)) strcpy(tmp, "SUV"); else strcpy(tmp, "%i.d.");
352 if(verbose>0) fprintf(stdout, "%s curves written in %s\n", tmp, sfile);
353 }
354
355 /*
356 * Stop, if SUV mean was not requested
357 */
358 if(!rfile[0]) {dftEmpty(&dft); return(0);}
359
360
361 /*
362 * Calculate the TAC average over user-specified time range
363 */
364 if(verbose>1) printf("calculating average\n");
365 ret=dftTimeIntegral(&dft, time1, time2, &suv, 1, tmp, verbose-3);
366 if(ret!=0) {
367 fprintf(stderr, "Error: %s.\n", tmp);
368 if(verbose>2)
369 printf("dftTimeIntegral(suvtac, %g, %g, suvavg, 1, tmp) := %d\n", time1, time2, ret);
370 dftEmpty(&dft); dftEmpty(&suv);
371 return(3);
372 }
373 if(verbose>1) fprintf(stdout, "%s.\n", tmp);
374
375 /* Original TAC is not needed any more */
376 dftEmpty(&dft);
377
378
379 /*
380 * Save the SUV or PID mean
381 */
382 if(verbose>1) printf("saving average\n");
383 /* If filename extension is .dft, .csv, or .dat, then save in DFT, CSV-UK, or
384 simple format, respectively, otherwise as RES */
385 cptr=strrchr(rfile, '.');
386 if(cptr!=NULL && strcasecmp(cptr, ".DFT")==0) {
388 dftSetComments(&suv);
389 if(dftWrite(&suv, rfile)) {
390 fprintf(stderr, "Error in writing %s: %s\n", rfile, dfterrmsg);
391 dftEmpty(&suv); return(21);
392 }
393 } else if(cptr!=NULL && strcasecmp(cptr, ".CSV")==0) {
396 if(dftWrite(&suv, rfile)) {
397 fprintf(stderr, "Error in writing %s: %s\n", rfile, dfterrmsg);
398 dftEmpty(&suv); return(22);
399 }
400 } else if(cptr!=NULL && strcasecmp(cptr, ".DAT")==0) {
403 if(dftWrite(&suv, rfile)) {
404 fprintf(stderr, "Error in writing %s: %s\n", rfile, dfterrmsg);
405 dftEmpty(&suv); return(23);
406 }
407 } else {
408 /* Copy DFT data into RES */
409 ret=dftToResult(&suv, &res, tmp);
410 if(ret!=0) {
411 fprintf(stderr, "Error: %s.\n", tmp);
412 dftEmpty(&suv); resEmpty(&res); return(24);
413 }
414 /* Set more header information */
415 tpcProgramName(argv[0], 1, 1, res.program, 256);
416 strcpy(res.datafile, dfile);
417 sprintf(res.reffile, "Dose %g MBq", dose);
418 if(!isnan(weight)) {
419 sprintf(tmp, " ; %g kg", weight);
420 strcat(res.reffile, tmp);
421 }
422 if(density>0.0) res.density=density;
423 /* Set the string for parameter names */
424 if(isnan(weight) && density<=0.0) {
425 strcpy(res.parname[0], "PID/L"); strcpy(res.parunit[0], "");
426 } else if(isnan(weight) && density>0.0) {
427 strcpy(res.parname[0], "PID/kg"); strcpy(res.parunit[0], "");
428 } else if(density<=0.0) {
429 strcpy(res.parname[0], "SUV"); strcpy(res.parunit[0], "mL/g");
430 } else {
431 strcpy(res.parname[0], "SUV"); strcpy(res.parunit[0], "mL/mL");
432 }
433 res.isweight=0;
434 /* Save RES */
435 ret=resWrite(&res, rfile, verbose-3);
436 if(ret) {
437 fprintf(stderr, " Error (%d) in writing file %s\n", ret, rfile);
438 dftEmpty(&suv); resEmpty(&res); return(25);
439 }
440 resEmpty(&res);
441 }
442 if(verbose>0) {
443 if(!isnan(weight)) strcpy(tmp, "SUV"); else strcpy(tmp, "%i.d.");
444 if(verbose>0) fprintf(stdout, "%s written in %s\n", tmp, rfile);
445 }
446
447 dftEmpty(&suv);
448
449 return(0);
450}
451/*****************************************************************************/
452
453/*****************************************************************************/
int atof_with_check(char *double_as_string, double *result_value)
Definition decpoint.c:107
double atof_dpi(char *str)
Definition decpoint.c:59
void dftInit(DFT *data)
Definition dft.c:38
char dfterrmsg[64]
Definition dft.c:6
int dftDeleteFrameOverlap(DFT *dft)
Definition dft.c:1237
void dftSetComments(DFT *dft)
Definition dft.c:1326
int dftSortByFrame(DFT *dft)
Definition dft.c:1169
void dftEmpty(DFT *data)
Definition dft.c:20
int dft_nr_of_NA(DFT *dft)
Definition dft.c:905
int dftTimeIntegral(DFT *dft, double t1, double t2, DFT *idft, int calc_mode, char *status, int verbose)
Definition dftint.c:382
int dftRead(char *filename, DFT *data)
Definition dftio.c:22
int dftWrite(DFT *data, char *filename)
Definition dftio.c:594
int dftToResult(DFT *dft, RES *res, char *status)
Definition dftres.c:60
int dftUnitConversion(DFT *dft, int dunit)
Definition dftunit.c:25
void filenameRmPath(char *s)
Definition filename.c:13
Header file for libtpccurveio.
void resInit(RES *res)
Definition result.c:52
#define DFT_FORMAT_CSV_UK
#define DFT_TIME_MIDDLE
#define DFT_FORMAT_STANDARD
int resWrite(RES *res, char *filename, int verbose)
Definition result.c:565
#define DFT_FORMAT_PLAIN
#define DFT_TIME_STARTEND
void resEmpty(RES *res)
Definition result.c:22
Header file for libtpcmisc.
int tpcProcessStdOptions(const char *s, int *print_usage, int *print_version, int *verbose_level)
Definition proginfo.c:40
int petCunitId(const char *unit)
Definition petunits.c:74
size_t strlcpy(char *dst, const char *src, size_t dstsize)
Definition strext.c:245
void tpcProgramName(const char *program, int version, int copyright, char *prname, int n)
Definition proginfo.c:101
int tpcHtmlUsage(const char *program, char *text[], const char *path)
Definition proginfo.c:213
void tpcPrintBuild(const char *program, FILE *fp)
Definition proginfo.c:383
void tpcPrintUsage(const char *program, char *text[], FILE *fp)
Definition proginfo.c:158
Header file for libtpcmodel.
Header file for libtpcmodext.
int _type
int timetype
Voi * voi
int timeunit
double * x1
char comments[_DFT_COMMENT_LEN+1]
int voiNr
double * x2
int frameNr
double * x
char unit[MAX_UNITS_LEN+1]
double density
char parname[MAX_RESPARAMS][MAX_RESPARNAME_LEN+1]
char program[1024]
int isweight
char datafile[FILENAME_MAX]
char reffile[FILENAME_MAX]
char parunit[MAX_RESPARAMS][MAX_RESPARNAME_LEN+1]
double * y