UNPKG

edf-header-visualization

Version:

Interactively visualize the structure of the static and dynamic header of an EDF file

384 lines (352 loc) 16.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = require('react'); var React__default = _interopDefault(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__default.memo(function (_ref) { var groups = _ref.groups, name = _ref.name, setHoveredItem = _ref.setHoveredItem; return React__default.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__default.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__default.createElement("code", { key: i }, char); })); })); }); var EdfHeaderGrid = function EdfHeaderGrid(_ref3) { var edfHeader = _ref3.edfHeader, hoveredItem = _ref3.hoveredItem, setHoveredItem = _ref3.setHoveredItem; var parsedHeader = React.useMemo(function () { return parseHeader(edfHeader); }, [edfHeader]); return React__default.createElement("div", { className: "edf-grid ".concat(hoveredItem), onMouseLeave: function onMouseLeave() { return setHoveredItem('NONE'); } }, React__default.createElement(Header, { groups: parsedHeader.staticHeader, name: "static-header", setHoveredItem: setHoveredItem }), React__default.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 = React.useMemo(function () { return getChannelNames(edfHeader); }, [edfHeader]); return React__default.createElement("div", { className: "edf-legend edf-flex ".concat(hoveredItem), onMouseLeave: function onMouseLeave() { return setHoveredItem('NONE'); } }, React__default.createElement("div", { className: "container-static" }, React__default.createElement("h2", { onMouseOver: function onMouseOver() { return setHoveredItem('static-header'); }, className: "".concat(hoveredItem.includes('static-header') ? 'active' : '') }, "Static Header"), React__default.createElement("ul", null, staticFields.map(function (field) { return React__default.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__default.createElement("div", { className: "container-dynamic" }, React__default.createElement("h2", { onMouseOver: function onMouseOver() { return setHoveredItem('dynamic-header'); }, className: "".concat(hoveredItem.includes('dynamic-header') ? 'active' : '') }, "Dynamic Header"), React__default.createElement("div", { className: "edf-flex" }, React__default.createElement("div", null, React__default.createElement("h3", null, "Parts"), React__default.createElement("ul", null, dynamicFields.map(function (field) { return React__default.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__default.createElement("div", null, React__default.createElement("h3", null, "Channels"), React__default.createElement("ul", { className: "colorable" }, channelNames.map(function (name, index) { return React__default.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__default.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 = React.useState('NONE'), _useState2 = _slicedToArray(_useState, 2), hoveredItem = _useState2[0], setHoveredItem = _useState2[1]; var props = { edfHeader: edfHeader, hoveredItem: hoveredItem, setHoveredItem: setHoveredItem }; return React__default.createElement("div", { className: "edf-header-visualization" }, React__default.createElement(EdfHeaderGrid, props), React__default.createElement(EdfHeaderLegend, props)); } exports.EdfHeaderVisualization = EdfHeaderVisualization; exports.EdfHeaderGrid = EdfHeaderGrid; exports.EdfHeaderLegend = EdfHeaderLegend; //# sourceMappingURL=index.js.map