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