Skip to content

Commit 57aa485

Browse files
authored
[EEG Browser] Signal Visualization, Events, Electrode map and EEG split files support (#7387)
This adds dynamic visualization of EEG based on a previous project started by Armin Taheri (https://github.com/aces/react-series-data-viewer). These changes and include cleanup of older code in the Electrophysiology Browser and new features like viewing the list of Events, and an Electrode Map dynamically plotted from the BIDS-compliant coordinates file.
1 parent 72bd9a0 commit 57aa485

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+4987
-766
lines changed

.babelrc

Lines changed: 0 additions & 13 deletions
This file was deleted.

.eslintignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
node_modules/*
22
vendor/*
33
project/*
4+
babel.config.js
45

56
# compiled js ignored since it is run on the jsx directory
67
modules/*/js/*
78
modules/*/js/*/*
9+
modules/electrophysiology_browser/jsx/react-series-data-viewer/src/protocol-buffers/chunk_pb.js
810
htdocs/js/components/*
911

1012
# Ignore external libs
@@ -21,4 +23,3 @@ htdocs/js/FileSaver.min.js
2123
htdocs/vendor/
2224
htdocs/fontawesome/
2325
htdocs/bootstrap/
24-

.github/workflows/loristest.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ jobs:
4040
4141
- name: Change PHP Version in Dockerfile
4242
run: sed -i "s/7.4/${{ matrix.php }}/g" Dockerfile.test.php7
43-
43+
44+
- name: Install OS package dependencies
45+
run: sudo apt-get install -y protobuf-compiler
46+
4447
- name: Install composer dependencies
4548
if: steps.composer-cache.outputs.cache-hit != 'true'
4649
run: composer install --prefer-dist --no-progress --no-suggest

SQL/0000-00-05-ElectrophysiologyTables.sql

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ CREATE TABLE `physiological_file` (
3232
`AcquisitionTime` DATETIME DEFAULT '1970-01-01 00:00:01',
3333
`InsertedByUser` VARCHAR(50) NOT NULL,
3434
`FilePath` VARCHAR(255) NOT NULL,
35+
`Index` INT(5) DEFAULT NULL,
36+
`ParentID` INT(10) unsigned DEFAULT NULL,
3537
PRIMARY KEY (`PhysiologicalFileID`),
3638
CONSTRAINT `FK_session_ID`
3739
FOREIGN KEY (`SessionID`)
@@ -44,10 +46,29 @@ CREATE TABLE `physiological_file` (
4446
REFERENCES `physiological_modality` (`PhysiologicalModalityID`),
4547
CONSTRAINT `FK_phys_output_type_TypeID`
4648
FOREIGN KEY (`PhysiologicalOutputTypeID`)
47-
REFERENCES `physiological_output_type` (`PhysiologicalOutputTypeID`)
49+
REFERENCES `physiological_output_type` (`PhysiologicalOutputTypeID`),
50+
CONSTRAINT `FK_ParentID`
51+
FOREIGN KEY (`ParentID`)
52+
REFERENCES `physiological_file` (`PhysiologicalFileID`)
4853
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
4954

5055

56+
-- Create a physiological_split_file table
57+
CREATE TABLE `physiological_split_file` (
58+
`ID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
59+
`Index` INT(5) NOT NULL,
60+
`ArchiveID` INT(10) UNSIGNED NOT NULL,
61+
`FileType` VARCHAR(12) DEFAULT NULL,
62+
`FilePath` VARCHAR(255) NOT NULL,
63+
`Duration` DECIMAL(10,3) NOT NULL,
64+
CONSTRAINT `FK_ArchiveID`
65+
FOREIGN KEY (`ArchiveID`)
66+
REFERENCES `physiological_file` (`PhysiologicalFileID`),
67+
CONSTRAINT `FK_ImagingFileTypes`
68+
FOREIGN KEY (`FileType`)
69+
REFERENCES `ImagingFileTypes` (`type`),
70+
PRIMARY KEY (`ID`)
71+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
5172

5273
-- Create a physiological_parameter_file table that will store all JSON
5374
-- information that accompanies the BIDS physiological dataset
@@ -364,7 +385,8 @@ INSERT INTO ImagingFileTypes
364385
('vhdr', 'Brainvision file format (EEG)'),
365386
('vsm', 'BrainStorm file format (EEG)'),
366387
('edf', 'European data format (EEG)'),
367-
('cnt', 'Neuroscan CNT data format (EEG)');
388+
('cnt', 'Neuroscan CNT data format (EEG)'),
389+
('archive', 'Archive file');
368390

369391
-- Insert into annotation_file_type
370392
INSERT INTO physiological_annotation_file_type
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
INSERT INTO `ImagingFileTypes` VALUE ('archive', 'Archive file');
2+
3+
ALTER TABLE `physiological_file`
4+
ADD COLUMN `Index` INT(5) DEFAULT NULL,
5+
ADD COLUMN `ParentID` INT(10) unsigned DEFAULT NULL,
6+
ADD CONSTRAINT `FK_ParentID` FOREIGN KEY (`ParentID`) REFERENCES `physiological_file` (`PhysiologicalFileID`);
7+
8+
CREATE TABLE `physiological_split_file` (
9+
`ID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
10+
`Index` INT(5) NOT NULL,
11+
`ArchiveID` INT(10) UNSIGNED NOT NULL,
12+
`FileType` VARCHAR(12) DEFAULT NULL,
13+
`FilePath` VARCHAR(255) NOT NULL,
14+
`Duration` DECIMAL(10,3) NOT NULL,
15+
CONSTRAINT `FK_ArchiveID`
16+
FOREIGN KEY (`ArchiveID`)
17+
REFERENCES `physiological_file` (`PhysiologicalFileID`),
18+
CONSTRAINT `FK_ImagingFileTypes`
19+
FOREIGN KEY (`FileType`)
20+
REFERENCES `ImagingFileTypes` (`type`),
21+
PRIMARY KEY (`ID`)
22+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

babel.config.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = function(api) {
2+
api.cache(true);
3+
const presets = [
4+
"@babel/preset-react",
5+
"@babel/preset-env"
6+
];
7+
const plugins = [
8+
"@babel/plugin-proposal-object-rest-spread",
9+
["@babel/plugin-transform-runtime",
10+
{
11+
"regenerator": true
12+
}
13+
]
14+
];
15+
return {
16+
presets,
17+
plugins
18+
};
19+
}

htdocs/bootstrap/css/custom-css.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ div.navbar div.container div.navbar-brand {
496496
margin-right: 5px;
497497
}
498498

499-
.btn-default {
499+
.btn-default:not(.disabled) {
500500
border-color: #246EB6;
501501
color: #246EB6;
502502
background-color: white;
@@ -533,15 +533,15 @@ div.navbar div.container div.navbar-brand {
533533
border-color: #246EB6;
534534
}
535535

536-
a.btn.btn-primary {
536+
a.btn.btn-primary:not(.download, .split-nav) {
537537
color: #064785;
538538
border-color: transparent;
539539
background-color: white;
540540
-webkit-transition: background-color 0.2s ease-in; /* Safari */
541541
transition: background-color 0.2s ease-in;
542542
}
543543

544-
a.btn.btn-primary:hover {
544+
a.btn.btn-primary:hover:not(.download, .split-nav) {
545545
color: #E89A0C;
546546
border-color: transparent;
547547
background-color: white;

jsx/Panel.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,15 @@ class Panel extends Component {
6868
onClick={this.toggleCollapsed}
6969
data-toggle="collapse"
7070
data-target={'#' + this.props.id}
71-
style={this.props.collapsing ?
72-
{cursor: 'pointer', height: '3em', fontWeight: 'bold'} :
73-
{cursor: 'default', height: '3em', fontWeight: 'bold'}
71+
data-parent={this.props.parentId ?
72+
'#'+this.props.parentId :
73+
false
7474
}
75+
style={{
76+
cursor: this.props.collapsing ? 'pointer' : 'default',
77+
height: '3em',
78+
fontWeight: 'bold',
79+
}}
7580
>
7681
{title}
7782
{this.props.collapsing ? <span className={glyphClass}/> : ''}
@@ -86,7 +91,7 @@ class Panel extends Component {
8691
<div id={this.props.id}
8792
className={this.panelClass}
8893
role='tabpanel'
89-
style={{height: 'calc(100% - 3em)'}}
94+
style={this.props.collapsing ? {} : {height: 'calc(100% - 3em)'}}
9095
>
9196
<div className="panel-body"
9297
style={{...this.props.style, height: this.props.height}}>
@@ -100,6 +105,7 @@ class Panel extends Component {
100105

101106
Panel.propTypes = {
102107
initCollapsed: PropTypes.bool,
108+
parentId: PropTypes.string,
103109
id: PropTypes.string,
104110
height: PropTypes.string,
105111
title: PropTypes.string,
@@ -109,6 +115,7 @@ Panel.propTypes = {
109115
};
110116
Panel.defaultProps = {
111117
initCollapsed: false,
118+
parentId: null,
112119
id: 'default-panel',
113120
height: '100%',
114121
class: 'panel-primary',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
js/*
2+
jsx/react-series-data-viewer/src/protocol-buffers/chunk_pb.js
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
.react-series-data-viewer-scoped .dropdown-menu {
2+
width: calc(100% - 5px);
3+
}
4+
5+
.react-series-data-viewer-scoped .dropdown-menu li {
6+
margin-top: 0;
7+
padding: 0 10px;
8+
}
9+
10+
.react-series-data-viewer-scoped .dropdown-menu li:hover {
11+
background: #eee;
12+
cursor: pointer;
13+
margin-top: 0;
14+
width: 100%;
15+
}
16+
17+
.btn.btn-xs {
18+
font-size: 12px;
19+
}
20+
21+
.btn-group .btn {
22+
margin: 0;
23+
}
24+
25+
.btn-group {
26+
margin-right: 10px;
27+
}
28+
29+
.btn-primary:focus:not(.active),
30+
.btn-primary:active:not(.active) {
31+
color: #246EB6;
32+
background-color: white;
33+
border-color: #246EB6;
34+
outline: 0;
35+
}
36+
37+
.no-gutters > div {
38+
padding:0;
39+
}
40+
41+
svg {
42+
user-select: none;
43+
}
44+
45+
.annotation.list-group-item {
46+
background: #fffae6;
47+
border-left: 5px solid #ff6600;
48+
}
49+
50+
.event-list .btn.btn-primary {
51+
color: #555;
52+
border: 1px solid #555;
53+
}
54+
55+
.event-list .btn.btn-primary.active {
56+
color: #000;
57+
background-color: #ddd;
58+
border: 1px solid #000;
59+
}
60+
61+
.event-list .btn.btn-primary:hover {
62+
color: #333;
63+
background-color: #eee;
64+
border: 1px solid #333;
65+
}
66+
67+
#electrode-montage .list-group {
68+
border: 1px solid #ddd;
69+
}
70+
71+
#electrode-montage .list-group-item:first-child {
72+
border-top: none;
73+
}
74+
75+
#electrode-montage .list-group-item {
76+
margin-bottom: 0;
77+
border-left: none;
78+
border-right: none;
79+
border-bottom: none;
80+
}
81+
82+
.electrode:hover circle {
83+
stroke: #064785;
84+
cursor: pointer;
85+
fill: #E4EBF2
86+
}
87+
88+
.electrode:hover text {
89+
fill: #064785;
90+
cursor: pointer;
91+
}
92+
93+
#eegSessionView .table-scroll {
94+
padding-bottom: 0;
95+
}
96+
97+
#eegSessionView #lorisworkspace > .panel {
98+
border: none;
99+
}
100+
101+
#eegSessionView .panel-heading {
102+
height: auto !important;
103+
}
104+
105+
#eegSidebar {
106+
top: 0;
107+
bottom: 0;
108+
left: 0;
109+
height: calc(100%);
110+
position: fixed;
111+
}
112+
113+
#page.eegBrowser {
114+
vertical-align: top;
115+
position: relative;
116+
width: auto;
117+
}
118+
119+
/* Custom, iPhone Retina */
120+
@media only screen and (min-width : 320px) {
121+
.pagination-nav {
122+
padding-top: 8px;
123+
}
124+
125+
#eegSidebar {
126+
display: none;
127+
}
128+
}
129+
130+
/* Extra Small Devices, Phones */
131+
@media only screen and (min-width : 480px) {
132+
133+
}
134+
135+
/* Small Devices, Tablets */
136+
@media only screen and (min-width : 768px) {
137+
#eegSidebar {
138+
display: block;
139+
}
140+
141+
#page.eegBrowser {
142+
margin-left: 150px;
143+
}
144+
}
145+
146+
/* Medium Devices, Desktops */
147+
@media only screen and (min-width : 992px) {
148+
.event-list {
149+
margin-top: 40px;
150+
margin-bottom: 0;
151+
}
152+
}
153+
154+
/* Large Devices, Wide Screens */
155+
@media only screen and (min-width : 1200px) {
156+
.pull-right-lg {
157+
float: right;
158+
}
159+
160+
.pagination-nav {
161+
padding-top: 0;
162+
}
163+
}

0 commit comments

Comments
 (0)