1 /*
2
3 Copyright (C) 2005-2018 Alois Schloegl <alois.schloegl@ist.ac.at>
4
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
15 */
16
17
18 #include <assert.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include "../biosig-dev.h"
23
24 #define min(a,b) (((a) < (b)) ? (a) : (b))
25 #define max(a,b) (((a) > (b)) ? (a) : (b))
26
27 #ifdef __cplusplus
28 extern "C" {
29 #endif
30
sopen_SCP_write(HDRTYPE * hdr)31 int sopen_SCP_write(HDRTYPE* hdr) {
32 /*
33 This function is an auxillary function and is only called by the function SOPEN in "biosig.c"
34
35 Input:
36 HDRTYPE *hdr // defines the HDR structure according to "biosig.h"
37 hdr->VERSION specifies the target version
38 */
39 uint8_t* ptr; // pointer to memory mapping of the file layout
40 uint8_t* PtrCurSect; // point to current section
41 int curSect;
42 uint32_t len;
43 uint16_t crc;
44 uint32_t i;
45 uint32_t sectionStart;
46 struct tm* T0_tm;
47 double AVM, avm;
48 uint16_t avm16;
49 struct aecg* aECG;
50
51 assert(hdr != NULL);
52 assert(hdr->TYPE == SCP_ECG);
53
54 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i) : V%f\n",__FILE__,__LINE__,hdr->VERSION);
55
56 if ((fabs(hdr->VERSION - 1.3)<0.01) && (fabs(hdr->VERSION-2.0)<0.01) && (fabs(hdr->VERSION-3.0)<0.01))
57 fprintf(stderr,"Warning SOPEN (SCP-WRITE): Version %f not supported\n",hdr->VERSION);
58
59 uint8_t versionSection = (hdr->VERSION < 3.0) ? 20 : 29; // (uint8_t)round(hdr->VERSION*10); // implemented version number
60 uint8_t versionProtocol = versionSection;
61
62 if (hdr->aECG==NULL) {
63 fprintf(stderr,"Warning SOPEN_SCP_WRITE: No aECG info defined\n");
64 hdr->aECG = malloc(sizeof(struct aecg));
65 aECG = (struct aecg*)hdr->aECG;
66 aECG->diastolicBloodPressure=0.0;
67 aECG->systolicBloodPressure=0.0;
68 aECG->MedicationDrugs="/0";
69 aECG->ReferringPhysician="/0";
70 aECG->LatestConfirmingPhysician="/0";
71 aECG->Diagnosis="/0";
72 aECG->EmergencyLevel=0;
73 #if (BIOSIG_VERSION < 10500)
74 aECG->Section8.NumberOfStatements = 0;
75 aECG->Section8.Statements = NULL;
76 aECG->Section11.NumberOfStatements = 0;
77 aECG->Section11.Statements = NULL;
78 #endif
79 }
80 else
81 aECG = (struct aecg*)hdr->aECG;
82
83
84 //fprintf(stdout,"SCP-Write: IIb %s\n",hdr->aECG->ReferringPhysician);
85 /* predefined values */
86 aECG->Section1.Tag14.INST_NUMBER = 0; // tag 14, byte 1-2
87 aECG->Section1.Tag14.DEPT_NUMBER = 0; // tag 14, byte 3-4
88 aECG->Section1.Tag14.DEVICE_ID = 0; // tag 14, byte 5-6
89 aECG->Section1.Tag14.DeviceType = 0; // tag 14, byte 7: 0: Cart, 1: System (or Host)
90 aECG->Section1.Tag14.MANUF_CODE = 255; // tag 14, byte 8 (MANUF_CODE has to be 255)
91 aECG->Section1.Tag14.MOD_DESC = "Cart1"; // tag 14, byte 9 (MOD_DESC has to be "Cart1")
92 aECG->Section1.Tag14.VERSION = versionSection; // tag 14, byte 15 (VERSION * 10)
93 aECG->Section1.Tag14.PROT_COMP_LEVEL = 0xA0; // tag 14, byte 16 (PROT_COMP_LEVEL has to be 0xA0 => level II)
94 // tag 14, byte 17 (LANG_SUPP_CODE has to be 0x00 => Ascii only, latin and 1-byte code, 0x37: UTF-8)
95 aECG->Section1.Tag14.LANG_SUPP_CODE = (versionSection < 25) ? 0x00 : 0x37;
96 aECG->Section1.Tag14.ECG_CAP_DEV = 0xD0; // tag 14, byte 18 (ECG_CAP_DEV has to be 0xD0 => Acquire, (No Analysis), Print and Store)
97 aECG->Section1.Tag14.MAINS_FREQ = 0; // tag 14, byte 19 (MAINS_FREQ has to be 0: unspecified, 1: 50 Hz, 2: 60Hz)
98 aECG->Section1.Tag14.ANAL_PROG_REV_NUM = "";
99 aECG->Section1.Tag14.SERIAL_NUMBER_ACQ_DEV = "";
100 aECG->Section1.Tag14.ACQ_DEV_SYS_SW_ID = "";
101 aECG->Section1.Tag14.ACQ_DEV_SCP_SW = "OpenECG XML-SCP 1.00"; // tag 14, byte 38 (SCP_IMPL_SW has to be "OpenECG XML-SCP 1.00")
102 aECG->Section1.Tag14.ACQ_DEV_MANUF = "Manufacturer"; // tag 14, byte 38 (ACQ_DEV_MANUF has to be "Manufacturer")
103
104 aECG->Section5.Length = 0;
105 aECG->Section6.Length = 0;
106
107
108 /* */
109 aECG->FLAG.HUFFMAN = 0;
110 aECG->FLAG.REF_BEAT= 0;
111 aECG->FLAG.DIFF = 0;
112 aECG->FLAG.BIMODAL = 0;
113
114
115 /*
116 check channels:
117 disable channels that do not have a known ECG LeadId
118 disable channels with physical units other than Voltage.
119 The number of channels for conversion is stored in NS.
120 */
121 typeof(hdr->NS) NS = 0, k;
122 for (k=0; k < hdr->NS; k++) {
123 CHANNEL_TYPE *CH=hdr->CHANNEL+k;
124
125 if ( CH->LeadIdCode > 255) CH->OnOff = 0;
126 if ( (CH->PhysDimCode & 0xffe0) != PhysDimCode("V")) CH->OnOff = 0;
127
128 if (CH->OnOff != 1) continue;
129 NS++;
130 }
131
132 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i) : v%f VERSION=%i\n",__FILE__,__LINE__, hdr->VERSION, versionSection);
133
134 ptr = (uint8_t*)hdr->AS.Header;
135
136 int NSections = (versionSection < 25) ? 12 : 19;
137 // initialize section 0
138 sectionStart = 6+16+NSections*10;
139 ptr = (uint8_t*)realloc(ptr,sectionStart);
140 memset(ptr,0,sectionStart);
141
142 uint32_t curSectLen; // current section length
143 for (curSect=NSections-1; curSect>=0; curSect--) {
144
145 curSectLen = 0; // current section length
146 //ptr = (uint8_t*)realloc(ptr,sectionStart+curSectLen);
147
148 // if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i) : Section %i/%i %i %p\n",__FILE__,__LINE__,curSect,NSections,sectionStart,ptr);
149
150 if (curSect==0) // SECTION 0
151 {
152 hdr->HeadLen = sectionStart; // length of all other blocks together
153 ptr = (uint8_t*)realloc(ptr,hdr->HeadLen); // total file length
154
155 curSectLen = 16; // current section length
156 sectionStart = 6;
157
158 curSectLen += NSections*10;
159 }
160 else if (curSect==1) // SECTION 1
161 {
162 ptr = (uint8_t*)realloc(ptr,sectionStart+10000);
163 PtrCurSect = ptr+sectionStart;
164 curSectLen = 16; // current section length
165 char *nextPartOfPatientName=hdr->Patient.Name;
166
167 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 0 \n");
168
169 // Tag 0 (max len = 64)
170 if (!hdr->FLAG.ANONYMOUS && (nextPartOfPatientName != NULL) && strlen(nextPartOfPatientName))
171 {
172 *(ptr+sectionStart+curSectLen) = 0; // tag
173 len = strcspn(nextPartOfPatientName, "\x1f");
174 leu16a(len, ptr+sectionStart+curSectLen+1); // length
175 strncpy((char*)ptr+sectionStart+curSectLen+3,nextPartOfPatientName,len); // field
176 nextPartOfPatientName += len+1;
177 curSectLen += len+3;
178 }
179
180 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 1 \n");
181
182 // Tag 1 (max len = 64) Firstname
183 if (!hdr->FLAG.ANONYMOUS && (nextPartOfPatientName != NULL) && strlen(nextPartOfPatientName))
184 {
185 *(ptr+sectionStart+curSectLen) = 1; // tag
186 len = strcspn(nextPartOfPatientName, "\x1f");
187 leu16a(len, ptr+sectionStart+curSectLen+1); // length
188 strncpy((char*)ptr+sectionStart+curSectLen+3,nextPartOfPatientName,len); // field
189 nextPartOfPatientName += len+1;
190 curSectLen += len+3;
191 }
192
193 // Tag 2 (max len = 64) Patient ID
194 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 2 \n");
195
196 // if (hdr->Patient.Id != NULL) {
197 if (strlen(hdr->Patient.Id)>0) {
198 *(ptr+sectionStart+curSectLen) = 2; // tag
199 len = strlen(hdr->Patient.Id) + 1;
200 leu16a(len, ptr+sectionStart+curSectLen+1); // length
201 strncpy((char*)ptr+sectionStart+curSectLen+3,hdr->Patient.Id,len); // field
202 curSectLen += len+3;
203 }
204
205 // Tag 3 (max len = 64) Second Last Name
206 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 3 \n");
207
208 if (!hdr->FLAG.ANONYMOUS && (nextPartOfPatientName != NULL) && strlen(nextPartOfPatientName))
209 {
210 *(ptr+sectionStart+curSectLen) = 3; // tag
211 len = strcspn(nextPartOfPatientName, "\x1f");
212 leu16a(len, ptr+sectionStart+curSectLen+1); // length
213 strncpy((char*)ptr+sectionStart+curSectLen+3,nextPartOfPatientName,len); // field
214 nextPartOfPatientName += len+1;
215 curSectLen += len+3;
216 }
217
218 // Tag 5 (len = 4)
219 if ((hdr->Patient.Birthday) > 0) {
220 T0_tm = gdf_time2tm_time(hdr->Patient.Birthday);
221
222 *(ptr+sectionStart+curSectLen) = 5; // tag
223 leu16a(4, ptr+sectionStart+curSectLen+1); // length
224 leu16a(T0_tm->tm_year+1900, ptr+sectionStart+curSectLen+3);// year
225 *(ptr+sectionStart+curSectLen+5) = (uint8_t)(T0_tm->tm_mon + 1); // month
226 *(ptr+sectionStart+curSectLen+6) = (uint8_t)(T0_tm->tm_mday); // day
227 curSectLen += 7;
228 }
229
230 // Tag 6 (len = 3) Height
231 if (hdr->Patient.Height>0.0) {
232 *(ptr+sectionStart+curSectLen) = 6; // tag
233 leu16a(3, ptr+sectionStart+curSectLen+1); // length
234 leu16a(hdr->Patient.Height, ptr+sectionStart+curSectLen+3); // value
235 *(ptr+sectionStart+curSectLen+5) = 1; // cm
236 curSectLen += 6;
237 }
238
239 // Tag 7 (len = 3) Weight
240 if (hdr->Patient.Weight>0.0) {
241 *(ptr+sectionStart+curSectLen) = 7; // tag
242 leu16a(3, ptr+sectionStart+curSectLen+1); // length
243 leu16a(hdr->Patient.Weight, ptr+sectionStart+curSectLen+3); // value
244 *(ptr+sectionStart+curSectLen+5) = 1; // kg
245 curSectLen += 6;
246 }
247
248 // Tag 8 (len = 1)
249 if (hdr->Patient.Sex != 0) {
250 *(ptr+sectionStart+curSectLen) = 8; // tag
251 leu16a(1, ptr+sectionStart+curSectLen+1); // length
252 *(ptr+sectionStart+curSectLen+3) = hdr->Patient.Sex; // value
253 curSectLen += 4;
254 }
255
256 // Tag 11 (len = 2)
257 if (aECG->systolicBloodPressure>0.0) {
258 *(ptr+sectionStart+curSectLen) = 11; // tag
259 leu16a(2, ptr+sectionStart+curSectLen+1); // length
260 leu16a((uint16_t)aECG->systolicBloodPressure, ptr+sectionStart+curSectLen+3); // value
261 curSectLen += 5;
262 };
263
264 // Tag 12 (len = 2)
265 if (aECG->diastolicBloodPressure>0.0) {
266 *(ptr+sectionStart+curSectLen) = 12; // tag
267 leu16a(2, ptr+sectionStart+curSectLen+1); // length
268 leu16a((uint16_t)aECG->diastolicBloodPressure, ptr+sectionStart+curSectLen+3); // value
269 curSectLen += 5;
270 };
271 // Tag 13 (max len = 80)
272 aECG->Diagnosis="";
273 len = strlen(aECG->Diagnosis);
274 if (len>0) {
275 *(ptr+sectionStart+curSectLen) = 13; // tag
276 len = min(64,len+1);
277 leu16a(len, ptr+sectionStart+curSectLen+1); // length
278 strncpy((char*)(ptr+sectionStart+curSectLen+3),aECG->Diagnosis,len);
279 curSectLen += 3+len;
280 };
281
282 // Tag 14 (max len = 2 + 2 + 2 + 1 + 1 + 6 + 1 + 1 + 1 + 1 + 1 + 16 + 1 + 25 + 25 + 25 + 25 + 25)
283 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 14 \n");
284
285 // Total = 161 (max value)
286 *(ptr+sectionStart+curSectLen) = 14; // tag
287 //len = 41; // minimum length
288 // leu16a(len, ptr+sectionStart+curSectLen+1); // length
289 memset(ptr+sectionStart+curSectLen+3,0,41); // dummy value
290
291 curSectLen += 3;
292 leu16a(aECG->Section1.Tag14.INST_NUMBER, ptr+sectionStart+curSectLen);
293 leu16a(aECG->Section1.Tag14.DEPT_NUMBER, ptr+sectionStart+curSectLen+2);
294 leu16a(aECG->Section1.Tag14.DEVICE_ID, ptr+sectionStart+curSectLen+4);
295 *(ptr+sectionStart+curSectLen+ 6) = aECG->Section1.Tag14.DeviceType;
296 *(ptr+sectionStart+curSectLen+ 7) = aECG->Section1.Tag14.MANUF_CODE; // tag 14, byte 7 (MANUF_CODE has to be 255)
297 strncpy((char*)(ptr+sectionStart+curSectLen+8), aECG->Section1.Tag14.MOD_DESC, 6); // tag 14, byte 7 (MOD_DESC has to be "Cart1")
298 *(ptr+sectionStart+curSectLen+14) = versionSection; // tag 14, byte 14 (VERSION has to be 20)
299 *(ptr+sectionStart+curSectLen+14) = aECG->Section1.Tag14.VERSION;
300 *(ptr+sectionStart+curSectLen+15) = aECG->Section1.Tag14.PROT_COMP_LEVEL; // tag 14, byte 15 (PROT_COMP_LEVEL has to be 0xA0 => level II)
301 *(ptr+sectionStart+curSectLen+16) = aECG->Section1.Tag14.LANG_SUPP_CODE; // tag 14, byte 16 (LANG_SUPP_CODE has to be 0x00 => Ascii only, latin and 1-byte code)
302 *(ptr+sectionStart+curSectLen+17) = aECG->Section1.Tag14.ECG_CAP_DEV; // tag 14, byte 17 (ECG_CAP_DEV has to be 0xD0 => Acquire, (No Analysis), Print and Store)
303 *(ptr+sectionStart+curSectLen+18) = aECG->Section1.Tag14.MAINS_FREQ; // tag 14, byte 18 (MAINS_FREQ has to be 0: unspecified, 1: 50 Hz, 2: 60Hz)
304 *(ptr+sectionStart+curSectLen+35) = strlen(aECG->Section1.Tag14.ANAL_PROG_REV_NUM)+1; // tag 14, byte 34 => length of ANAL_PROG_REV_NUM + 1 = 1
305 uint16_t len1 = 36;
306
307 char* tmp;
308 tmp = aECG->Section1.Tag14.ANAL_PROG_REV_NUM;
309 len = min(25, strlen(tmp) + 1);
310 strncpy((char*)(ptr+sectionStart+curSectLen+len1), tmp, len);
311 len1 += len;
312
313 tmp = aECG->Section1.Tag14.SERIAL_NUMBER_ACQ_DEV;
314 len = min(25, strlen(tmp) + 1);
315 strncpy((char*)(ptr+sectionStart+curSectLen+len1), tmp, len);
316 len1 += len;
317
318 tmp = aECG->Section1.Tag14.ACQ_DEV_SYS_SW_ID;
319 len = min(25, strlen(tmp) + 1);
320 strncpy((char*)(ptr+sectionStart+curSectLen+len1), tmp, len);
321 len1 += len;
322
323 tmp = aECG->Section1.Tag14.ACQ_DEV_SCP_SW;
324 len = min(25, strlen(tmp) + 1);
325 strncpy((char*)(ptr+sectionStart+curSectLen+len1), tmp, len);
326 len1 += len;
327
328 tmp = aECG->Section1.Tag14.ACQ_DEV_MANUF;
329 len = min(25, strlen(tmp) + 1);
330 strncpy((char*)(ptr+sectionStart+curSectLen+len1), tmp, len);
331 len1 += len;
332
333 leu16a(len1, ptr+sectionStart+curSectLen+1-3); // length
334 curSectLen += len1;
335
336 // Tag 16 (max len = 80)
337 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 16 \n");
338 len = hdr->ID.Hospital ? strlen(hdr->ID.Hospital) : 0;
339 if (len > 0) {
340 *(ptr+sectionStart+curSectLen) = 16; // tag
341 len = min(64,len+1);
342 leu16a(len, ptr+sectionStart+curSectLen+1); // length
343 strncpy((char*)(ptr+sectionStart+curSectLen+3),hdr->ID.Hospital,len);
344 curSectLen += 3+len;
345 }
346
347 // Tag 20 (max len = 64 )
348 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 20 \n");
349 len = aECG->ReferringPhysician ? strlen(aECG->ReferringPhysician) : 0;
350 if (len > 0) {
351 *(ptr+sectionStart+curSectLen) = 20; // tag
352 len = min(64,len+1);
353 leu16a(len, ptr+sectionStart+curSectLen+1); // length
354 strncpy((char*)(ptr+sectionStart+curSectLen+3),aECG->ReferringPhysician,len);
355 curSectLen += 3+len;
356 };
357
358 // Tag 21 (max len = 64 )
359 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 21 \n");
360 len = aECG->MedicationDrugs ? strlen(aECG->MedicationDrugs) : 0;
361 if (len>0) {
362 *(ptr+sectionStart+curSectLen) = 21; // tag
363 len = min(64,len+1);
364 leu16a(len, ptr+sectionStart+curSectLen+1); // length
365 strncpy((char*)(ptr+sectionStart+curSectLen+3),aECG->MedicationDrugs,len);
366 curSectLen += 3+len;
367 };
368
369 // Tag 22 (max len = 40 )
370 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 22 \n");
371 len = hdr->ID.Technician ? strlen(hdr->ID.Technician) : 0;
372 if (len > 0) {
373 *(ptr+sectionStart+curSectLen) = 22; // tag
374 len = min(64,len+1);
375 leu16a(len, ptr+sectionStart+curSectLen+1); // length
376 strncpy((char*)(ptr+sectionStart+curSectLen+3),hdr->ID.Technician,len);
377 curSectLen += 3+len;
378 }
379
380 // Tag 24 ( len = 1 )
381 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 24 \n");
382 *(ptr+sectionStart+curSectLen) = 24; // tag
383 leu16a(1, ptr+sectionStart+curSectLen+1); // length
384 *(ptr+sectionStart+curSectLen+3) = aECG->EmergencyLevel;
385 curSectLen += 4;
386
387 // Tag 25 (len = 4)
388 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 25 \n");
389
390 gdf_time T1 = hdr->T0;
391 T0_tm = gdf_time2tm_time(hdr->T0);
392 T0_tm->tm_gmtoff = hdr->tzmin*60;
393
394 *(ptr+sectionStart+curSectLen) = 25; // tag
395 leu16a(4, ptr+sectionStart+curSectLen+1); // length
396 leu16a(T0_tm->tm_year+1900, ptr+sectionStart+curSectLen+3);// year
397 *(ptr+sectionStart+curSectLen+5) = (uint8_t)(T0_tm->tm_mon + 1);// month
398 *(ptr+sectionStart+curSectLen+6) = (uint8_t)T0_tm->tm_mday; // day
399 curSectLen += 7;
400
401 // Tag 26 (len = 3)
402 *(ptr+sectionStart+curSectLen) = 26; // tag
403 leu16a(3, ptr+sectionStart+curSectLen+1); // length
404 *(ptr+sectionStart+curSectLen+3) = (uint8_t)T0_tm->tm_hour; // hour
405 *(ptr+sectionStart+curSectLen+4) = (uint8_t)T0_tm->tm_min; // minute
406 *(ptr+sectionStart+curSectLen+5) = (uint8_t)T0_tm->tm_sec; // second
407 curSectLen += 6;
408
409 if (NS>0) {
410 CHANNEL_TYPE *CH = hdr->CHANNEL;
411 while (CH->OnOff != 1) CH++;
412
413 // Tag 27 (len = 3) highpass filter
414 *(ptr+sectionStart+curSectLen) = 27; // tag
415 leu16a(2, ptr+sectionStart+curSectLen+1); // length
416 leu16a((uint16_t)CH->HighPass, ptr+sectionStart+curSectLen+3); // hour
417 curSectLen += 5;
418
419 // Tag 28 (len = 3) lowpass filter
420 *(ptr+sectionStart+curSectLen) = 28; // tag
421 leu16a(2, ptr+sectionStart+curSectLen+1); // length
422 leu16a((uint16_t)CH->LowPass, ptr+sectionStart+curSectLen+3); // hour
423 curSectLen += 5;
424
425 // Tag 29 (len = 1) filter bitmap
426 uint8_t bitmap = 0;
427 if (fabs(CH->LowPass-60.0)<0.01)
428 bitmap = 1;
429 else if (fabs(CH->LowPass-50.0)<0.01)
430 bitmap = 2;
431 else
432 bitmap = 0;
433 *(ptr+sectionStart+curSectLen) = 29; // tag
434 leu16a(1, ptr+sectionStart+curSectLen+1); // length
435 *(ptr+sectionStart+curSectLen+3) = bitmap;
436 curSectLen += 4;
437
438 }
439
440 // Tag 32 (len = 5)
441 if (VERBOSE_LEVEL>7) fprintf(stdout,"Section 1 Tag 32 \n");
442
443 *(ptr+sectionStart+curSectLen) = 32; // tag
444 leu16a(2, ptr+sectionStart+curSectLen+1); // length
445 if (hdr->Patient.Impairment.Heart==1) {
446 *(ptr+sectionStart+curSectLen+3) = 0;
447 *(ptr+sectionStart+curSectLen+4) = 1; // Apparently healthy
448 curSectLen += 5;
449 }
450 else if (hdr->Patient.Impairment.Heart==3) {
451 *(ptr+sectionStart+curSectLen+3) = 0;
452 *(ptr+sectionStart+curSectLen+4) = 42; // Implanted cardiac pacemaker
453 curSectLen += 5;
454 }
455
456 // Tag 34 (len = 5)
457 *(ptr+sectionStart+curSectLen) = 34; // tag
458 leu16a(5, ptr+sectionStart+curSectLen+1); // length
459 lei16a(hdr->tzmin, ptr+sectionStart+curSectLen+3);
460 lei16a(0, ptr+sectionStart+curSectLen+5);
461 curSectLen += 8;
462
463 // Tag 255 (len = 0)
464 *(ptr+sectionStart+curSectLen) = 255; // tag
465 leu16a(0, ptr+sectionStart+curSectLen+1); // length
466 curSectLen += 3;
467
468 // Evaluate the size and correct it if odd
469 if (curSectLen & 1) {
470 *(ptr+sectionStart+curSectLen++) = 0;
471 }
472
473 if (VERBOSE_LEVEL>7) fprintf(stdout,"End-of-Section %i %p\n",curSect,ptr);
474
475 }
476 else if (curSect==2) // SECTION 2
477 {
478 }
479 else if (curSect==3) // SECTION 3
480 {
481 ptr = (uint8_t*)realloc(ptr,sectionStart+16+2+9*NS+1);
482 PtrCurSect = ptr+sectionStart;
483 curSectLen = 16; // current section length
484
485 // Number of leads enclosed
486 *(ptr+sectionStart+curSectLen++) = NS;
487
488 // ### Situations with reference beat subtraction are not supported
489 // Situations with not all the leads simultaneously recorded are not supported
490 // Situations number of leads simultaneouly recorded != total number of leads are not supported
491 // We assume all the leads are recorded simultaneously
492 *(ptr+sectionStart+curSectLen++) = (NS<<3) | 0x04;
493
494 for (i = 0; i < hdr->NS; i++) {
495 CHANNEL_TYPE *CH=hdr->CHANNEL+i;
496 if (CH->OnOff != 1) continue;
497
498 leu32a(1L, ptr+sectionStart+curSectLen);
499 leu32a(hdr->data.size[0], ptr+sectionStart+curSectLen+4);
500 *(ptr+sectionStart+curSectLen+8) = (uint8_t)CH->LeadIdCode;
501 curSectLen += 9;
502 }
503
504 // Evaluate the size and correct it if odd
505 if ((curSectLen % 2) != 0) {
506 *(ptr+sectionStart+curSectLen++) = 0;
507 }
508 memset(ptr+sectionStart+10,0,6); // reserved
509 }
510 else if (curSect==4) // SECTION 4
511 {
512 }
513 else if (curSect==5) // SECTION 5
514 {
515 curSectLen = 0; // current section length
516 aECG->Section5.StartPtr = sectionStart;
517 aECG->Section5.Length = curSectLen;
518 }
519 else if (curSect==6) // SECTION 6
520 {
521 if (versionSection < 25) // SECTION 6
522 {
523 uint16_t GDFTYP = 3;
524 size_t SZ = GDFTYP_BITS[GDFTYP]>>3;
525 typeof(hdr->NS) i=0;
526 for (i = 0; i < hdr->NS; i++) {
527 CHANNEL_TYPE *hc=hdr->CHANNEL+i;
528 if (hc->OnOff != 1) continue;
529 hc->GDFTYP = GDFTYP;
530 }
531 ptr = (uint8_t*)realloc(ptr,sectionStart+16+6+2*NS+SZ*(hdr->data.size[0]*hdr->data.size[1]));
532
533 PtrCurSect = ptr+sectionStart;
534 curSectLen = 16; // current section length
535
536 // Create all the fields
537 char flagfirst = 1;
538 for (i = 0; i < hdr->NS; i++) {
539 CHANNEL_TYPE *CH=hdr->CHANNEL+i;
540 if (CH->OnOff != 1) continue;
541
542 // check for physical dimension and adjust scaling factor to "nV"
543 avm = CH->Cal * 1e9 * PhysDimScale(CH->PhysDimCode);
544 if (flagfirst) {
545 // Amplitude Value Multiplier (AVM)
546 AVM = avm;
547 flagfirst=0;
548 continue;
549 }
550
551 // check whether all channels have the same scaling factor
552 if (fabs((AVM - avm)/AVM) > 1e-14)
553 fprintf(stderr,"Warning SOPEN (SCP-WRITE): scaling factors differ between channel #1 and #%i. Scaling factor of 1st channel is used.\n",i+1);
554 };
555 avm16 = lrint(AVM);
556 leu16a(avm16, ptr+sectionStart+curSectLen);
557 avm = leu16p(ptr+sectionStart+curSectLen);
558 curSectLen += 2;
559 if (fabs((AVM - avm)/AVM)>1e-14)
560 fprintf(stderr,"Warning SOPEN (SCP-WRITE): Scaling factor has been truncated (%f instead %f).\n",avm,AVM);
561
562 // Sample interval
563 AVM = 1e6/hdr->SampleRate;
564 avm16 = lrint(AVM);
565 leu16a(avm16, ptr+sectionStart+curSectLen);
566 avm = leu16p(ptr+sectionStart+curSectLen);
567 curSectLen += 2;
568 if (fabs((AVM - avm)/AVM)>1e-14)
569 fprintf(stderr,"Warning SOPEN (SCP-WRITE): Sampling interval has been truncated (%f instead %f us).\n",avm,AVM);
570
571 // Diff used
572 *(ptr+sectionStart+curSectLen++) = 0;
573
574 // Bimodal/Non-bimodal
575 *(ptr+sectionStart+curSectLen++) = 0;
576
577
578 /* DATA COMPRESSION
579 currently, no compression method is supported. In case of data compression, the
580 data compression can happen here.
581 */
582
583 // Fill the length block
584 for (i = 0; i < NS; i++) {
585 leu16a((uint16_t)hdr->data.size[0]*2, ptr+sectionStart+curSectLen);
586 avm = leu16p(ptr+sectionStart+curSectLen);
587 AVM = hdr->data.size[0]*2;
588 if (fabs((AVM - avm)/AVM)>1e-14)
589 fprintf(stderr,"Warning SOPEN (SCP-WRITE): Block length truncated (%f instead %f us).\n",avm,AVM);
590 curSectLen += 2;
591 }
592
593 /* data in channel multiplexed order */
594 for (i = 0; i < hdr->NS; i++) {
595 hdr->CHANNEL[i].SPR *= hdr->NRec;
596 };
597 hdr->NRec = 1;
598
599 // Prepare filling the data block with the ECG samples by SWRITE
600 curSectLen += SZ * (hdr->data.size[hdr->FLAG.ROW_BASED_CHANNELS] * NS);
601
602 // Evaluate the size and correct it if odd
603 if ((curSectLen % 2) != 0) {
604 fprintf(stderr,"Warning Section 6 has an odd length\n");
605 *(ptr+sectionStart+curSectLen++) = 0;
606 }
607
608 memset(ptr+sectionStart+10,0,6); // reserved
609 aECG->Section6.StartPtr = sectionStart;
610 aECG->Section6.Length = curSectLen;
611 }
612 }
613 else if (curSect==7) // SECTION 7
614 {
615 if (hdr->SCP.Section7 != NULL) {
616 curSectLen = hdr->SCP.Section7Length+16; // current section length
617 ptr = (uint8_t*)realloc(ptr,sectionStart+curSectLen);
618 PtrCurSect = ptr+sectionStart;
619 memcpy(PtrCurSect+16,hdr->SCP.Section7,hdr->SCP.Section7Length);
620 }
621 }
622 else if (curSect==8) // SECTION 8
623 {
624 if (hdr->SCP.Section8 != NULL) {
625 curSectLen = hdr->SCP.Section8Length+16; // current section length
626 ptr = (uint8_t*)realloc(ptr,sectionStart+curSectLen);
627 PtrCurSect = ptr+sectionStart;
628 memcpy(PtrCurSect,hdr->SCP.Section8,hdr->SCP.Section8Length);
629 }
630 }
631 else if (curSect==9) // SECTION 9
632 {
633 if (hdr->SCP.Section9 != NULL) {
634 curSectLen = hdr->SCP.Section9Length+16; // current section length
635 ptr = (uint8_t*)realloc(ptr,sectionStart+curSectLen);
636 PtrCurSect = ptr+sectionStart;
637 memcpy(PtrCurSect,hdr->SCP.Section9,hdr->SCP.Section9Length);
638 }
639 }
640 else if (curSect==10) // SECTION 10
641 {
642 if (hdr->SCP.Section10 != NULL) {
643 curSectLen = hdr->SCP.Section10Length+16; // current section length
644 ptr = (uint8_t*)realloc(ptr,sectionStart+curSectLen);
645 PtrCurSect = ptr+sectionStart;
646 memcpy(PtrCurSect+16,hdr->SCP.Section10,hdr->SCP.Section10Length);
647 }
648 }
649 else if (curSect==11) // SECTION 11
650 {
651 if (hdr->SCP.Section11 != NULL) {
652 curSectLen = hdr->SCP.Section11Length+16; // current section length
653 ptr = (uint8_t*)realloc(ptr,sectionStart+curSectLen);
654 PtrCurSect = ptr+sectionStart;
655 memcpy(PtrCurSect+16,hdr->SCP.Section11,hdr->SCP.Section11Length);
656 }
657 }
658 else if (curSect==12) // SECTION 12
659 {
660 if (versionSection > 25 ) // SECTION 12, SCP version 3
661 {
662 uint16_t gdftyp= 0;
663 uint8_t bps = 0;
664 //double PhysMax = -1.0/0.0;
665 uint16_t PhysDimBaseCode = 4256; // Volt
666 for (k = 0; k < hdr->NS; k++) {
667 CHANNEL_TYPE *hc = hdr->CHANNEL+k;
668 if (hc->OnOff == 1) {
669 if (bps < GDFTYP_BITS[gdftyp]) {
670 bps = GDFTYP_BITS[hc->GDFTYP];
671 gdftyp = hc->GDFTYP;
672 }
673 }
674 }
675
676 if (NS>255) {
677 NS=255;
678 fprintf(stderr,"Warning SOPEN (SCP-WRITE): Number of channels exceeds 255, limited to 255.\n");
679 }
680
681 bps = GDFTYP_BITS[gdftyp]>>3;
682 double DigMax = ldexp(1.0, GDFTYP_BITS[gdftyp]-1)-1;
683
684 int ns=0;
685 for (k=0; k < hdr->NS; k++) {
686 CHANNEL_TYPE *hc = hdr->CHANNEL+k;
687 if (hc->OnOff > 0) {
688 if (ns > 255) {
689 hc->OnOff = 0;
690 continue;
691 }
692 ns++;
693
694 double scale = PhysDimScale(hc->PhysDimCode);
695 hc->GDFTYP = gdftyp;
696 hc->PhysMax = max(fabs(hc->PhysMax),fabs(-hc->PhysMin))*scale*1e9;
697 hc->PhysMin = -hc->PhysMax;
698 hc->Off = 0.0;
699 hc->Cal = ceil(hc->PhysMax / hc->DigMax); // AVM must be integer
700 //hc->DigMax = hc->PhysMax / hc->Cal;
701 hc->DigMin = -hc->DigMax;
702 hc->PhysDimCode = 4276; // nV
703 }
704 }
705
706 curSectLen = 16 + 70 + NS * (4 + hdr->SPR*hdr->NRec * bps); // current section length without 16 bytes
707 curSectLen+= (curSectLen & 1);
708
709 ptr = (uint8_t*)realloc(ptr, sectionStart + curSectLen);
710 memset(ptr + sectionStart, 0, 16+70);
711
712 PtrCurSect = ptr + sectionStart + 16;
713 leu32a((uint32_t)hdr->SampleRate, PtrCurSect);
714 PtrCurSect[4] = (uint8_t)NS;
715 leu32a((uint32_t)(hdr->NRec*hdr->SPR), PtrCurSect + 5);
716 PtrCurSect[9] = (uint8_t)bps;
717
718 // Recording Date and Time
719 gdf_time T1 = hdr->T0;
720 T0_tm = gdf_time2tm_time(hdr->T0);
721 T0_tm->tm_gmtoff = hdr->tzmin*60;
722
723 leu16a(T0_tm->tm_year+1900, PtrCurSect+10); // year
724 PtrCurSect[12] = (uint8_t)(T0_tm->tm_mon + 1); // month
725 PtrCurSect[13] = (uint8_t)T0_tm->tm_mday; // day
726 PtrCurSect[14] = (uint8_t)T0_tm->tm_hour; // hour
727 PtrCurSect[15] = (uint8_t)T0_tm->tm_min; // minute
728 PtrCurSect[16] = (uint8_t)T0_tm->tm_sec; // second
729 // all other fields are set to zero. filter settings are defined in Section 1, Tags 27-29
730
731 /* Leads Definistion block */
732 PtrCurSect += 70;
733 for (k=0,ns=0; k < hdr->NS; k++) {
734 CHANNEL_TYPE *hc = hdr->CHANNEL+k;
735 if (hc->OnOff > 0) {
736 PtrCurSect[ns*4] = hc->LeadIdCode;
737 leu16a(hc->Cal, PtrCurSect+1+ns*4); // AVM per lead
738 PtrCurSect[3+ns*4] = 0;
739 ns++;
740 }
741 }
742
743 /* ECG signals data block */
744 PtrCurSect += 4*NS;
745 }
746 aECG->Section12.StartPtr = sectionStart;
747 aECG->Section12.Length = curSectLen;
748 }
749 else {
750 }
751
752 // write to pointer field in Section 0
753 leu16a(curSect, ptr+curSect*10+6+16); //
754 leu32a(curSectLen, ptr+curSect*10+6+16+2); // length
755 // Section start - must be odd. See EN1064:2005(E) Section 5.2.1
756
757 // write to Section ID Header
758 if (curSectLen>0) {
759 // Section 0: startpos in pointer field
760 leu32a(sectionStart+1, ptr+curSect*10+6+16+6);
761
762 // Section ID header (16 bytes)
763 leu16a(curSect, ptr+sectionStart+2); // Section ID
764 leu32a(curSectLen, ptr+sectionStart+4); // section length->section header
765 ptr[sectionStart+8] = versionSection; // Section Version Number
766 ptr[sectionStart+9] = versionProtocol; // Protocol Version Number
767 if (curSect==0) {
768 memcpy(ptr+16,"SCPECG",6); // defined as in ISO/DIS 11073-91064 Section 5.3.2
769 }
770 else {
771 memset(ptr+sectionStart+10,0,6); // reserved according to ISO/DIS 11073-91064 Section 5.2.7
772 }
773 crc = CRCEvaluate(ptr+sectionStart+2,curSectLen-2); // compute CRC
774 leu16a(crc, ptr+sectionStart);
775 }
776 sectionStart += curSectLen; // offset for next section
777 }
778
779 if (VERBOSE_LEVEL>7) fprintf(stdout,"SOPEN_SCP_WRITE 300\n");
780
781 // Prepare filling the data block with the ECG samples by SWRITE
782 if (versionSection < 25)
783 hdr->AS.rawdata = ptr+aECG->Section6.StartPtr+16+6+2*NS;
784 else
785 hdr->AS.rawdata = ptr+aECG->Section12.StartPtr+16+70+4*NS;
786
787 hdr->AS.Header = ptr;
788
789 if (VERBOSE_LEVEL>7) fprintf(stdout,"%s (line %i): %i,%i %i,%i \n",__func__,__LINE__,(int)aECG->Section6.StartPtr,(int)aECG->Section6.Length,(int)aECG->Section12.StartPtr,(int)aECG->Section12.Length);
790
791 return(0);
792 }
793
794 #ifdef __cplusplus
795 }
796 #endif
797
798