1 /*
2
3 Copyright (C) 2000,2005,2007-2020 Alois Schloegl <alois.schloegl@gmail.com>
4 Copyright (C) 2007 Elias Apostolopoulos
5 This file is part of the "BioSig for C/C++" repository
6 (biosig4c++) at http://biosig.sf.net/
7
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 3
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 */
23
24 // T1T2 is an experimental flag for testing segment selection
25 #define T1T2
26
27
28 #include <assert.h>
29 #include <math.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <time.h>
35 #include "biosig-dev.h"
36 #include "biosig.h"
37
38 #define min(a,b) (((a) < (b)) ? (a) : (b))
39 #define max(a,b) (((a) > (b)) ? (a) : (b))
40
41 #ifdef __cplusplus
42 extern "C" {
43 #endif
44 int savelink(const char* filename);
45 #ifdef __cplusplus
46 }
47 #endif
48
49 #ifdef WITH_PDP
50 void sopen_pdp_read(HDRTYPE *hdr);
51 #endif
52
main(int argc,char ** argv)53 int main(int argc, char **argv){
54
55 HDRTYPE *hdr;
56 size_t count, k1, ne=0;
57 char *source, *dest, *tmpstr;
58 biosig_options_type biosig_options;
59 biosig_options.free_text_event_limiter = ""; // default is no delimiter
60
61 enum FileFormat SOURCE_TYPE; // type of file format
62 struct {
63 enum FileFormat TYPE;
64 float VERSION;
65 } TARGET;
66 TARGET.TYPE=GDF;
67 TARGET.VERSION=-1.0;
68
69 int COMPRESSION_LEVEL=0;
70 int status;
71 uint16_t k;
72 uint16_t chansel = 0;
73 int TARGETSEGMENT=1; // select segment in multi-segment file format EEG1100 (Nihon Kohden)
74 int VERBOSE = 0;
75 char FLAG_ANON = 1;
76 char FLAG_CSV = 0;
77 char FLAG_JSON = 0;
78 char FLAG_DYGRAPH = 0;
79 char *argsweep = NULL;
80 double t1=0.0, t2=1.0/0.0;
81 #ifdef CHOLMOD_H
82 char *rrFile = NULL;
83 int refarg = 0;
84 #endif
85
86 for (k=1; k<argc; k++) {
87 if (!strcmp(argv[k],"-v") || !strcmp(argv[k],"--version") ) {
88 fprintf(stdout,"save2gdf (BioSig4C++) v%i.%i.%i\n", BIOSIG_VERSION_MAJOR, BIOSIG_VERSION_MINOR, BIOSIG_PATCHLEVEL);
89 fprintf(stdout,"Copyright (C) 2006-2013 by Alois Schloegl and others\n");
90 fprintf(stdout,"This file is part of BioSig http://biosig.sf.net - the free and\n");
91 fprintf(stdout,"open source software library for biomedical signal processing.\n\n");
92 fprintf(stdout,"BioSig is free software; you can redistribute it and/or modify\n");
93 fprintf(stdout,"it under the terms of the GNU General Public License as published by\n");
94 fprintf(stdout,"the Free Software Foundation; either version 3 of the License, or\n");
95 fprintf(stdout,"(at your option) any later version.\n\n");
96 }
97 else if (!strcmp(argv[k],"-h") || !strcmp(argv[k],"--help") ) {
98 fprintf(stdout,"\nusage: save2gdf [OPTIONS] SOURCE DEST\n");
99 fprintf(stdout," SOURCE is the source file \n");
100 fprintf(stdout," SOURCE can be also network file bscs://<hostname>/ID e.g. bscs://129.27.3.99/9aebcc5b4eef1024 \n");
101 fprintf(stdout," DEST is the destination file \n");
102 fprintf(stdout," DEST can be also network server bscs://<hostname>\n");
103 fprintf(stdout," The corresponding ID number is reported and a bscs-link file is stored in /tmp/<SOURCE>.bscs\n");
104 fprintf(stdout,"\n Supported OPTIONS are:\n");
105 fprintf(stdout," -v, --version\n\tprints version information\n");
106 fprintf(stdout," -h, --help \n\tprints this information\n");
107 #ifdef T1T2
108 fprintf(stdout," [t0,dt]\n\tstart time and duration in seconds (do not use any spaces). \n");
109 fprintf(stdout,"\tTHIS FEATURE IS CURRENTLY EXPERIMENTAL !!!\n");
110 #endif
111 #ifdef CHOLMOD_H
112 fprintf(stdout," -r, --ref=MM \n\trereference data with matrix file MM. \n\tMM must be a 'MatrixMarket matrix coordinate real general' file.\n");
113 #endif
114 fprintf(stdout," -f=FMT \n\tconverts data into format FMT\n");
115 fprintf(stdout,"\tFMT must represent a valid target file format\n");
116 #if defined(WITH_SCP3)
117 fprintf(stdout,"\tCurrently are supported: HL7aECG, SCP_ECG (EN1064:2005), SCP2, SCP3, GDF, EDF, BDF, CFWB, BIN, ASCII, ATF, BVA (BrainVision)\n\tas well as HEKA v2 -> ITX\n");
118 #else
119 fprintf(stdout,"\tCurrently are supported: HL7aECG, SCP_ECG (EN1064:2005), GDF, EDF, BDF, CFWB, BIN, ASCII, ATF, BVA (BrainVision)\n\tas well as HEKA v2 -> ITX\n");
120 #endif
121 fprintf(stdout, " -a, --anon=yes (default)\n"
122 "\tanonymized data processing - personalize data (name, and birthday) is not processed but ignored.\n"
123 "\tThe patient can be still identified with the unique patient identifier (and an external database).\n"
124 "\tThis is for many cases sufficient (e.g. for research etc.). This mode can be turn off with\n"
125 " -n, --anon=no\n"
126 "\tThis will process personal information like name and birthday. One might want to use this mode\n"
127 "\twhen converting personalized patient data and no unique patient identifier is available.\n"
128 "\tIt's recommended to pseudonize the data, or to use the patient identifier instead of patient name and birthday.\n"
129 );
130 fprintf(stdout," -CSV \n\texports data into CSV file\n");
131 fprintf(stdout," --free-text-event-limiter=\";\"\n\tfree text of events limited to first occurrence of \";\" (only EDF+/BDF+ format)\n");
132 fprintf(stdout," -DYGRAPH, -f=DYGRAPH \n\tproduces JSON output for presentation with dygraphs\n");
133 fprintf(stdout," -JSON \n\tshows header and events in JSON format\n");
134 fprintf(stdout," -z=#, -z#\n\t# indicates the compression level (#=0 no compression; #=9 best compression, default #=1)\n");
135 fprintf(stdout," -s=#\tselect target segment # (in the multisegment file format EEG1100)\n");
136 fprintf(stdout," -SWEEP=ne,ng,ns\n\tsweep selection of HEKA/PM files\n\tne,ng, and ns select the number of experiment, the number of group, and the sweep number, resp.\n");
137 fprintf(stdout," -VERBOSE=#, verbosity level #\n\t0=silent [default], 9=debugging\n");
138 fprintf(stdout," --chan=CHAN\n\tselect channel CHAN (0: all channels, 1: first channel, etc.)\n");
139 fprintf(stdout,"\n\n");
140 return(0);
141 }
142 else if (!strncmp(argv[k],"-z",2)) {
143 #ifdef ZLIB_H
144 COMPRESSION_LEVEL = 1; // default
145 char *s = argv[k] + 2;
146 if (s[0] == '=') s++; // skip "="
147 if (strlen(s)>0) COMPRESSION_LEVEL = atoi(s);
148 if (COMPRESSION_LEVEL<0 || COMPRESSION_LEVEL>9)
149 fprintf(stderr,"Error %s: Invalid Compression Level %s\n",argv[0],argv[k]);
150 #else
151 fprintf(stderr,"Warning: option -z (compression) not supported. zlib not linked.\n");
152 #endif
153 }
154 else if (!strncmp(argv[k],"-VERBOSE",2)) {
155 VERBOSE = argv[k][strlen(argv[k])-1]-48;
156 #ifndef NDEBUG
157 // then VERBOSE_LEVEL is not a constant but a variable
158 VERBOSE_LEVEL = VERBOSE;
159 #endif
160 }
161 else if (!strncasecmp(argv[k],"-SWEEP=",7)) {
162 argsweep = argv[k]+6;
163
164 }
165
166 else if (!strcasecmp(argv[k],"-a") || !strcasecmp(argv[k],"--anon") )
167 FLAG_ANON = 1;
168
169 else if (!strcasecmp(argv[k],"-n") || !strcasecmp(argv[k],"--anon=no") )
170 FLAG_ANON = 0;
171
172 else if (!strcasecmp(argv[k],"-CSV"))
173 FLAG_CSV = 1;
174
175 else if (!strcasecmp(argv[k],"-JSON"))
176 FLAG_JSON = 1;
177
178 else if (!strcasecmp(argv[k],"-DYGRAPH"))
179 FLAG_DYGRAPH = 1;
180
181 else if (!strncmp(argv[k],"--free-text-event-limiter=",26))
182 biosig_options.free_text_event_limiter = strstr(argv[k],"=") + 1;
183
184 else if (!strncmp(argv[k],"-f=",3)) {
185 if (0) {}
186 else if (!strncmp(argv[k],"-f=ASCII",8))
187 TARGET.TYPE=ASCII;
188 else if (!strcmp(argv[k],"-f=ATF"))
189 TARGET.TYPE=ATF;
190 else if (!strcmp(argv[k],"-f=BDF"))
191 TARGET.TYPE=BDF;
192 else if (!strncmp(argv[k],"-f=BIN",6))
193 TARGET.TYPE=BIN;
194 else if (!strncmp(argv[k],"-f=BVA",6))
195 TARGET.TYPE=BrainVision;
196 else if (!strncmp(argv[k],"-f=CFWB",7))
197 TARGET.TYPE=CFWB;
198 else if (!strcmp(argv[k],"-f=EDF"))
199 TARGET.TYPE=EDF;
200 else if (!strcmp(argv[k],"-f=GDF"))
201 TARGET.TYPE=GDF;
202 else if (!strcmp(argv[k],"-f=GDF1")) {
203 TARGET.TYPE=GDF;
204 TARGET.VERSION=1.0;
205 }
206 else if (!strcmp(argv[k],"-f=GDF2")) {
207 TARGET.TYPE=GDF;
208 TARGET.VERSION=2.0;
209 }
210 #if (BIOSIG_VERSION >= 10700)
211 else if (!strcmp(argv[k],"-f=GDF3") && (get_biosig_version() > 0x010703) ) {
212 TARGET.TYPE=GDF;
213 TARGET.VERSION=3.0;
214 }
215 #endif
216 else if (!strncmp(argv[k],"-f=HL7",6))
217 TARGET.TYPE=HL7aECG;
218 else if (!strncmp(argv[k],"-f=CSV",6)) {
219 FLAG_CSV = 1;
220 }
221 else if (!strncmp(argv[k],"-f=DYGRAPH",10)) {
222 FLAG_DYGRAPH = 1;
223 }
224 else if (!strncmp(argv[k],"-f=MFER",7))
225 TARGET.TYPE=MFER;
226 #if defined(WITH_SCP3)
227 else if (!strncmp(argv[k],"-f=SCP3",7) && (get_biosig_version() > 0x010806) ) {
228 TARGET.TYPE=SCP_ECG;
229 TARGET.VERSION=3.0;
230 fprintf(stderr,"WARNING %s: Specification of SCPv3 is not finalized, "
231 "\n\tand is subject to change without further notice. "
232 "\n\tSCP3 may be used only for experimental work"
233 "\n\tYou are warned !!!\n",__FILE__);
234 }
235 #endif
236 else if (!strncmp(argv[k],"-f=SCP",6)) {
237 TARGET.TYPE=SCP_ECG;
238 TARGET.VERSION=2.0;
239 }
240 // else if (!strncmp(argv[k],"-f=TMSi",7))
241 // TARGET.TYPE=TMSiLOG;
242 else if (!strncmp(argv[k],"-f=ITX",6))
243 TARGET.TYPE=ITX;
244 else {
245 fprintf(stderr,"format %s not supported.\n",argv[k]);
246 return(-1);
247 }
248 }
249
250 #ifdef CHOLMOD_H
251 else if ( !strncmp(argv[k],"-r=",3) || !strncmp(argv[k],"--ref=",6) ) {
252 // re-referencing matrix
253 refarg = k;
254 }
255 #endif
256
257 else if (!strncmp(argv[k],"-s=",3)) {
258 TARGETSEGMENT = atoi(argv[k]+3);
259 }
260
261 else if (argv[k][0]=='[' && argv[k][strlen(argv[k])-1]==']' && (tmpstr=strchr(argv[k],',')) ) {
262 t1 = strtod(argv[k]+1,NULL);
263 t2 = strtod(tmpstr+1,NULL);
264 if (VERBOSE_LEVEL>7) fprintf(stdout,"[%f,%f]\n",t1,t2);
265 }
266
267 else {
268 break;
269 }
270
271 if (VERBOSE_LEVEL>7)
272 fprintf(stdout,"%s (line %i): save2gdf: arg%i = <%s>\n",__FILE__,__LINE__, k, argv[k]);
273
274 }
275
276
277 source = NULL;
278 dest = NULL;
279
280 switch (argc - k) {
281 case 0:
282 fprintf(stderr,"save2gdf: missing file argument\n");
283 fprintf(stdout,"usage: save2gdf [options] SOURCE DEST\n");
284 fprintf(stdout," for more details see also save2gdf --help \n");
285 exit(-1);
286 case 2:
287 dest = argv[k+1];
288 case 1:
289 source = argv[k];
290 }
291
292 if (VERBOSE_LEVEL<0) VERBOSE=1; // default
293 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): SAVE2GDF %s %s started \n",__FILE__,__LINE__, source, dest);
294 fprintf(stderr,"%s %s %s\n", argv[0], source, dest);
295
296 tzset();
297 hdr = constructHDR(0,0);
298 // hdr->FLAG.OVERFLOWDETECTION = FlagOverflowDetection;
299 hdr->FLAG.UCAL = ((TARGET.TYPE==BIN) || (TARGET.TYPE==SCP_ECG));
300 hdr->FLAG.TARGETSEGMENT = TARGETSEGMENT;
301 hdr->FLAG.ANONYMOUS = FLAG_ANON;
302
303 if (argsweep) {
304 k = 0;
305 do {
306 if (VERBOSE_LEVEL>7) fprintf(stdout,"SWEEP [109] %i: %s\t",k,argsweep);
307 hdr->AS.SegSel[k++] = strtod(argsweep+1, &argsweep);
308 if (VERBOSE_LEVEL>7) fprintf(stdout,",%i\n",hdr->AS.SegSel[k-1]);
309 } while (argsweep[0]==',' && (k < 5) );
310 }
311
312 // HEKA2ITX hack
313 if (TARGET.TYPE==ITX) {
314 // hack: HEKA->ITX conversion will be done in SOPEN
315 hdr->aECG = dest;
316 }
317
318 hdr = sopen_extended(source, "r", hdr, &biosig_options);
319 #ifdef WITH_PDP
320 if (hdr->AS.B4C_ERRNUM) {
321 biosigERROR(hdr, 0, NULL); // reset error
322 sopen_pdp_read(hdr);
323 }
324 #endif
325 // HEKA2ITX hack
326 if (TARGET.TYPE==ITX) {
327 if (hdr->TYPE==HEKA) {
328 // hack: HEKA->ITX conversion is already done in SOPEN
329 dest = NULL;
330 }
331 else {
332 fprintf(stdout,"error: only HEKA->ITX is supported - source file is not HEKA file");
333 biosigERROR(hdr, B4C_UNSPECIFIC_ERROR, "error: only HEKA->ITX is supported - source file is not HEKA file");
334 }
335 }
336
337 #ifdef CHOLMOD_H
338 if (refarg > 0) {
339 rrFile = strchr(argv[refarg], '=') + 1;
340 if (RerefCHANNEL(hdr, rrFile, 1))
341 fprintf(stdout,"error: reading re-ref matrix %s \n",rrFile);
342 }
343 #endif
344
345 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): SOPEN-R finished (error %i)\n",__FILE__,__LINE__, hdr->AS.B4C_ERRNUM);
346
347 if ((status=serror2(hdr))) {
348 destructHDR(hdr);
349 exit(status);
350 }
351
352 t1 *= hdr->SampleRate / hdr->SPR;
353 t2 *= hdr->SampleRate / hdr->SPR;
354 if (isnan(t1)) t1 = 0.0;
355 if (t2+t1 > hdr->NRec) t2 = hdr->NRec - t1;
356
357 if ( ( t1 - floor (t1) ) || ( t2 - floor(t2) ) ) {
358 fprintf(stderr,"ERROR SAVE2GDF: cutting from parts of blocks not supported; t1 (%f) and t2 (%f) must be a multiple of block duration %f\n", t1,t2,hdr->SPR / hdr->SampleRate);
359 biosigERROR(hdr, B4C_UNSPECIFIC_ERROR, "blocks must not be split");
360 }
361
362 if ((status=serror2(hdr))) {
363 destructHDR(hdr);
364 exit(status);
365 }
366
367 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i) SOPEN-R finished\n",__FILE__,__LINE__);
368
369 sort_eventtable(hdr);
370
371 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i) event table sorted\n",__FILE__,__LINE__);
372
373 if (FLAG_JSON) {
374 fprintf_hdr2json(stdout, hdr);
375 }
376 else {
377 hdr2ascii(hdr, stdout, VERBOSE);
378 }
379
380 // all channels are converted - channel selection currently not supported
381 for (k=0; k<hdr->NS; k++) {
382 if ( (hdr->CHANNEL[k].OnOff > 0) && hdr->CHANNEL[k].SPR ) {
383 if ((hdr->SPR/hdr->CHANNEL[k].SPR)*hdr->CHANNEL[k].SPR != hdr->SPR)
384 fprintf(stdout,"Warning: channel %i might be decimated!\n",k+1);
385 };
386 // hdr->CHANNEL[k].OnOff = 1; // convert all channels
387 }
388
389 #ifdef CHOLMOD_H
390 int flagREREF = hdr->Calib != NULL && hdr->rerefCHANNEL != NULL;
391 #else
392 int flagREREF = 0;
393 #endif
394 hdr->FLAG.OVERFLOWDETECTION = 0;
395 hdr->FLAG.UCAL = hdr->FLAG.UCAL && !flagREREF;
396 hdr->FLAG.ROW_BASED_CHANNELS = flagREREF;
397
398 #ifdef CHOLMOD_H
399 if (VERBOSE_LEVEL>7)
400 fprintf(stdout,"%s (line %i): %p %p Flag.ReRef=%i\n",__FILE__,__LINE__,hdr->Calib, hdr->rerefCHANNEL,flagREREF);
401 #endif
402
403 if (VERBOSE_LEVEL>7)
404 fprintf(stdout,"%s (line %i): SREAD [%f,%f].\n",__FILE__,__LINE__,t1,t2);
405
406 if (hdr->NRec <= 0) {
407 // in case number of samples is not known
408 count = sread(NULL, t1, (size_t)-1, hdr);
409 t2 = count;
410 }
411 else {
412 if (t2+t1 > hdr->NRec) t2 = hdr->NRec - t1;
413 if ((dest != NULL) || FLAG_CSV || FLAG_DYGRAPH )
414 count = sread(NULL, t1, t2, hdr);
415 }
416
417 biosig_data_type* data = hdr->data.block;
418 if ((VERBOSE_LEVEL>8) && (hdr->data.size[0]*hdr->data.size[1]>500))
419 fprintf(stdout,"%s (line %i): UCAL=%i %e %e %e \n",__FILE__,__LINE__,hdr->FLAG.UCAL,data[100],data[110],data[500+hdr->SPR]);
420
421 if ((status=serror2(hdr))) {
422 destructHDR(hdr);
423 exit(status);
424 };
425
426 if (VERBOSE_LEVEL>7)
427 fprintf(stdout,"\n%s (line %i): SREAD on %s successful [%i,%i].\n",__FILE__,__LINE__,hdr->FileName,(int)hdr->data.size[0],(int)hdr->data.size[1]);
428
429 // fprintf(stdout,"\n %f,%f.\n",hdr->FileName,hdr->data.block[3*hdr->SPR],hdr->data.block[4*hdr->SPR]);
430 if (VERBOSE_LEVEL>7)
431 fprintf(stdout,"\n%s (line %i): File %s =%i/%i\n",__FILE__,__LINE__,hdr->FileName,hdr->FILE.OPEN,hdr->FILE.Des);
432
433 if ((dest==NULL) && !FLAG_CSV && !FLAG_DYGRAPH) {
434 if (ne) /* used for testig SFLUSH_GDF_EVENT_TABLE */
435 {
436 if (hdr->EVENT.N > ne)
437 hdr->EVENT.N -= ne;
438 else
439 hdr->EVENT.N = 0;
440
441 // fprintf(stdout,"Status-SFLUSH %i\n",sflush_gdf_event_table(hdr));
442 }
443
444 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): going for SCLOSE\n",__FILE__,__LINE__);
445 sclose(hdr);
446 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): SCLOSE(HDR) finished\n",__FILE__,__LINE__);
447 status=serror2(hdr);
448 destructHDR(hdr);
449 exit(status);
450 }
451
452 if (hdr->FILE.OPEN) {
453 sclose(hdr);
454 free(hdr->AS.Header);
455 hdr->AS.Header = NULL;
456 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): file closed\n",__FILE__,__LINE__);
457 }
458 if (VERBOSE_LEVEL>7 )
459 fprintf(stdout,"\n%s (line %i): File %s closed sd=%i/%i\n",__FILE__,__LINE__,hdr->FileName,hdr->FILE.OPEN,hdr->FILE.Des);
460
461 SOURCE_TYPE = hdr->TYPE;
462 if (FLAG_DYGRAPH) TARGET.TYPE=SOURCE_TYPE;
463
464 hdr->TYPE = TARGET.TYPE;
465 if (TARGET.VERSION>=0) hdr->VERSION = TARGET.VERSION;
466
467 hdr->FILE.COMPRESSION = COMPRESSION_LEVEL;
468
469 /*******************************************
470 make block size as small as possible
471 *******************************************/
472
473 if (1) {
474 uint32_t asGCD=hdr->SPR;
475 for (k=0; k<hdr->NS; k++)
476 if (hdr->CHANNEL[k].OnOff && hdr->CHANNEL[k].SPR)
477 asGCD = gcd(asGCD, hdr->CHANNEL[k].SPR);
478 if (TARGET.TYPE==EDF) {
479 double d = asGCD / hdr->SampleRate;
480 if (d==ceil(d)) asGCD = d; // make block duration 1 second
481 }
482 hdr->SPR /= asGCD;
483 hdr->NRec *= asGCD;
484 for (k=0; k<hdr->NS; k++)
485 hdr->CHANNEL[k].SPR /= asGCD;
486 #ifdef CHOLMOD_H
487 if (hdr->Calib)
488 for (k=0; k<hdr->Calib->ncol; k++)
489 hdr->rerefCHANNEL[k].SPR /= asGCD;
490 #endif
491 }
492
493 /*********************************
494 re-referencing
495 *********************************/
496
497 #ifdef CHOLMOD_H
498 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): %p %p\n",__FILE__,__LINE__,hdr->CHANNEL,hdr->rerefCHANNEL);
499
500 if (hdr->Calib && hdr->rerefCHANNEL) {
501 if (VERBOSE_LEVEL>6)
502 hdr2ascii(hdr,stdout,3);
503
504 hdr->NS = hdr->Calib->ncol;
505
506 free(hdr->CHANNEL);
507 hdr->CHANNEL = hdr->rerefCHANNEL;
508 hdr->rerefCHANNEL = NULL;
509
510 if (VERBOSE_LEVEL>7) fprintf(stdout,"[200-]\n");
511
512 RerefCHANNEL(hdr, NULL, 0); // clear HDR.Calib und HDR.rerefCHANNEL
513
514 if (VERBOSE_LEVEL>7) fprintf(stdout,"[200+]\n");
515 hdr->Calib = NULL;
516
517 if (VERBOSE_LEVEL>6)
518 hdr2ascii(hdr,stdout,3);
519 }
520 #endif
521
522 /*********************************
523 Write data
524 *********************************/
525
526 //************ identify Max/Min **********
527
528 if (VERBOSE_LEVEL>7) fprintf(stdout,"[201]\n");
529
530 double PhysMaxValue0 = -INFINITY; //hdr->data.block[0];
531 double PhysMinValue0 = +INFINITY; //hdr->data.block[0];
532 biosig_data_type val = NAN;
533 char FLAG_CONVERSION_TESTED = 1;
534 size_t N;
535 #ifdef T1T2
536 N = hdr->FLAG.ROW_BASED_CHANNELS ? hdr->data.size[1] : hdr->data.size[0];
537 hdr->NRec = N/hdr->SPR;
538 #else
539 N = hdr->NRec*hdr->SPR;
540 #endif
541 typeof(hdr->NS) k2=0;
542 for (k=0; k<hdr->NS; k++)
543 if (hdr->CHANNEL[k].OnOff && hdr->CHANNEL[k].SPR) {
544
545 if (VERBOSE_LEVEL > 7) fprintf(stdout,"%s (line %i): #%i %i %i N=%i [%i,%i]\n",__FILE__,__LINE__,(int)k,(int)k2,(int)hdr->FLAG.ROW_BASED_CHANNELS,(int)N,(int)hdr->data.size[0],(int)(hdr->data.size[1]));
546
547 double MaxValue;
548 double MinValue;
549 double MaxValueF;
550 double MinValueF;
551 double MaxValueD;
552 double MinValueD;
553 if (hdr->FLAG.ROW_BASED_CHANNELS) {
554 MaxValue = hdr->data.block[k2];
555 MinValue = hdr->data.block[k2];
556 for (k1=1; k1<N; k1++) {
557 val = hdr->data.block[k2 + k1*hdr->data.size[0]];
558 if (MaxValue < val) MaxValue = val;
559 if (MinValue > val) MinValue = val;
560 }
561 }
562 else {
563 MaxValue = hdr->data.block[k2*N];
564 MinValue = hdr->data.block[k2*N];
565 for (k1=1; k1<N; k1++) {
566 val = hdr->data.block[k2*N + k1];
567 if (MaxValue < val) MaxValue = val;
568 if (MinValue > val) MinValue = val;
569 }
570 }
571
572 if (!hdr->FLAG.UCAL) {
573 MaxValueF = MaxValue;
574 MinValueF = MinValue;
575 MaxValueD = (MaxValue - hdr->CHANNEL[k].Off) / hdr->CHANNEL[k].Cal;
576 MinValueD = (MinValue - hdr->CHANNEL[k].Off) / hdr->CHANNEL[k].Cal;
577 }
578 else {
579 MaxValueF = MaxValue * hdr->CHANNEL[k].Cal + hdr->CHANNEL[k].Off;
580 MinValueF = MinValue * hdr->CHANNEL[k].Cal + hdr->CHANNEL[k].Off;
581 MaxValueD = MaxValue;
582 MinValueD = MinValue;
583 }
584 if (PhysMaxValue0 < MaxValueF) PhysMaxValue0 = MaxValueF;
585 if (PhysMinValue0 > MinValueF) PhysMinValue0 = MinValueF;
586
587 if ((SOURCE_TYPE==alpha) && (hdr->CHANNEL[k].GDFTYP==(255+12)) && (TARGET.TYPE==GDF))
588 // 12 bit into 16 bit
589 ; //hdr->CHANNEL[k].GDFTYP = 3;
590 else if ((SOURCE_TYPE==ETG4000) && (TARGET.TYPE==GDF)) {
591 hdr->CHANNEL[k].GDFTYP = 16;
592 hdr->CHANNEL[k].PhysMax = MaxValueF;
593 hdr->CHANNEL[k].PhysMin = MinValueF;
594 hdr->CHANNEL[k].DigMax = MaxValueD;
595 hdr->CHANNEL[k].DigMin = MinValueD;
596 }
597 /* TODO: check whether this is really needed - was probably just a workaround for another bug
598
599 else if ((SOURCE_TYPE==GDF) && (TARGET.TYPE==GDF))
600 ;
601 else if (SOURCE_TYPE==HEKA)
602 ;
603 else if (SOURCE_TYPE==ABF)
604 ;
605 */
606 else if (TARGET.TYPE==SCP_ECG && !hdr->FLAG.UCAL) {
607 double scale = PhysDimScale(hdr->CHANNEL[k].PhysDimCode) *1e9;
608 if (hdr->FLAG.ROW_BASED_CHANNELS) {
609 for (k1=0; k1<N; k1++)
610 hdr->data.block[k2 + k1*hdr->data.size[0]] *= scale;
611 }
612 else {
613 for (k1=0; k1<N; k1++)
614 hdr->data.block[k2*N + k1] *= scale;
615 }
616 hdr->CHANNEL[k].GDFTYP = 3;
617 hdr->CHANNEL[k].PhysDimCode = 4276; // nV
618 hdr->CHANNEL[k].DigMax = ldexp(1.0,15)-1.0;
619 hdr->CHANNEL[k].DigMin = -hdr->CHANNEL[k].DigMax;
620 double PhysMax = max(fabs(PhysMaxValue0),fabs(PhysMinValue0)) * scale;
621 hdr->CHANNEL[k].PhysMax = PhysMax;
622 hdr->CHANNEL[k].PhysMin = -PhysMax;
623 }
624 else if (TARGET.TYPE==EDF) {
625 hdr->CHANNEL[k].GDFTYP = 3;
626 hdr->CHANNEL[k].DigMax = ldexp(1.0,15)-1.0;
627 hdr->CHANNEL[k].DigMin = -hdr->CHANNEL[k].DigMax;
628 hdr->CHANNEL[k].PhysMax = MaxValueF;
629 hdr->CHANNEL[k].PhysMin = MinValueF;
630 hdr->CHANNEL[k].Cal = (hdr->CHANNEL[k].PhysMax - hdr->CHANNEL[k].PhysMin) / (hdr->CHANNEL[k].DigMax - hdr->CHANNEL[k].DigMin);
631 hdr->CHANNEL[k].Off = hdr->CHANNEL[k].PhysMin - hdr->CHANNEL[k].DigMin * hdr->CHANNEL[k].Cal;
632 }
633 else if ((SOURCE_TYPE==EDF) && (TARGET.TYPE==GDF)) {
634 // do nothing
635 }
636 else if ((hdr->CHANNEL[k].GDFTYP<10 ) && (TARGET.TYPE==GDF || TARGET.TYPE==CFWB)) {
637 /* heuristic to determine optimal data type */
638
639 if (VERBOSE_LEVEL > 7) fprintf(stdout,"%s (line %i): #%i %i %f %f %f %f %f %f\n",__FILE__,__LINE__,k+1,(int)hdr->FLAG.UCAL,MinValue,MaxValue,MinValueF,MaxValueF,MinValueD,MaxValueD);
640
641 if ((MaxValueD <= 127) && (MinValueD >= -128))
642 hdr->CHANNEL[k].GDFTYP = 1;
643 else if ((MaxValueD <= 255.0) && (MinValueD >= 0.0))
644 hdr->CHANNEL[k].GDFTYP = 2;
645 else if ((MaxValueD <= ldexp(1.0,15)-1.0) && (MinValueD >= ldexp(-1.0,15)))
646 hdr->CHANNEL[k].GDFTYP = 3;
647 else if ((MaxValueD <= ldexp(1.0,16)-1.0) && (MinValueD >= 0.0))
648 hdr->CHANNEL[k].GDFTYP = 4;
649 else if ((MaxValueD <= ldexp(1.0,31)-1.0) && (MinValueD >= ldexp(-1.0,31)))
650 hdr->CHANNEL[k].GDFTYP = 5;
651 else if ((MaxValueD <= ldexp(1.0,32)-1.0) && (MinValueD >= 0.0))
652 hdr->CHANNEL[k].GDFTYP = 6;
653 }
654 else {
655 FLAG_CONVERSION_TESTED = 0;
656 }
657
658 if (VERBOSE_LEVEL>7) fprintf(stdout,"#%3d %d [%g %g][%g %g]\n",k,hdr->CHANNEL[k].GDFTYP,MinValue,MaxValue,PhysMinValue0,PhysMaxValue0);
659 k2++;
660 }
661 if (!FLAG_CONVERSION_TESTED)
662 fprintf(stderr,"Warning SAVE2GDF: conversion from %s to %s not tested\n",GetFileTypeString(SOURCE_TYPE),GetFileTypeString(TARGET.TYPE));
663
664 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): UCAL=%i\n",__FILE__,__LINE__, hdr->FLAG.UCAL);
665
666 hdr->FLAG.ANONYMOUS = FLAG_ANON;
667
668 /*
669 keep header data from previous file, in might contain optional data
670 (GDF Header3, EventDescription, hdr->SCP.SectionX, etc. )
671 and might still be referenced and needed.
672 */
673 void *tmpmem = hdr->AS.Header;
674 hdr->AS.Header = NULL;
675
676 if (FLAG_CSV) {
677 const char SEP=',';
678 FILE *fid = stdout;
679 if (dest != NULL) fid = fopen(dest,"wt+");
680 ssize_t k1;
681 size_t k2;
682 char flag = 0;
683 for (k2 = 0; k2 < hdr->NS; k2++) {
684 CHANNEL_TYPE *hc = hdr->CHANNEL+k2;
685 if (hc->OnOff) {
686 if (flag) fprintf(fid,"%c",SEP);
687 flag = 1;
688 fprintf(fid,"\"%s [%s]\"", hc->Label, PhysDim3(hc->PhysDimCode) );
689 }
690 }
691 for (k1 = 0; k1 < hdr->SPR*hdr->NRec; k1++) {
692 flag = 0;
693 for (k2 = 0; k2 < hdr->NS; k2++) {
694 CHANNEL_TYPE *hc = hdr->CHANNEL+k2;
695 if (hc->OnOff) {
696 fprintf(fid,"%c", flag ? SEP : '\n');
697 flag = 1;
698 size_t p = hdr->FLAG.ROW_BASED_CHANNELS ? hdr->data.size[0] * k1 + k2 : hdr->data.size[0] * k2 + k1 ;
699 fprintf(fid,"%g", data[p]);
700 }
701 }
702 }
703 fprintf(fid,"\n");
704 if (dest != NULL) fclose(fid);
705 }
706 else if (FLAG_DYGRAPH) {
707 FILE *fid = stdout;
708 if (dest != NULL) fid = fopen(dest,"wb+");
709 fprintf(fid,"{\n\"Header\": ");
710 fprintf_hdr2json(fid, hdr);
711 fprintf(fid,",\n\"Data\": [ ");
712 ssize_t k1;
713 size_t k2;
714 for (k1=0; k1 < hdr->SPR * hdr->NRec; k1++) {
715 if (k1>0) fprintf(fid,",\n\t");
716 fprintf(fid,"[");
717 for (k2=0; k2<hdr->NS; k2++) {
718 if (k2>0) fprintf(fid,", ");
719 size_t p = hdr->FLAG.ROW_BASED_CHANNELS ? hdr->data.size[0] * k1 + k2 : hdr->data.size[0] * k2 + k1 ;
720 fprintf(fid,"%g",data[p]);
721 }
722 fprintf(fid,"]");
723 }
724
725 fprintf(fid," ],\n\"labels\": [ ");
726 for (k2=0; k2<hdr->NS; k2++) {
727 CHANNEL_TYPE *hc = hdr->CHANNEL+k2;
728 if (k2>0) fprintf(fid,", ");
729 fprintf(fid,"\"%s [%s]\"",hc->Label,PhysDim3(hc->PhysDimCode));
730 }
731 fprintf(fid,"]\n}\n");
732 if (dest != NULL) fclose(fid);
733 }
734 else {
735 /* write file */
736 size_t destlen = strlen(dest);
737 char *tmp = (char*)malloc(destlen+4);
738 strcpy(tmp,dest);
739 if (hdr->FILE.COMPRESSION) // add .gz extension to filename
740 strcpy(tmp+destlen,".gz");
741
742 if (VERBOSE_LEVEL>7)
743 fprintf(stdout,"%s (line %i) z=%i sd=%i\n",__FILE__,__LINE__,hdr->FILE.COMPRESSION,hdr->FILE.Des);
744
745 sopen(tmp, "wb", hdr);
746
747 if (VERBOSE_LEVEL>7) fprintf(stdout,"returned from sopen-wb\n");
748
749 free(tmp);
750 if ((status=serror2(hdr))) {
751 destructHDR(hdr);
752 exit(status);
753 }
754 #ifndef WITHOUT_NETWORK
755 if (hdr->FILE.Des>0)
756 savelink(source);
757 #endif
758 if (VERBOSE_LEVEL>7)
759 fprintf(stdout,"\n%s (line %i): File %s opened. %i %i %i Des=%i\n",__FILE__,__LINE__,hdr->FileName,hdr->AS.bpb,hdr->NS,(int)(hdr->NRec),hdr->FILE.Des);
760
761 swrite(data, hdr->NRec, hdr);
762
763 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): SWRITE finishes (errno=%i)\n",__FILE__,__LINE__,(int)hdr->AS.B4C_ERRNUM);
764 if ((status=serror2(hdr))) {
765 destructHDR(hdr);
766 exit(status);
767 }
768
769 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): SWRITE finishes %i\n",__FILE__,__LINE__,(int)status);
770
771 sclose(hdr);
772
773 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): SCLOSE finished\n",__FILE__,__LINE__);
774
775 }
776 status = serror2(hdr);
777 destructHDR(hdr);
778 if (tmpmem != NULL) free(tmpmem);
779 exit(status);
780 }
781
782