Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Neuroscout tour + tooltips in Builder #607

Merged
merged 13 commits into from
Jul 8, 2019
Merged
25 changes: 25 additions & 0 deletions neuroscout/api_spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Setup API docs and Swagger
from .core import app
from .auth import add_auth_to_swagger
from apispec import APISpec
from flask_apispec.extension import FlaskApiSpec
import webargs as wa
from apispec.ext.marshmallow import MarshmallowPlugin

file_plugin = MarshmallowPlugin()
spec = APISpec(
title='neuroscout',
version='v1',
plugins=[file_plugin],
openapi_version='2.0'
)
app.config.update({
'APISPEC_SPEC': spec})
add_auth_to_swagger(spec)

docs = FlaskApiSpec(app)


@file_plugin.map_to_openapi_type('file', None)
class FileField(wa.fields.Raw):
pass
3 changes: 2 additions & 1 deletion neuroscout/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ def create_app():
STIMULUS_DIR=str(app.config['FILE_DIR'] / 'stimuli'),
EXTRACTION_DIR=str(app.config['FILE_DIR'] / 'extracted'),
FEATURE_SCHEMA=str(app.config['CONFIG_PATH'] / 'feature_schema.json'),
PREDICTOR_SCHEMA=str(app.config['CONFIG_PATH'] / 'predictor_schema.json'),
PREDICTOR_SCHEMA=str(
app.config['CONFIG_PATH'] / 'predictor_schema.json'),
ALL_TRANSFORMERS=str(app.config['CONFIG_PATH'] / 'transformers.json'),
BIBLIOGRAPHY=str(app.config['CONFIG_PATH'] / 'bibliography.json')
)
Expand Down
63 changes: 23 additions & 40 deletions neuroscout/core.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,36 @@
# -*- coding: utf-8 -*-
""" Core Neuroscout App """
from flask import send_file, render_template, url_for
from .basic import create_app
from .models import db

app = create_app()

from flask_mail import Mail
mail = Mail(app)

from flask_caching import Cache
cache = Cache(config={'CACHE_TYPE': 'filesystem', 'CACHE_DIR': app.config['CACHE_DIR']})
cache.init_app(app)

from flask_jwt import JWT
from flask_security import Security
from flask_security.confirmable import confirm_email_token_status, confirm_user
from .auth import authenticate, load_user, add_auth_to_swagger
from .models import *
from flask_cors import CORS

# Setup Flask-Security and JWT
security = Security(app, user_datastore)
jwt = JWT(app, authenticate, load_user)
from .basic import create_app
from .models import db, user_datastore

app = create_app()
mail = Mail(app)
cache = Cache(
config={'CACHE_TYPE': 'filesystem', 'CACHE_DIR': app.config['CACHE_DIR']})
cache.init_app(app)
# Enable CORS
from flask_cors import CORS
cors = CORS(app, resources={r"/api/*": {"origins": "*"},
r"/swagger/": {"origins": "*"}})
cors = CORS(
app,
resources={r"/api/*": {"origins": "*"}, r"/swagger/": {"origins": "*"}})

# Setup API
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from flask_apispec.extension import FlaskApiSpec
from .utils.core import route_factory
# These imports require the above
from .auth import authenticate, load_user
from .utils.factory import route_factory
from .api_spec import docs

file_plugin = MarshmallowPlugin()
spec = APISpec(
title='neuroscout',
version='v1',
plugins=[file_plugin],
openapi_version='2.0'
)
app.config.update({
'APISPEC_SPEC': spec})
add_auth_to_swagger(spec)
# Setup Flask-Security and JWT
security = Security(app, user_datastore)
jwt = JWT(app, authenticate, load_user)

docs = FlaskApiSpec(app)
# Set up API routes
route_factory(
app, docs,
[
Expand Down Expand Up @@ -80,19 +65,17 @@
def confirm(token):
''' Serve confirmaton page '''
expired, invalid, user = confirm_email_token_status(token)
name = None
confirmed = None
name, confirmed = None, None
if user:
if not expired and not invalid:
confirmed = confirm_user(user)
db.session.commit()
name = user.name
else:
confirmed = None
return render_template('confirm.html',
confirmed=confirmed, expired=expired,
invalid=invalid, name=name,
action_url=url_for('index', _external=True))
return render_template(
'confirm.html', confirmed=confirmed, expired=expired, invalid=invalid,
name=name, action_url=url_for('index', _external=True))


@app.route('/', defaults={'path': ''})
Expand Down
2 changes: 2 additions & 0 deletions neuroscout/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"react-router": "^4.1.1",
"react-router-dom": "^4.1.1",
"react-scripts": "2.1.0",
"reactour": "^1.15.0",
"reflux": "^6.4.1",
"styled-components": "^4.0.0",
"vega": "5.3.4",
"vega-embed": "^4.0.0",
"vega-lite": "3.1.0"
Expand Down
17 changes: 13 additions & 4 deletions neuroscout/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Home from './Home';
import Private from './Private';
import Public from './Public';
import { displayError, jwtFetch, timeout } from './utils';
import Tour from './Tour';

const FormItem = Form.Item;
const DOMAINROOT = config.server_url;
Expand Down Expand Up @@ -116,7 +117,7 @@ class App extends Reflux.Component<any, {}, AppState> {
analyses: [],
publicAnalyses: [],
auth: authActions.getInitialState(),
datasets: []
datasets: [],
};
this.store = AuthStore;
this.loadPublicAnalyses();
Expand Down Expand Up @@ -483,7 +484,10 @@ class App extends Reflux.Component<any, {}, AppState> {
{openSignup && signupModal}
{openEnterResetToken && authActions.enterResetTokenModal()}
<Layout>

<Tour
isOpen={this.state.auth.openTour}
closeTour={authActions.closeTour}
/>
<Content style={{ background: '#fff' }}>
<Row type="flex" justify="center" style={{padding: 0 }}>
<MainCol>
Expand All @@ -492,7 +496,7 @@ class App extends Reflux.Component<any, {}, AppState> {
style={{ lineHeight: '64px'}}
selectedKeys={[]}
>
<Menu.Item key="home">
<Menu.Item className="menuHome" key="home">
<Link to="/" style={{fontSize: 20}}>Neuroscout</Link>
</Menu.Item>
{this.state.auth.loggedIn ?
Expand Down Expand Up @@ -549,6 +553,7 @@ class App extends Reflux.Component<any, {}, AppState> {
<Menu.SubMenu
style={{float: 'right'}}
key="browse"
className="browseMain"
title={<span><Icon type="search"/>Browse</span>}
>

Expand All @@ -560,6 +565,7 @@ class App extends Reflux.Component<any, {}, AppState> {
</Menu.Item>
}
<Menu.Item
className="browsePublic"
key="public"
>
<Link to="/public">
Expand All @@ -569,7 +575,7 @@ class App extends Reflux.Component<any, {}, AppState> {
</Menu.SubMenu>

{this.state.auth.loggedIn &&
<Menu.Item key="create" style={{float: 'right'}}>
<Menu.Item className="newAnalysis" key="create" style={{float: 'right'}}>
<Link
to={{pathname: '/builder'}}
>
Expand Down Expand Up @@ -602,6 +608,7 @@ class App extends Reflux.Component<any, {}, AppState> {
return <AnalysisBuilder
updatedAnalysis={() => this.loadAnalyses()}
key={props.location.key}
doTour={this.state.auth.openTour}
datasets={this.state.datasets}
/>;
}
Expand All @@ -616,6 +623,7 @@ class App extends Reflux.Component<any, {}, AppState> {
id={props.match.params.id}
updatedAnalysis={() => this.loadAnalyses()}
userOwns={this.state.analyses.filter((x) => x.id === props.match.params.id).length > 0}
doTooltip={true}
datasets={this.state.datasets}
/>}
/>
Expand All @@ -627,6 +635,7 @@ class App extends Reflux.Component<any, {}, AppState> {
id={props.match.params.id}
updatedAnalysis={() => this.loadAnalyses()}
userOwns={this.state.analyses.filter((x) => x.id === props.match.params.id).length > 0}
doTooltip={true}
datasets={this.state.datasets}
/>
}
Expand Down
51 changes: 46 additions & 5 deletions neuroscout/frontend/src/Builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ let initializeStore = (): Store => ({
xformErrors: [],
contrastErrors: [],
fillAnalysis: false,
analysis404: false
analysis404: false,
doTooltip: false,
});

// Get list of tasks from a given dataset
Expand Down Expand Up @@ -220,6 +221,7 @@ type BuilderProps = {
id?: string;
updatedAnalysis: () => void;
userOwns?: boolean;
doTour?: boolean;
datasets: Dataset[];
};

Expand Down Expand Up @@ -1015,6 +1017,16 @@ export default class AnalysisBuilder extends React.Component<BuilderProps & Rout
this.setState({loadInitialPredictors: false});
}
}
//
componentDidMount() {
if (this.props.doTour) {
this.setState({doTooltip: true});
}
}

componentWillUnmount() {
this.setState({doTooltip: false});
}

render() {
if (this.state.analysis404) {
Expand Down Expand Up @@ -1086,7 +1098,14 @@ export default class AnalysisBuilder extends React.Component<BuilderProps & Rout
key="predictors"
disabled={(!predictorsActive || !isEditable) && !isFailed}
>
<h2>Select Predictors</h2>
<h2>Select Predictors&nbsp;&nbsp;
<Tooltip
title={'Use the search bar to find and select predictors to add to your analysis.\
For example, try searching for "face" or "fmriprep"'}
defaultVisible={this.state.doTooltip}
>
<Icon type="info-circle" style={{ fontSize: '15px'}}/>
</Tooltip></h2>
<PredictorSelector
availablePredictors={availablePredictors}
selectedPredictors={selectedPredictors}
Expand All @@ -1102,7 +1121,14 @@ export default class AnalysisBuilder extends React.Component<BuilderProps & Rout
key="transformations"
disabled={(!transformationsActive || !isEditable) && !isFailed}
>
<h2>Add Transformations</h2>
<h2>Add Transformations&nbsp;&nbsp;
<Tooltip
title={'Add transformations to sequentially modify your predictors \
prior to constructing the final design matrix.'}
defaultVisible={this.state.doTooltip}
>
<Icon type="info-circle" style={{ fontSize: '15px'}}/>
</Tooltip></h2>
<XformsTab
predictors={selectedPredictors}
xforms={analysis.transformations.filter(x => x.Name !== 'Convolve')}
Expand All @@ -1117,7 +1143,15 @@ export default class AnalysisBuilder extends React.Component<BuilderProps & Rout
<br/>
</TabPane>}
{isEditable && <TabPane tab="HRF" key="hrf" disabled={(!hrfActive || !isEditable) && !isFailed}>
<h2>HRF Convolution</h2>
<h2>HRF Convolution&nbsp;&nbsp;
<Tooltip
title={'Select which variables to convolve with the hemodynamic response function. \
To convolve all variables that are not fMRIPrep confounds, \
click "Select All Non-Confounds"'}
defaultVisible={this.state.doTooltip}
>
<Icon type="info-circle" style={{ fontSize: '15px'}}/>
</Tooltip></h2>
<PredictorSelector
availablePredictors={selectedPredictors}
selectedPredictors={selectedHRFPredictors}
Expand All @@ -1137,7 +1171,14 @@ export default class AnalysisBuilder extends React.Component<BuilderProps & Rout
key="contrasts"
disabled={(!contrastsActive || !isEditable) && !isFailed}
>
<h2>Add Contrasts</h2>
<h2>Add Contrasts&nbsp;&nbsp;
<Tooltip
title={'Here you can define statistical contrasts to compute from the fitted parameter estimates.\
To create identity contrasts [1, 0] for each predictor, use "Generate Automatic Contrasts"'}
defaultVisible={this.state.doTooltip}
>
<Icon type="info-circle" style={{ fontSize: '15px'}}/>
</Tooltip></h2>
<ContrastsTab
analysis={analysis}
contrasts={analysis.contrasts}
Expand Down
4 changes: 2 additions & 2 deletions neuroscout/frontend/src/Contrasts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const ContrastDisplay = (props: ContrastDisplayProps) => {
</Button>
</div>
<div>
<b>{`${index + 1}: ${contrast.Name}`} </b>{`${contrast.ContrastType} test`}<br/>
<b>{`${contrast.Name}`} </b>{`${contrast.ContrastType} test`}<br/>
{/*contrast.ConditionList && contrast.ConditionList.map((predictor, i) => {
return(predictor + ': ' + contrast.Weights[i] + ' ');
})*/}
Expand Down Expand Up @@ -212,7 +212,7 @@ export class ContrastsTab extends React.Component<ContrastsTabProps, ContrastsTa
<List.Item className={this.getStyle(index)}>
<Draggable key={index} draggableId={'' + index} index={index}>
{(providedDraggable: DraggableProvided, snapshotDraggable: DraggableStateSnapshot) => (
<div
<div
style={{'width': '100%'}}
ref={providedDraggable.innerRef}
{...providedDraggable.dragHandleProps}
Expand Down
4 changes: 1 addition & 3 deletions neuroscout/frontend/src/FAQ.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { MainCol } from './HelperComponents';
const Panel = Collapse.Panel;

const faqs = [
[`How do I get started?`,
`See the Neuroscout tutorial here`],
[`Is this service free to use?`,
`Yes! Note, however, that Neuroscout is a web-based engine for fMRI analysis specification; at the moment,
we don't provide free computing resources for the execution of the resulting analysis bundles.`],
Expand Down Expand Up @@ -48,7 +46,7 @@ const faqs = [
to extract novel feature timecourses. To facility this process, we have developed a Python library for
multimodal feature extraction called <a href="https://github.com/tyarkoni/pliers"> pliers</a>.
Pliers allows us to extract a wide-variety of features across modalities using various external content
analysis services with ease. For example, we are able to use Google Vision API to encode various
analysis services with ease. For example, we are able to use Google Vision API to encode various
aspects of the visual elements of movie frames, such as when a face is present. In addition, pliers
allows us to easily link up various feature extraction services; for example, we can use the IBM Watson
Speech to Text API to transcribe the speech in a movie into words with precise onsets, and then use a
Expand Down