copied all files and added two heart rate modules
Esse commit está contido em:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import React, { useState, useCallback } from "react";
|
||||
import { MuseClient } from "muse-js";
|
||||
import { Select, Card, Stack, Button, ButtonGroup } from "@shopify/polaris";
|
||||
|
||||
@@ -8,6 +8,8 @@ import * as generalTranslations from "./components/translations/en";
|
||||
import { emptyChannelData } from "./components/chartOptions";
|
||||
|
||||
import * as funIntro from "./components/EEGEduIntro/EEGEduIntro"
|
||||
import * as funHeartRaw from "./components/EEGEduHeartRaw/EEGEduHeartRaw"
|
||||
import * as funHeartSpectra from "./components/EEGEduHeartSpectra/EEGEduHeartSpectra"
|
||||
import * as funRaw from "./components/EEGEduRaw/EEGEduRaw";
|
||||
import * as funSpectra from "./components/EEGEduSpectra/EEGEduSpectra";
|
||||
import * as funBands from "./components/EEGEduBands/EEGEduBands";
|
||||
@@ -17,6 +19,8 @@ import * as funAlpha from "./components/EEGEduAlpha/EEGEduAlpha"
|
||||
import * as funSsvep from "./components/EEGEduSsvep/EEGEduSsvep"
|
||||
|
||||
const intro = translations.types.intro;
|
||||
const heartRaw = translations.types.heartRaw;
|
||||
const heartSpectra = translations.types.heartSpectra;
|
||||
const raw = translations.types.raw;
|
||||
const spectra = translations.types.spectra;
|
||||
const bands = translations.types.bands;
|
||||
@@ -29,6 +33,8 @@ export function PageSwitcher() {
|
||||
|
||||
// data pulled out of multicast$
|
||||
const [introData, setIntroData] = useState(emptyChannelData)
|
||||
const [heartRawData, setHeartRawData] = useState(emptyChannelData);
|
||||
const [heartSpectraData, setHeartSpectraData] = useState(emptyChannelData);
|
||||
const [rawData, setRawData] = useState(emptyChannelData);
|
||||
const [spectraData, setSpectraData] = useState(emptyChannelData);
|
||||
const [bandsData, setBandsData] = useState(emptyChannelData);
|
||||
@@ -39,6 +45,8 @@ export function PageSwitcher() {
|
||||
|
||||
// pipe settings
|
||||
const [introSettings] = useState(funIntro.getSettings);
|
||||
const [heartRawSettings] = useState(funHeartRaw.getSettings);
|
||||
const [heartSpectraSettings] = useState(funHeartSpectra.getSettings);
|
||||
const [rawSettings, setRawSettings] = useState(funRaw.getSettings);
|
||||
const [spectraSettings, setSpectraSettings] = useState(funSpectra.getSettings);
|
||||
const [bandsSettings, setBandsSettings] = useState(funBands.getSettings);
|
||||
@@ -58,6 +66,8 @@ export function PageSwitcher() {
|
||||
console.log("Switching to: " + value);
|
||||
|
||||
if (window.subscriptionIntro) window.subscriptionIntro.unsubscribe();
|
||||
if (window.subscriptionHeartRaw) window.subscriptionHeartRaw.unsubscribe();
|
||||
if (window.subscriptionHeartSpectra) window.subscriptionHeartSpectra.unsubscribe();
|
||||
if (window.subscriptionRaw) window.subscriptionRaw.unsubscribe();
|
||||
if (window.subscriptionSpectra) window.subscriptionSpectra.unsubscribe();
|
||||
if (window.subscriptionBands) window.subscriptionBands.unsubscribe();
|
||||
@@ -80,6 +90,8 @@ export function PageSwitcher() {
|
||||
|
||||
const chartTypes = [
|
||||
{ label: intro, value: intro },
|
||||
{ label: heartRaw, value: heartRaw },
|
||||
{ label: heartSpectra, value: heartSpectra },
|
||||
{ label: raw, value: raw },
|
||||
{ label: spectra, value: spectra },
|
||||
{ label: bands, value: bands },
|
||||
@@ -91,6 +103,8 @@ export function PageSwitcher() {
|
||||
|
||||
function buildPipes(value) {
|
||||
funIntro.buildPipe(introSettings);
|
||||
funHeartRaw.buildPipe(heartRawSettings);
|
||||
funHeartSpectra.buildPipe(heartSpectraSettings);
|
||||
funRaw.buildPipe(rawSettings);
|
||||
funSpectra.buildPipe(spectraSettings);
|
||||
funBands.buildPipe(bandsSettings);
|
||||
@@ -105,6 +119,12 @@ export function PageSwitcher() {
|
||||
case intro:
|
||||
funIntro.setup(setIntroData, introSettings);
|
||||
break;
|
||||
case heartRaw:
|
||||
funHeartRaw.setup(setHeartRawData, heartRawSettings);
|
||||
break;
|
||||
case heartSpectra:
|
||||
funHeartSpectra.setup(setHeartSpectraData, heartSpectraSettings);
|
||||
break;
|
||||
case raw:
|
||||
funRaw.setup(setRawData, rawSettings);
|
||||
break;
|
||||
@@ -173,6 +193,10 @@ export function PageSwitcher() {
|
||||
switch(selected) {
|
||||
case intro:
|
||||
return null
|
||||
case heartRaw:
|
||||
return null
|
||||
case heartSpectra:
|
||||
return null
|
||||
case raw:
|
||||
return (
|
||||
funRaw.renderSliders(setRawData, setRawSettings, status, rawSettings)
|
||||
@@ -209,6 +233,10 @@ export function PageSwitcher() {
|
||||
switch (selected) {
|
||||
case intro:
|
||||
return <funIntro.renderModule data={introData} />;
|
||||
case heartRaw:
|
||||
return <funHeartRaw.renderModule data={heartRawData} />;
|
||||
case heartSpectra:
|
||||
return <funHeartSpectra.renderModule data={heartSpectraData} />;
|
||||
case raw:
|
||||
return <funRaw.renderModule data={rawData} />;
|
||||
case spectra:
|
||||
@@ -232,8 +260,14 @@ export function PageSwitcher() {
|
||||
switch (selected) {
|
||||
case intro:
|
||||
return null
|
||||
case heartRaw:
|
||||
return null
|
||||
case heartSpectra:
|
||||
return (
|
||||
funHeartSpectra.renderRecord(recordPopChange, recordPop, status, heartSpectraSettings)
|
||||
)
|
||||
case raw:
|
||||
return(
|
||||
return (
|
||||
funRaw.renderRecord(recordPopChange, recordPop, status, rawSettings)
|
||||
)
|
||||
case spectra:
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
import React from "react";
|
||||
import { catchError, multicast } from "rxjs/operators";
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import { TextContainer, Card, Stack, RangeSlider, Button, ButtonGroup, Modal } from "@shopify/polaris";
|
||||
import { saveAs } from 'file-saver';
|
||||
import { take } from "rxjs/operators";
|
||||
|
||||
import { channelNames } from "muse-js";
|
||||
import { Line } from "react-chartjs-2";
|
||||
|
||||
import { zipSamples } from "muse-js";
|
||||
|
||||
import {
|
||||
bandpassFilter,
|
||||
epoch
|
||||
} from "@neurosity/pipes";
|
||||
|
||||
import { chartStyles, generalOptions } from "../chartOptions";
|
||||
|
||||
import * as generalTranslations from "../translations/en";
|
||||
import * as specificTranslations from "./translations/en";
|
||||
|
||||
import { generateXTics, standardDeviation } from "../../utils/chartUtils";
|
||||
|
||||
export function getSettings () {
|
||||
return {
|
||||
cutOffLow: 2,
|
||||
cutOffHigh: 20,
|
||||
nbChannels: 4,
|
||||
interval: 50,
|
||||
srate: 256,
|
||||
duration: 1024,
|
||||
name: 'Raw'
|
||||
}
|
||||
};
|
||||
|
||||
export function buildPipe(Settings) {
|
||||
if (window.subscriptionRaw) window.subscriptionRaw.unsubscribe();
|
||||
|
||||
window.pipeRaw$ = null;
|
||||
window.multicastRaw$ = null;
|
||||
window.subscriptionRaw = null;
|
||||
|
||||
// Build Pipe
|
||||
window.pipeRaw$ = zipSamples(window.source.eegReadings$).pipe(
|
||||
bandpassFilter({
|
||||
cutoffFrequencies: [Settings.cutOffLow, Settings.cutOffHigh],
|
||||
nbChannels: Settings.nbChannels }),
|
||||
epoch({
|
||||
duration: Settings.duration,
|
||||
interval: Settings.interval,
|
||||
samplingRate: Settings.srate
|
||||
}),
|
||||
catchError(err => {
|
||||
console.log(err);
|
||||
})
|
||||
);
|
||||
window.multicastRaw$ = window.pipeRaw$.pipe(
|
||||
multicast(() => new Subject())
|
||||
);
|
||||
}
|
||||
|
||||
export function setup(setData, Settings) {
|
||||
console.log("Subscribing to " + Settings.name);
|
||||
|
||||
if (window.multicastRaw$) {
|
||||
window.subscriptionRaw = window.multicastRaw$.subscribe(data => {
|
||||
setData(rawData => {
|
||||
Object.values(rawData).forEach((channel, index) => {
|
||||
if (index < 4) {
|
||||
channel.datasets[0].data = data.data[index];
|
||||
channel.xLabels = generateXTics(Settings.srate, Settings.duration);
|
||||
channel.datasets[0].qual = standardDeviation(data.data[index])
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
ch0: rawData.ch0,
|
||||
ch1: rawData.ch1,
|
||||
ch2: rawData.ch2,
|
||||
ch3: rawData.ch3
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
window.multicastRaw$.connect();
|
||||
console.log("Subscribed to Raw");
|
||||
}
|
||||
}
|
||||
|
||||
export function renderModule(channels) {
|
||||
function renderCharts() {
|
||||
return Object.values(channels.data).map((channel, index) => {
|
||||
const options = {
|
||||
...generalOptions,
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
scaleLabel: {
|
||||
...generalOptions.scales.xAxes[0].scaleLabel,
|
||||
labelString: specificTranslations.xlabel
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
scaleLabel: {
|
||||
...generalOptions.scales.yAxes[0].scaleLabel,
|
||||
labelString: specificTranslations.ylabel
|
||||
},
|
||||
ticks: {
|
||||
max: 300,
|
||||
min: -300
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
borderColor: 'rgba(' + channel.datasets[0].qual*10 + ', 128, 128)',
|
||||
fill: false
|
||||
},
|
||||
point: {
|
||||
radius: 0
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
title: {
|
||||
...generalOptions.title,
|
||||
text: generalTranslations.channel + channelNames[index] + ' --- SD: ' + channel.datasets[0].qual
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card.Section key={"Card_" + index}>
|
||||
<Line key={"Line_" + index} data={channel} options={options} />
|
||||
</Card.Section>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Card title={specificTranslations.title}>
|
||||
<Card.Section>
|
||||
<Stack>
|
||||
<TextContainer>
|
||||
<p>{specificTranslations.description}</p>
|
||||
</TextContainer>
|
||||
</Stack>
|
||||
</Card.Section>
|
||||
<Card.Section>
|
||||
<div style={chartStyles.wrapperStyle.style}>{renderCharts()}</div>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderSliders(setData, setSettings, status, Settings) {
|
||||
|
||||
function resetPipeSetup(value) {
|
||||
buildPipe(Settings);
|
||||
setup(setData, Settings)
|
||||
}
|
||||
|
||||
function handleIntervalRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, interval: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
function handleCutoffLowRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, cutOffLow: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
function handleCutoffHighRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, cutOffHigh: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
function handleDurationRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, duration: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
return (
|
||||
<Card title={Settings.name + ' Settings'} sectioned>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={128} step={128} max={4096}
|
||||
label={'Epoch duration (Sampling Points): ' + Settings.duration}
|
||||
value={Settings.duration}
|
||||
onChange={handleDurationRangeSliderChange}
|
||||
/>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={10} step={1} max={Settings.duration}
|
||||
label={'Sampling points between epochs onsets: ' + Settings.interval}
|
||||
value={Settings.interval}
|
||||
onChange={handleIntervalRangeSliderChange}
|
||||
/>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={.01} step={.5} max={Settings.cutOffHigh - .5}
|
||||
label={'Cutoff Frequency Low: ' + Settings.cutOffLow + ' Hz'}
|
||||
value={Settings.cutOffLow}
|
||||
onChange={handleCutoffLowRangeSliderChange}
|
||||
/>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={Settings.cutOffLow + .5} step={.5} max={Settings.srate/2}
|
||||
label={'Cutoff Frequency High: ' + Settings.cutOffHigh + ' Hz'}
|
||||
value={Settings.cutOffHigh}
|
||||
onChange={handleCutoffHighRangeSliderChange}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export function renderRecord(recordPopChange, recordPop, status, Settings) {
|
||||
return (
|
||||
<Card title={'Record ' + Settings.name + ' Data'} sectioned>
|
||||
<Card.Section>
|
||||
<p>
|
||||
{"When you are recording raw data it is recommended you set the "}
|
||||
{"number of sampling points between epochs onsets to be equal to the epoch duration. "}
|
||||
{"This will ensure that consecutive rows of your output file are not overlapping in time."}
|
||||
{"It will make the live plots appear more choppy."}
|
||||
</p>
|
||||
</Card.Section>
|
||||
<Stack>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
onClick={() => {
|
||||
saveToCSV(Settings);
|
||||
recordPopChange();
|
||||
}}
|
||||
primary={status !== generalTranslations.connect}
|
||||
disabled={status === generalTranslations.connect}
|
||||
>
|
||||
{'Save to CSV'}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Modal
|
||||
open={recordPop}
|
||||
onClose={recordPopChange}
|
||||
title="Recording Data"
|
||||
>
|
||||
<Modal.Section>
|
||||
<TextContainer>
|
||||
|
||||
<p>
|
||||
Your data is currently recording,
|
||||
once complete it will be downloaded as a .csv file
|
||||
and can be opened with your favorite spreadsheet program.
|
||||
Close this window once the download completes.
|
||||
</p>
|
||||
</TextContainer>
|
||||
</Modal.Section>
|
||||
</Modal>
|
||||
</Stack>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
function saveToCSV(Settings) {
|
||||
const numSamplesToSave = 50;
|
||||
console.log('Saving ' + numSamplesToSave + ' samples...');
|
||||
var localObservable$ = null;
|
||||
const dataToSave = [];
|
||||
|
||||
console.log('making ' + Settings.name + ' headers')
|
||||
|
||||
// for each module subscribe to multicast and make header
|
||||
// take one sample from selected observable object for headers
|
||||
localObservable$ = window.multicastRaw$.pipe(
|
||||
take(1)
|
||||
);
|
||||
//take one sample to get header info
|
||||
localObservable$.subscribe({
|
||||
next(x) {
|
||||
dataToSave.push(
|
||||
"Timestamp (ms),",
|
||||
generateXTics(x.info.samplingRate,x.data[0].length,false).map(function(f) {return "ch0_" + f + "ms"}) + ",",
|
||||
generateXTics(x.info.samplingRate,x.data[0].length,false).map(function(f) {return "ch1_" + f + "ms"}) + ",",
|
||||
generateXTics(x.info.samplingRate,x.data[0].length,false).map(function(f) {return "ch2_" + f + "ms"}) + ",",
|
||||
generateXTics(x.info.samplingRate,x.data[0].length,false).map(function(f) {return "ch3_" + f + "ms"}) + ",",
|
||||
generateXTics(x.info.samplingRate,x.data[0].length,false).map(function(f) {return "chAux_" + f + "ms"}) + ",",
|
||||
"info",
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
});
|
||||
// put selected observable object into local and start taking samples
|
||||
localObservable$ = window.multicastRaw$.pipe(
|
||||
take(numSamplesToSave)
|
||||
);
|
||||
|
||||
// now with header in place subscribe to each epoch and log it
|
||||
localObservable$.subscribe({
|
||||
next(x) {
|
||||
dataToSave.push(Date.now() + "," + Object.values(x).join(",") + "\n");
|
||||
// logging is useful for debugging -yup
|
||||
// console.log(x);
|
||||
},
|
||||
error(err) { console.log(err); },
|
||||
complete() {
|
||||
console.log('Trying to save')
|
||||
var blob = new Blob(
|
||||
dataToSave,
|
||||
{type: "text/plain;charset=utf-8"}
|
||||
);
|
||||
saveAs(blob, Settings.name + "_Recording_" + Date.now() + ".csv");
|
||||
console.log('Completed');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"title": "Heart Rate (Electrocardiogram)",
|
||||
"description": [
|
||||
"As a first introduction to measurement of electrical potentials from the body we will look at something accessible. ",
|
||||
"As the heart beats and pumps blood throuhgouts our body, a series of electrical potentials are created, which can be measured using electrodes placed around the heart. ",
|
||||
"This is referred to as the Electrocardiogram (ECG), and is best measured comparing the potential accross the left vs. right side of the body. ",
|
||||
"Therefore, we can take off the muse and place a finger on one hand on the muse's reference electrode (in the center of the forehead). ",
|
||||
"We can then place a finger of our opposite hand on one of the eeg electrodes. For this example pick the left forehead electrode. ",
|
||||
"So place your left fingers pinching the left forehead electrode, and your right fingers pinching the center electrode. ",
|
||||
"Rest the muse on the table as you do this, and relax your body. Soon you should see spikes in voltage measured between thsoe two electrodes each time your heart beats. "
|
||||
],
|
||||
"xlabel": "Time (msec)",
|
||||
"ylabel": "Voltage (\u03BCV)"
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
import React from "react";
|
||||
import { catchError, multicast } from "rxjs/operators";
|
||||
|
||||
import { TextContainer, Card, Stack, RangeSlider, Button, ButtonGroup, Modal } from "@shopify/polaris";
|
||||
import { saveAs } from 'file-saver';
|
||||
import { take } from "rxjs/operators";
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import { channelNames } from "muse-js";
|
||||
import { Line } from "react-chartjs-2";
|
||||
|
||||
import { zipSamples } from "muse-js";
|
||||
|
||||
import {
|
||||
bandpassFilter,
|
||||
epoch,
|
||||
fft,
|
||||
sliceFFT
|
||||
} from "@neurosity/pipes";
|
||||
|
||||
import { chartStyles, generalOptions } from "../chartOptions";
|
||||
|
||||
import * as generalTranslations from "../translations/en";
|
||||
import * as specificTranslations from "./translations/en";
|
||||
|
||||
export function getSettings() {
|
||||
return {
|
||||
cutOffLow: 2,
|
||||
cutOffHigh: 20,
|
||||
nbChannels: 4,
|
||||
interval: 100,
|
||||
bins: 256,
|
||||
sliceFFTLow: 1,
|
||||
sliceFFTHigh: 30,
|
||||
duration: 1024,
|
||||
srate: 256,
|
||||
name: 'Spectra'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export function buildPipe(Settings) {
|
||||
if (window.subscriptionSpectra) window.subscriptionSpectra.unsubscribe();
|
||||
|
||||
window.pipeSpectra$ = null;
|
||||
window.multicastSpectra$ = null;
|
||||
window.subscriptionSpectra = null;
|
||||
|
||||
// Build Pipe
|
||||
window.pipeSpectra$ = zipSamples(window.source.eegReadings$).pipe(
|
||||
bandpassFilter({
|
||||
cutoffFrequencies: [Settings.cutOffLow, Settings.cutOffHigh],
|
||||
nbChannels: Settings.nbChannels }),
|
||||
epoch({
|
||||
duration: Settings.duration,
|
||||
interval: Settings.interval,
|
||||
samplingRate: Settings.srate
|
||||
}),
|
||||
fft({ bins: Settings.bins }),
|
||||
sliceFFT([Settings.sliceFFTLow, Settings.sliceFFTHigh]),
|
||||
catchError(err => {
|
||||
console.log(err);
|
||||
})
|
||||
);
|
||||
|
||||
window.multicastSpectra$ = window.pipeSpectra$.pipe(
|
||||
multicast(() => new Subject())
|
||||
);
|
||||
}
|
||||
|
||||
export function setup(setData, Settings) {
|
||||
console.log("Subscribing to " + Settings.name);
|
||||
|
||||
if (window.multicastSpectra$) {
|
||||
window.subscriptionSpectra = window.multicastSpectra$.subscribe(data => {
|
||||
setData(spectraData => {
|
||||
Object.values(spectraData).forEach((channel, index) => {
|
||||
if (index < 4) {
|
||||
channel.datasets[0].data = data.psd[index];
|
||||
channel.xLabels = data.freqs;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
ch0: spectraData.ch0,
|
||||
ch1: spectraData.ch1,
|
||||
ch2: spectraData.ch2,
|
||||
ch3: spectraData.ch3
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
window.multicastSpectra$.connect();
|
||||
console.log("Subscribed to " + Settings.name);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderModule(channels) {
|
||||
function renderCharts() {
|
||||
return Object.values(channels.data).map((channel, index) => {
|
||||
const options = {
|
||||
...generalOptions,
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
scaleLabel: {
|
||||
...generalOptions.scales.xAxes[0].scaleLabel,
|
||||
labelString: specificTranslations.xlabel
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
scaleLabel: {
|
||||
...generalOptions.scales.yAxes[0].scaleLabel,
|
||||
labelString: specificTranslations.ylabel
|
||||
},
|
||||
ticks: {
|
||||
max: 25,
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 3
|
||||
}
|
||||
},
|
||||
title: {
|
||||
...generalOptions.title,
|
||||
text: generalTranslations.channel + channelNames[index]
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card.Section key={"Card_" + index}>
|
||||
<Line key={"Line_" + index} data={channel} options={options} />
|
||||
</Card.Section>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Card title={specificTranslations.title}>
|
||||
<Card.Section>
|
||||
<Stack>
|
||||
<TextContainer>
|
||||
<p>{specificTranslations.description}</p>
|
||||
</TextContainer>
|
||||
</Stack>
|
||||
</Card.Section>
|
||||
<Card.Section>
|
||||
<div style={chartStyles.wrapperStyle.style}>{renderCharts()}</div>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderSliders(setData, setSettings, status, Settings) {
|
||||
|
||||
function resetPipeSetup(value) {
|
||||
buildPipe(Settings);
|
||||
setup(setData, Settings)
|
||||
}
|
||||
|
||||
function handleIntervalRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, interval: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
function handleCutoffLowRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, cutOffLow: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
function handleCutoffHighRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, cutOffHigh: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
function handleSliceFFTLowRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, sliceFFTLow: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
function handleSliceFFTHighRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, sliceFFTHigh: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
function handleDurationRangeSliderChange(value) {
|
||||
setSettings(prevState => ({...prevState, duration: value}));
|
||||
resetPipeSetup();
|
||||
}
|
||||
|
||||
return (
|
||||
<Card title={Settings.name + ' Settings'} sectioned>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={128} step={128} max={4096}
|
||||
label={'Epoch duration (Sampling Points): ' + Settings.duration}
|
||||
value={Settings.duration}
|
||||
onChange={handleDurationRangeSliderChange}
|
||||
/>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={10} step={5} max={Settings.duration}
|
||||
label={'Sampling points between epochs onsets: ' + Settings.interval}
|
||||
value={Settings.interval}
|
||||
onChange={handleIntervalRangeSliderChange}
|
||||
/>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={.01} step={.5} max={Settings.cutOffHigh - .5}
|
||||
label={'Cutoff Frequency Low: ' + Settings.cutOffLow + ' Hz'}
|
||||
value={Settings.cutOffLow}
|
||||
onChange={handleCutoffLowRangeSliderChange}
|
||||
/>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={Settings.cutOffLow + .5} step={.5} max={Settings.srate/2}
|
||||
label={'Cutoff Frequency High: ' + Settings.cutOffHigh + ' Hz'}
|
||||
value={Settings.cutOffHigh}
|
||||
onChange={handleCutoffHighRangeSliderChange}
|
||||
/>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={1} max={Settings.sliceFFTHigh - 1}
|
||||
label={'Slice FFT Lower limit: ' + Settings.sliceFFTLow + ' Hz'}
|
||||
value={Settings.sliceFFTLow}
|
||||
onChange={handleSliceFFTLowRangeSliderChange}
|
||||
/>
|
||||
<RangeSlider
|
||||
disabled={status === generalTranslations.connect}
|
||||
min={Settings.sliceFFTLow + 1}
|
||||
label={'Slice FFT Upper limit: ' + Settings.sliceFFTHigh + ' Hz'}
|
||||
value={Settings.sliceFFTHigh}
|
||||
onChange={handleSliceFFTHighRangeSliderChange}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export function renderRecord(recordPopChange, recordPop, status, Settings) {
|
||||
return(
|
||||
<Card title={'Record ' + Settings.name +' Data'} sectioned>
|
||||
<Stack>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
onClick={() => {
|
||||
saveToCSV(Settings);
|
||||
recordPopChange();
|
||||
}}
|
||||
primary={status !== generalTranslations.connect}
|
||||
disabled={status === generalTranslations.connect}
|
||||
>
|
||||
{'Save to CSV'}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Modal
|
||||
open={recordPop}
|
||||
onClose={recordPopChange}
|
||||
title="Recording Data"
|
||||
>
|
||||
<Modal.Section>
|
||||
<TextContainer>
|
||||
<p>
|
||||
Your data is currently recording,
|
||||
once complete it will be downloaded as a .csv file
|
||||
and can be opened with your favorite spreadsheet program.
|
||||
Close this window once the download completes.
|
||||
</p>
|
||||
</TextContainer>
|
||||
</Modal.Section>
|
||||
</Modal>
|
||||
</Stack>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function saveToCSV(Settings) {
|
||||
const numSamplesToSave = 50;
|
||||
console.log('Saving ' + numSamplesToSave + ' samples...');
|
||||
var localObservable$ = null;
|
||||
const dataToSave = [];
|
||||
|
||||
console.log('making ' + Settings.name + ' headers')
|
||||
|
||||
// take one sample from selected observable object for headers
|
||||
localObservable$ = window.multicastSpectra$.pipe(
|
||||
take(1)
|
||||
);
|
||||
|
||||
localObservable$.subscribe({
|
||||
next(x) {
|
||||
let freqs = Object.values(x.freqs);
|
||||
dataToSave.push(
|
||||
"Timestamp (ms),",
|
||||
freqs.map(function(f) {return "ch0_" + f + "Hz"}) + ",",
|
||||
freqs.map(function(f) {return "ch1_" + f + "Hz"}) + ",",
|
||||
freqs.map(function(f) {return "ch2_" + f + "Hz"}) + ",",
|
||||
freqs.map(function(f) {return "ch3_" + f + "Hz"}) + ",",
|
||||
freqs.map(function(f) {return "chAux_" + f + "Hz"}) + ",",
|
||||
freqs.map(function(f) {return "f_" + f + "Hz"}) + "," ,
|
||||
"info",
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
});
|
||||
// put selected observable object into local and start taking samples
|
||||
localObservable$ = window.multicastSpectra$.pipe(
|
||||
take(numSamplesToSave)
|
||||
);
|
||||
|
||||
|
||||
// now with header in place subscribe to each epoch and log it
|
||||
localObservable$.subscribe({
|
||||
next(x) {
|
||||
dataToSave.push(Date.now() + "," + Object.values(x).join(",") + "\n");
|
||||
// logging is useful for debugging -yup
|
||||
// console.log(x);
|
||||
},
|
||||
error(err) { console.log(err); },
|
||||
complete() {
|
||||
console.log('Trying to save')
|
||||
var blob = new Blob(
|
||||
dataToSave,
|
||||
{type: "text/plain;charset=utf-8"}
|
||||
);
|
||||
saveAs(blob, Settings.name + "_Recording_" + Date.now() + ".csv");
|
||||
console.log('Completed');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"title": "Heart Rate (Electrocardiogram)",
|
||||
"description": [
|
||||
"Now we look at the same data, but in a different way. Instead of looking at the voltage over time, ",
|
||||
"We now transform the data to show us what frequencies are present in the continuous signal. ",
|
||||
"In this frequency domain, we now ignore time and consider how much power of each frequency there is in a segment of data. ",
|
||||
"If you place your fingers the same way you did when looking at your ECG, you should start to see a peak ",
|
||||
"Along the horizontal axis is the beats per minute, or the frequency of the peaks in your ECG. ",
|
||||
"The vertical y-axis shows the power of the rhythms in the data at each frequency, or how large the changes are between peak and through of the oscillations. "
|
||||
],
|
||||
"xlabel": "Time (msec)",
|
||||
"ylabel": "Voltage (\u03BCV)"
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"title": "Choose your Module",
|
||||
"types": {
|
||||
"intro": "Introduction",
|
||||
"raw": "Raw and Filtered Data",
|
||||
"spectra": "Frequency Spectra",
|
||||
"bands": "Frequency Bands",
|
||||
"animate": "Brain Controlled Animation",
|
||||
"spectro": "Spectrogram (spectra over time)",
|
||||
"alpha": "Eyes open vs. Eyes closed Experiment",
|
||||
"ssvep": "Steady-State Visual Evoked Potential (SSVEP) Experiment"
|
||||
"intro": "1. Introduction",
|
||||
"heartRaw": "2. Electrocardiogram (Heart beats)",
|
||||
"heartSpectra": "3. Heart Rate (Beats per minute)",
|
||||
"raw": "4. Raw and Filtered Data",
|
||||
"spectra": "5. Frequency Spectra",
|
||||
"bands": "6. Frequency Bands",
|
||||
"animate": "7. Brain Controlled Animation",
|
||||
"spectro": "8. Spectrogram (spectra over time)",
|
||||
"alpha": "9. Eyes open vs. Eyes closed Experiment",
|
||||
"ssvep": "10. Steady-State Visual Evoked Potential (SSVEP) Experiment"
|
||||
}
|
||||
}
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário