edf-header-visualization
Version:
Interactively visualize the structure of the static and dynamic header of an EDF file
375 lines (346 loc) • 16.2 kB
JavaScript
import React, { useMemo, useState } from 'react';
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArrayLimit(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
}
/*
== EDF Structure ==
STATIC HEADER RECORD
--------------------
8 ascii : version of this data format (0)
80 ascii : local patient identification
80 ascii : local recording identification
8 ascii : startdate of recording (dd.mm.yy)
8 ascii : starttime of recording (hh.mm.ss)
8 ascii : number of bytes in header record
44 ascii : reserved
8 ascii : number of data records
8 ascii : duration of a data record, in seconds
4 ascii : number of signals (ns) in data record
DYNAMIC HEADER RECORD
---------------------
ns * 16 ascii : ns * label (e.g. EEG Fpz-Cz or Body temp)
ns * 80 ascii : ns * transducer type (e.g. AgAgCl electrode)
ns * 8 ascii : ns * physical dimension (e.g. uV or degreeC)
ns * 8 ascii : ns * physical minimum (e.g. -500 or 34)
ns * 8 ascii : ns * physical maximum (e.g. 500 or 40)
ns * 8 ascii : ns * digital minimum (e.g. -2048)
ns * 8 ascii : ns * digital maximum (e.g. 2047)
ns * 80 ascii : ns * prefiltering (e.g. HP:0.1Hz LP:75Hz)
ns * 8 ascii : ns * nr of samples in each data record
ns * 32 ascii : ns * reserved
DATA RECORD
-----------
nr of samples[1] * integer : first signal in the data record
nr of samples[2] * integer : second signal
..
nr of samples[ns] * integer : last signal
*/
var staticFields = [
/* eslint-disable no-multi-spaces, key-spacing */
{
name: 'version',
size: 8,
description: 'version of this data format'
}, {
name: 'patientIdentification',
size: 80,
description: 'local patient identification'
}, {
name: 'recordIdentification',
size: 80,
description: 'local recording identification'
}, {
name: 'startDate',
size: 8,
description: 'startdate of recording (dd.mm.yy)'
}, {
name: 'startTime',
size: 8,
description: 'starttime of recording (hh.mm.ss)'
}, {
name: 'recordHeaderByteSize',
size: 8,
description: 'number of bytes in header record'
}, {
name: 'staticReserved',
size: 44,
description: 'reserved'
}, {
name: 'numberOfDataRecords',
size: 8,
description: 'number of data records'
}, {
name: 'recordDurationTime',
size: 8,
description: 'duration of a data record (seconds)'
}, {
name: 'numberOfSignals',
size: 4,
description: 'number of signals in data record'
}];
/* eslint-enable no-multi-spaces, key-spacing */
var dynamicFields = [
/* eslint-disable no-multi-spaces, key-spacing */
{
name: 'label',
size: 16,
description: 'label'
}, {
name: 'transducerType',
size: 80,
description: 'transducer type'
}, {
name: 'physicalDimension',
size: 8,
description: 'physical dimension'
}, {
name: 'physicalMinimum',
size: 8,
description: 'physical minimum'
}, {
name: 'physicalMaximum',
size: 8,
description: 'physical maximum'
}, {
name: 'digitalMinimum',
size: 8,
description: 'digital minimum'
}, {
name: 'digitalMaximum',
size: 8,
description: 'digital maximum'
}, {
name: 'preFiltering',
size: 80,
description: 'prefiltering'
}, {
name: 'numberOfSamples',
size: 8,
description: 'number of samples in each data record'
}, {
name: 'dynamicReserved',
size: 32,
description: 'reserved'
}];
/* eslint-enable no-multi-spaces, key-spacing */
function parseHeader(header) {
var numberOfSignals = +header.substr(252, 4);
var color = 0;
var index = 0;
var staticHeader = staticFields.map(function (_ref) {
var name = _ref.name,
size = _ref.size;
var values = header.slice(index, index + size).split('');
index += size;
color = (color + 1) % 10;
return {
name: name,
values: values,
color: color
};
});
var dynamicHeader = [];
dynamicFields.forEach(function (_ref2) {
var name = _ref2.name,
size = _ref2.size;
for (var channel = 1; channel <= numberOfSignals; channel++) {
// starts with 1
var values = header.slice(index, index + size).split('');
index += size;
color = (channel - 1) % Math.min(numberOfSignals, 10);
dynamicHeader.push({
name: name,
channel: "channel-".concat(channel),
values: values,
color: color
});
}
});
return {
staticHeader: staticHeader,
dynamicHeader: dynamicHeader
};
}
var Header = React.memo(function (_ref) {
var groups = _ref.groups,
name = _ref.name,
setHoveredItem = _ref.setHoveredItem;
return React.createElement("div", {
className: "header ".concat(name)
}, groups.map(function (_ref2) {
var _ref2$name = _ref2.name,
name = _ref2$name === void 0 ? '' : _ref2$name,
_ref2$channel = _ref2.channel,
channel = _ref2$channel === void 0 ? '' : _ref2$channel,
_ref2$color = _ref2.color,
color = _ref2$color === void 0 ? 1 : _ref2$color,
_ref2$values = _ref2.values,
values = _ref2$values === void 0 ? [] : _ref2$values;
return React.createElement("div", {
key: "".concat(name, "-").concat(channel),
className: "group ".concat(name, " ").concat(channel, " c").concat(color),
onMouseOver: function onMouseOver() {
return setHoveredItem("".concat(name, " ").concat(channel));
}
}, values.map(function (char, i) {
return React.createElement("code", {
key: i
}, char);
}));
}));
});
var EdfHeaderGrid = function EdfHeaderGrid(_ref3) {
var edfHeader = _ref3.edfHeader,
hoveredItem = _ref3.hoveredItem,
setHoveredItem = _ref3.setHoveredItem;
var parsedHeader = useMemo(function () {
return parseHeader(edfHeader);
}, [edfHeader]);
return React.createElement("div", {
className: "edf-grid ".concat(hoveredItem),
onMouseLeave: function onMouseLeave() {
return setHoveredItem('NONE');
}
}, React.createElement(Header, {
groups: parsedHeader.staticHeader,
name: "static-header",
setHoveredItem: setHoveredItem
}), React.createElement(Header, {
groups: parsedHeader.dynamicHeader,
name: "dynamic-header",
setHoveredItem: setHoveredItem
}));
};
var getChannelNames = function getChannelNames(edfHeader) {
var numberOfSignals = +edfHeader.substr(252, 4);
return Array.from(new Array(numberOfSignals)).map(function (val, index) {
return "channel-".concat(index + 1);
});
};
function EdfHeaderLegend(_ref) {
var edfHeader = _ref.edfHeader,
hoveredItem = _ref.hoveredItem,
setHoveredItem = _ref.setHoveredItem;
var channelNames = useMemo(function () {
return getChannelNames(edfHeader);
}, [edfHeader]);
return React.createElement("div", {
className: "edf-legend edf-flex ".concat(hoveredItem),
onMouseLeave: function onMouseLeave() {
return setHoveredItem('NONE');
}
}, React.createElement("div", {
className: "container-static"
}, React.createElement("h2", {
onMouseOver: function onMouseOver() {
return setHoveredItem('static-header');
},
className: "".concat(hoveredItem.includes('static-header') ? 'active' : '')
}, "Static Header"), React.createElement("ul", null, staticFields.map(function (field) {
return React.createElement("li", {
key: field.name,
className: "".concat(field.name, " ").concat(hoveredItem.includes(field.name) ? 'active' : ''),
onMouseOver: function onMouseOver() {
return setHoveredItem(field.name);
}
}, field.description);
}))), React.createElement("div", {
className: "container-dynamic"
}, React.createElement("h2", {
onMouseOver: function onMouseOver() {
return setHoveredItem('dynamic-header');
},
className: "".concat(hoveredItem.includes('dynamic-header') ? 'active' : '')
}, "Dynamic Header"), React.createElement("div", {
className: "edf-flex"
}, React.createElement("div", null, React.createElement("h3", null, "Parts"), React.createElement("ul", null, dynamicFields.map(function (field) {
return React.createElement("li", {
key: field.name,
className: "".concat(field.name, " ").concat(hoveredItem.includes(field.name) ? 'active' : ''),
onMouseOver: function onMouseOver() {
return setHoveredItem(field.name);
}
}, field.description);
}))), React.createElement("div", null, React.createElement("h3", null, "Channels"), React.createElement("ul", {
className: "colorable"
}, channelNames.map(function (name, index) {
return React.createElement("li", {
key: name,
className: "".concat(name, " ").concat(hoveredItem.includes(name) ? 'active' : '', " c").concat(index % Math.min(channelNames.length, 10)),
onMouseOver: function onMouseOver() {
return setHoveredItem(name);
}
}, name, " ", React.createElement("code", {
className: "color-bubble"
}));
}))))));
}
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css = "/* Colors */\n\n.edf-header-visualization .c0 code { background-color: #ff93a5 }\n.edf-header-visualization .c1 code { background-color: #a8ffaa }\n.edf-header-visualization .c2 code { background-color: #ffff91 }\n.edf-header-visualization .c3 code { background-color: #b4c0ff }\n.edf-header-visualization .c4 code { background-color: #ffe690 }\n.edf-header-visualization .c5 code { background-color: #bcffff }\n.edf-header-visualization .c6 code { background-color: #ffa8ff }\n.edf-header-visualization .c7 code { background-color: #ffffff }\n.edf-header-visualization .c8 code { background-color: #aafdf3 }\n.edf-header-visualization .c9 code { background-color: #c7bcb0 }\n\n/* Grid */\n\n.edf-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(1.25em, 1fr));\n grid-gap: 1px;\n grid-auto-rows: 1.25em;\n}\n\n.edf-grid .header,\n.edf-grid .group {\n display: contents;\n}\n\n.edf-grid code {\n overflow: hidden;\n font-size: .85em;\n line-height: 1.6;\n text-align: center;\n}\n\n@supports not (display: grid) {\n .edf-grid .header,\n .edf-grid .group {\n display: inline;\n }\n}\n\n@supports not (display: contents) {\n .edf-grid code {\n float: left;\n outline: 1px solid white;\n }\n}\n\n.edf-grid.version .version code,\n.edf-grid.patientIdentification .patientIdentification code,\n.edf-grid.recordIdentification .recordIdentification code,\n.edf-grid.startDate .startDate code,\n.edf-grid.startTime .startTime code,\n.edf-grid.recordHeaderByteSize .recordHeaderByteSize code,\n.edf-grid.staticReserved .staticReserved code,\n.edf-grid.numberOfDataRecords .numberOfDataRecords code,\n.edf-grid.recordDurationTime .recordDurationTime code,\n.edf-grid.numberOfSignals .numberOfSignals code,\n.edf-grid.label .label code,\n.edf-grid.transducerType .transducerType code,\n.edf-grid.physicalDimension .physicalDimension code,\n.edf-grid.physicalMinimum .physicalMinimum code,\n.edf-grid.physicalMaximum .physicalMaximum code,\n.edf-grid.digitalMinimum .digitalMinimum code,\n.edf-grid.digitalMaximum .digitalMaximum code,\n.edf-grid.preFiltering .preFiltering code,\n.edf-grid.numberOfSamples .numberOfSamples code,\n.edf-grid.dynamicReserved .dynamicReserved code {\n filter: invert(1) saturate(2);\n outline: 1px solid white;\n}\n\n.edf-grid.static-header .static-header code,\n.edf-grid.dynamic-header .dynamic-header code,\n.edf-grid.channel-1 .channel-1 code,\n.edf-grid.channel-2 .channel-2 code,\n.edf-grid.channel-3 .channel-3 code,\n.edf-grid.channel-4 .channel-4 code,\n.edf-grid.channel-5 .channel-5 code,\n.edf-grid.channel-6 .channel-6 code,\n.edf-grid.channel-7 .channel-7 code,\n.edf-grid.channel-8 .channel-8 code,\n.edf-grid.channel-9 .channel-9 code,\n.edf-grid.channel-10 .channel-10 code,\n.edf-grid.channel-11 .channel-11 code,\n.edf-grid.channel-12 .channel-12 code,\n.edf-grid.channel-13 .channel-13 code,\n.edf-grid.channel-14 .channel-14 code,\n.edf-grid.channel-15 .channel-15 code,\n.edf-grid.channel-16 .channel-16 code,\n.edf-grid.channel-17 .channel-17 code,\n.edf-grid.channel-18 .channel-18 code,\n.edf-grid.channel-19 .channel-19 code,\n.edf-grid.channel-20 .channel-20 code,\n.edf-grid.channel-21 .channel-21 code,\n.edf-grid.channel-22 .channel-22 code,\n.edf-grid.channel-23 .channel-23 code,\n.edf-grid.channel-24 .channel-24 code,\n.edf-grid.channel-25 .channel-25 code,\n.edf-grid.channel-26 .channel-26 code,\n.edf-grid.channel-27 .channel-27 code,\n.edf-grid.channel-28 .channel-28 code,\n.edf-grid.channel-29 .channel-29 code,\n.edf-grid.channel-30 .channel-30 code,\n.edf-grid.channel-31 .channel-31 code,\n.edf-grid.channel-32 .channel-32 code,\n.edf-grid.channel-33 .channel-33 code,\n.edf-grid.channel-34 .channel-34 code,\n.edf-grid.channel-35 .channel-35 code,\n.edf-grid.channel-36 .channel-36 code,\n.edf-grid.channel-37 .channel-37 code,\n.edf-grid.channel-38 .channel-38 code,\n.edf-grid.channel-39 .channel-39 code,\n.edf-grid.channel-40 .channel-40 code,\n.edf-grid.channel-41 .channel-41 code,\n.edf-grid.channel-42 .channel-42 code,\n.edf-grid.channel-43 .channel-43 code,\n.edf-grid.channel-44 .channel-44 code,\n.edf-grid.channel-45 .channel-45 code,\n.edf-grid.channel-46 .channel-46 code,\n.edf-grid.channel-47 .channel-47 code,\n.edf-grid.channel-48 .channel-48 code,\n.edf-grid.channel-49 .channel-49 code,\n.edf-grid.channel-49 .channel-50 code {\n filter: saturate(3);\n outline: 1px solid black;\n}\n\n/* Flexgrid */\n\n.edf-flex {\n margin: 0 -.5em;\n display: flex;\n}\n\n.edf-flex > * {\n flex: 1;\n margin: 0 .5em;\n}\n\n/* Legend */\n\n.edf-legend {\n margin-top: 1em;\n}\n\n.edf-legend ul {\n margin: 0;\n padding: 0;\n}\n\n.edf-legend .container-static h2 {\n margin-bottom: 1.85rem;\n}\n\n.edf-legend .container-dynamic {\n flex: 2;\n}\n\n.edf-legend h2,\n.edf-legend li {\n margin: 0;\n list-style: none;\n background: #f6f6f6;\n border: 1px solid #e5e5e5;\n padding: 2px 2px 2px 5px;\n cursor: pointer;\n}\n\n.edf-legend li {\n margin-top: -1px;\n line-height: 1.5;\n}\n\n.edf-legend h1,\n.edf-legend h2,\n.edf-legend h3 { line-height: 1; }\n.edf-legend h2 { padding: 4px 5px 5px; }\n.edf-legend h3 { margin: 6px 5px 7px; }\n\n.edf-legend .active {\n background-color: #a8ffaa;\n}\n\n.edf-legend .color-bubble {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n float: right;\n}\n";
styleInject(css);
function EdfHeaderVisualization(_ref) {
var edfHeader = _ref.edfHeader;
var _useState = useState('NONE'),
_useState2 = _slicedToArray(_useState, 2),
hoveredItem = _useState2[0],
setHoveredItem = _useState2[1];
var props = {
edfHeader: edfHeader,
hoveredItem: hoveredItem,
setHoveredItem: setHoveredItem
};
return React.createElement("div", {
className: "edf-header-visualization"
}, React.createElement(EdfHeaderGrid, props), React.createElement(EdfHeaderLegend, props));
}
export { EdfHeaderVisualization, EdfHeaderGrid, EdfHeaderLegend };
//# sourceMappingURL=index.es.js.map