├── .env.example ├── .gitignore ├── CODE_OF_CONDUCT.md ├── README.md ├── backups └── README.md ├── db └── README.md ├── debug └── debug.log ├── deploy ├── etc │ ├── redis.conf │ └── uwsgi.d │ │ └── waveform_annotation.ini ├── nginx │ └── sites_enabled │ │ └── breadboard_nginx.conf └── post-receive ├── record-files ├── merge_new_data.sh └── sample_data │ ├── README.md │ ├── RECORDS_VTVF_LIMIT-5 │ ├── filter_anns.sh │ ├── get_anns.sh │ ├── sub_records.sh │ ├── subfolder.sh │ ├── top_records.sh │ ├── user_assignments.csv │ ├── v101l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v101l_1m.alm │ ├── v101l_1m.hea │ └── v101l_1m.mat │ ├── v111l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v111l_1m.alm │ ├── v111l_1m.hea │ └── v111l_1m.mat │ ├── v113l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v113l_1m.alm │ ├── v113l_1m.hea │ └── v113l_1m.mat │ ├── v115l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v115l_1m.alm │ ├── v115l_1m.hea │ └── v115l_1m.mat │ ├── v119l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v119l_1m.alm │ ├── v119l_1m.hea │ └── v119l_1m.mat │ ├── v127l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v127l_1m.alm │ ├── v127l_1m.hea │ └── v127l_1m.mat │ ├── v131l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v131l_1m.alm │ ├── v131l_1m.hea │ └── v131l_1m.mat │ ├── v133l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v133l_1m.alm │ ├── v133l_1m.hea │ └── v133l_1m.mat │ ├── v135l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v135l_1m.alm │ ├── v135l_1m.hea │ └── v135l_1m.mat │ ├── v139l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v139l_1m.alm │ ├── v139l_1m.hea │ └── v139l_1m.mat │ ├── v141l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v141l_1m.alm │ ├── v141l_1m.hea │ └── v141l_1m.mat │ ├── v143l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v143l_1m.alm │ ├── v143l_1m.hea │ └── v143l_1m.mat │ ├── v147l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v147l_1m.alm │ ├── v147l_1m.hea │ └── v147l_1m.mat │ ├── v153l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v153l_1m.alm │ ├── v153l_1m.hea │ └── v153l_1m.mat │ ├── v155l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v155l_1m.alm │ ├── v155l_1m.hea │ └── v155l_1m.mat │ ├── v159l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v159l_1m.alm │ ├── v159l_1m.hea │ └── v159l_1m.mat │ ├── v169l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v169l_1m.alm │ ├── v169l_1m.hea │ └── v169l_1m.mat │ ├── v177l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v177l_1m.alm │ ├── v177l_1m.hea │ └── v177l_1m.mat │ ├── v179l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v179l_1m.alm │ ├── v179l_1m.hea │ └── v179l_1m.mat │ ├── v181l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v181l_1m.alm │ ├── v181l_1m.hea │ └── v181l_1m.mat │ ├── v197l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v197l_1m.alm │ ├── v197l_1m.hea │ └── v197l_1m.mat │ ├── v199l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v199l_1m.alm │ ├── v199l_1m.hea │ └── v199l_1m.mat │ ├── v201l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v201l_1m.alm │ ├── v201l_1m.hea │ └── v201l_1m.mat │ ├── v205l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v205l_1m.alm │ ├── v205l_1m.hea │ └── v205l_1m.mat │ └── v207l │ ├── RECORDS_VTVF_LIMIT-5 │ ├── v207l_1m.alm │ ├── v207l_1m.hea │ └── v207l_1m.mat ├── requirements.txt └── waveform-django ├── README.md ├── cron.py ├── export ├── __init__.py ├── apps.py ├── schema.py ├── tests.py └── urls.py ├── manage.py ├── schema.py ├── static ├── bootstrap │ ├── css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── dashboard.css │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── ie10-viewport-bug-workaround.js │ │ └── offcanvas.js ├── caliper.mp4 ├── caliper.png ├── favicon.ico ├── font-awesome │ ├── HELP-US-OUT.txt │ ├── css │ │ ├── font-awesome.css │ │ └── font-awesome.min.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── less │ │ ├── animated.less │ │ ├── bordered-pulled.less │ │ ├── core.less │ │ ├── fixed-width.less │ │ ├── font-awesome.less │ │ ├── icons.less │ │ ├── larger.less │ │ ├── list.less │ │ ├── mixins.less │ │ ├── path.less │ │ ├── rotated-flipped.less │ │ ├── screen-reader.less │ │ ├── stacked.less │ │ └── variables.less │ └── scss │ │ ├── _animated.scss │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _icons.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _screen-reader.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ └── font-awesome.scss ├── interface.mp4 ├── jquery-ui │ ├── AUTHORS.txt │ ├── LICENSE.txt │ ├── external │ │ └── jquery │ │ │ └── jquery.js │ ├── images │ │ ├── ui-icons_444444_256x240.png │ │ ├── ui-icons_555555_256x240.png │ │ ├── ui-icons_777620_256x240.png │ │ ├── ui-icons_777777_256x240.png │ │ ├── ui-icons_cc0000_256x240.png │ │ └── ui-icons_ffffff_256x240.png │ ├── index.html │ ├── jquery-ui.charts.js │ ├── jquery-ui.css │ ├── jquery-ui.js │ ├── jquery-ui.min.css │ ├── jquery-ui.min.js │ ├── jquery-ui.structure.css │ ├── jquery-ui.structure.min.css │ ├── jquery-ui.theme.css │ ├── jquery-ui.theme.min.css │ └── package.json ├── jquery │ └── jquery.min.js ├── popper │ ├── popper.min.js │ └── popper.min.js.map ├── practice-test.mp4 ├── self-assignment.mp4 └── tutorial_image.png ├── templates ├── base.html └── base_css.html ├── waveforms ├── __init__.py ├── apps.py ├── dash_apps │ └── finished_apps │ │ ├── waveform_vis.py │ │ ├── waveform_vis_adjudicate.py │ │ └── waveform_vis_tools.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_annotation_user.py │ ├── 0003_auto_20201110_1244.py │ ├── 0004_usersettings.py │ ├── 0005_auto_20210323_1427.py │ ├── 0006_user_is_admin.py │ ├── 0007_auto_20210326_1510.py │ ├── 0008_usersettings_background_color.py │ ├── 0009_usersettings_signal_std.py │ ├── 0010_auto_20210716_2244.py │ ├── 0011_invitedemails.py │ ├── 0012_usersettings_n_ekg_sigs.py │ ├── 0013_annotation_project.py │ ├── 0014_auto_20210802_2133.py │ ├── 0015_user_last_login.py │ ├── 0016_user_date_assigned.py │ ├── 0017_auto_20210918_0716.py │ ├── 0018_auto_20210919_1130.py │ ├── 0019_auto_20211019_1156.py │ ├── 0020_auto_20211025_1539.py │ ├── 0021_auto_20211030_1812.py │ ├── 0022_user_practice_status.py │ ├── 0023_auto_20211116_1221.py │ ├── 0024_user_is_adjudicator.py │ ├── 0025_annotation_is_adjudication.py │ └── __init__.py ├── models.py ├── templates │ └── waveforms │ │ ├── adjudications.html │ │ ├── adjudicator_console.html │ │ ├── admin_console.html │ │ ├── annotations.html │ │ ├── home.html │ │ ├── leaderboard.html │ │ ├── practice.html │ │ ├── settings.html │ │ └── tutorial.html ├── urls.py └── views.py └── website ├── __init__.py ├── forms.py ├── middleware.py ├── settings └── base.py ├── templates ├── registration │ ├── invite_user_email.html │ ├── invite_user_subject.txt │ ├── new_invite_email.html │ ├── new_invite_subject.txt │ ├── new_user_email.html │ ├── new_user_subject.txt │ ├── password_reset_complete.html │ ├── password_reset_confirm.html │ ├── password_reset_done.html │ ├── password_reset_email.html │ └── password_reset_form.html └── website │ ├── login.html │ └── register.html ├── tokens.py ├── urls.py ├── views.py └── wsgi.py /.env.example: -------------------------------------------------------------------------------- 1 | # Example .env file for testing 2 | 3 | DEBUG=true 4 | CACHE=false 5 | 6 | EMAIL_HOST='localhost' 7 | EMAIL_PORT=1025 8 | 9 | SECRET_KEY=secret 10 | ALLOWED_HOSTS=[] 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Django database 2 | db.sqlite3 3 | 4 | # Redis cache 5 | *.rdb 6 | 7 | # WFDB files 8 | #*.hea* 9 | #*.alm* 10 | #*.dat* 11 | #*.mat* 12 | #*.cba* 13 | 14 | # Byte-compiled / optimized / DLL files 15 | __pycache__/ 16 | 17 | # Django stuff 18 | *.log 19 | local_settings.py 20 | 21 | # Dotenv 22 | .env 23 | .venv 24 | 25 | # virtualenv 26 | env/ 27 | env3/ 28 | venv/ 29 | ENV/ 30 | 31 | # OSX .DS_Store 32 | .DS_Store 33 | 34 | # VSCode temp file 35 | .vscode/ 36 | 37 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported. All complaints will be reviewed and investigated and will result in 59 | a response that is deemed necessary and appropriate to the circumstances. The 60 | project team is obligated to maintain confidentiality with regard to the 61 | reporter of an incident. Further details of specific enforcement policies may 62 | be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # waveform-annotation 2 | Platform for annotating physiological waveform data. 3 | 4 | ## Running local instance using Django server 5 | 6 | - Install sqlite3: `sudo apt-get install sqlite3`. 7 | - Install redis for cache (or more recent version): 8 | ``` 9 | $ wget https://download.redis.io/releases/redis-6.2.6.tar.gz 10 | $ tar xzf redis-6.2.6.tar.gz 11 | $ cd redis-6.2.6 12 | $ make 13 | $ make install 14 | ``` 15 | - Create python environment with python 3.6: `python3 -m venv env`. 16 | - Activate virtual python environment: `source env/bin/activate`. 17 | - Install packages: `pip install -r requirements.txt`. 18 | - Set up environment: `cp .env.example .env`. 19 | - Within the `waveform-django` directory: 20 | - Run: `python manage.py runserver` to run the server. 21 | - You should be able to access the waveform landing page at: 22 | - To have access to the cache: 23 | - Run: `redis-server` in another terminal tab. You should be able to see the content of the website which would have been sent on the live site. If you do not run this command first before testing out the parts of the site which need cache, you will receive a `ConnectionRefusedError: [Errno 61] Connection refused` error. 24 | - If you would like to test out the email features: 25 | - Run: `python -m smtpd -n -c DebuggingServer localhost:1025` in another terminal tab. You should be able to see the content of the email which would have been sent on the live site. If you do not run this command first before testing out the email features, you will receive a `ConnectionRefusedError: [Errno 61] Connection refused` error. 26 | 27 | ## Basic server commands 28 | - To migrate new models: 29 | - Run: `python manage.py migrate --run-syncdb` 30 | - To reset the database: 31 | - Run: `python manage.py flush` 32 | - After finished, deactivate virtual python environment: `deactivate` 33 | 34 | ## Viewing current annotations in database 35 | 36 | - Using GraphQL API: Go to or other desired query as seen here ... 37 | - Using SQLite3: `cd waveform-django`, `sqlite3 db.sqlite3`, then `select * from waveforms_annotation;` 38 | -------------------------------------------------------------------------------- /backups/README.md: -------------------------------------------------------------------------------- 1 | # Will store all of the backups for the annotations 2 | -------------------------------------------------------------------------------- /db/README.md: -------------------------------------------------------------------------------- 1 | This directory holds the db.sqlite3 file, which will be created after the first use of 'python manage.py runserver' -------------------------------------------------------------------------------- /debug/debug.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/debug/debug.log -------------------------------------------------------------------------------- /deploy/etc/uwsgi.d/waveform_annotation.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | plugins-dir = /usr/local/uwsgi/plugins/python 3 | plugin = python3 4 | chdir = /var/www/vhosts/breadboard-lcp.mit.edu/Flask/waveform-annotation/waveform-django 5 | module = website.wsgi:application 6 | home = /var/www/vhosts/breadboard-lcp.mit.edu/Flask/waveform-annotation/env3 7 | #binary-path = /var/www/vhosts/breadboard-lcp.mit.edu/Flask/waveform-annotation/env3/bin/uwsgi 8 | 9 | master = true 10 | processes = 2 11 | 12 | socket = /etc/uwsgi.sockets/waveform-annotation_uwsgi.sock 13 | chmod-socket = 666 14 | 15 | vacuum = true 16 | 17 | gid = publicusers 18 | 19 | #callable = app 20 | 21 | #logto = /var/log/uwsgi/%n.log 22 | req-logger = file:/var/log/uwsgi/%n-req.log 23 | logger = file:/var/log/uwsgi/%n.log 24 | 25 | need-app = true 26 | 27 | die-on-term = true 28 | 29 | env = DJANGO_SETTINGS_MODULE=website.settings.base 30 | -------------------------------------------------------------------------------- /deploy/nginx/sites_enabled/breadboard_nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen *:443 ssl http2; 3 | listen 192.168.11.160:80; 4 | 5 | server_name breadboard-lcp.mit.edu;#, lcp-holyoke-web.mit.edu; 6 | 7 | include /etc/nginx/conf.d/ssl; 8 | 9 | client_max_body_size 5M; 10 | error_log /var/log/nginx/breadboard_error.log warn; 11 | access_log /var/log/nginx/breadboard_access.log; 12 | 13 | root /var/www/vhosts/breadboard-lcp.mit.edu/html/; #Flask/lcp-website; 14 | 15 | location / { 16 | port_in_redirect off; 17 | } 18 | 19 | location /lcp_dev/ { 20 | include uwsgi_params; 21 | uwsgi_pass unix:/etc/uwsgi.sockets/lcp_development_breadboard.sock; 22 | #/etc/uwsgi.sockets/lcp-website.sock; 23 | } 24 | location /static/ { 25 | alias /var/www/vhosts/breadboard-lcp.mit.edu/Flask/lcp-website/static/; 26 | try_files $uri @ann_static; 27 | } 28 | location @ann_static { 29 | root /var/www/vhosts/breadboard-lcp.mit.edu/Flask/annotation/static/; 30 | } 31 | location /annotation/ { 32 | include uwsgi_params; 33 | uwsgi_pass unix:/etc/uwsgi.sockets/annotation_uwsgi.sock; 34 | } 35 | location /annotation/static/ { 36 | alias /var/www/vhosts/breadboard-lcp.mit.edu/Flask/annotation/static/; 37 | } 38 | location /label-studio/ { 39 | include uwsgi_params; 40 | uwsgi_pass unix:/etc/uwsgi.sockets/label-studio.sock; 41 | } 42 | location /label-studio/static/ { 43 | alias /var/www/vhosts/breadboard-lcp.mit.edu/Flask/label-studio/backend/static/; 44 | } 45 | location /waveform-annotation/ { 46 | include uwsgi_params; 47 | uwsgi_pass unix:/etc/uwsgi.sockets/waveform-annotation_uwsgi.sock; 48 | } 49 | location /waveform-annotation/static { 50 | alias /var/www/vhosts/breadboard-lcp.mit.edu/Flask/waveform-annotation/static; 51 | } 52 | 53 | 54 | include /etc/nginx/fastcgi.conf; 55 | ssi on; 56 | include /etc/nginx/mime.types_custom; 57 | #types { 58 | #text/html html htm shtml inc css js; 59 | #} 60 | location /old_lcp/ { 61 | ssi on; 62 | index index.shtml; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /deploy/post-receive: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | install_dir=/var/www/vhosts/breadboard-lcp.mit.edu/Flask/waveform-annotation 6 | log_file=/var/log/github/update.log 7 | branch=main 8 | 9 | # If the following commands fail, it is too late to abort the push. 10 | # Try to avoid doing things that might fail! 11 | 12 | echo "$(date '+%F %T %z'): $branch: post-receive started" >> $log_file 13 | exec 3>&1 14 | exec &> >(tee -a $log_file) 15 | 16 | # Install into /var/www/vhosts/breadboard.mit.edu/Flask/waveform-annotation 17 | echo "* Installing new server code..." 18 | GIT_WORK_TREE=$install_dir git checkout --force "$branch" 2>> $log_file 19 | 20 | # Restart the uwsgi server 21 | echo "* Reloading uwsgi server..." 22 | # Could do 'sudo symstemctl restart uwsgi.service' but floods logs 23 | sudo touch /etc/uwsgi.d/waveform_annotation.ini 24 | 25 | echo "$(date '+%F %T %z'): $branch: post-receive finished" >> $log_file 26 | echo >> $log_file 27 | -------------------------------------------------------------------------------- /record-files/merge_new_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Merge one folder's records into another 3 | folder_new="test_data" 4 | folder_merge_into="2015_data" 5 | 6 | # Create the RECORDS files 7 | # cd folder_new 8 | # ./top_records.sh 9 | # ./sub_records.sh 10 | # ./filter_anns.sh 11 | # cd .. 12 | 13 | # Copy all of the records directories and events 14 | find $folder_new/ -mindepth 1 -type d | xargs -I {} cp -R {} $folder_merge_into/ 15 | 16 | # Merge all of the RECORDS files 17 | all_records_files=$(find test_data/RECORDS* -type f) 18 | for current_record_path in $all_records_files; do 19 | current_record=$(echo $current_record_path | cut -d / -f2) 20 | cat $current_record_path >> $folder_merge_into/$current_record 21 | done 22 | -------------------------------------------------------------------------------- /record-files/sample_data/README.md: -------------------------------------------------------------------------------- 1 | # record-files 2 | The directory to download remote record files from the 2015 Challenge data and filter for desired events. 3 | 4 | ## get_anns.sh 5 | 6 | - Download the remote files from the 2015 Challenge data hosted on the PhysioNet archive site. 7 | - Replace the "username" and "password" string variables with your actual username and password for the archive site. 8 | - The "Challenge2015ReferenceAnnotations" project should be added to your account so that you can access the files, if not, contact Benjamin Moody for more details on how to set that up. 9 | 10 | ## filter_anns.sh 11 | 12 | - To filter for different signal types, adjust the "ann_types" list variable and change the name of the "out_record_name" string variable to reflect the new output RECORDS file 13 | - If you create a new "out_record_name" and subsequent new RECORDS file, you must change the "RECORDS_FILE" variable in the [settings](https://github.com/MIT-LCP/waveform-annotation/blob/57ac6fbd28cc0ae926daa88dce98776a9826e5de/waveform-django/website/settings/base.py) to update the annotator. 14 | 15 | ## subfolder.sh 16 | 17 | - All of the downloaded data will probably be in only directory which is not desirable. 18 | - Run this shell script to create directories based on the the name of the file (the record name). 19 | 20 | ## sub_records.sh 21 | 22 | - After all of the files have been downloaded and the correct subfolders have been created, create a RECORDS file for each subfolder. 23 | 24 | ## top_records.sh 25 | 26 | - After all of the subfolders have been created, create a top-level directory RECORDS file which references each subfolder with a RECORDS file. 27 | -------------------------------------------------------------------------------- /record-files/sample_data/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v101l 2 | v111l 3 | v113l 4 | v115l 5 | v119l 6 | v127l 7 | v131l 8 | v133l 9 | v135l 10 | v139l 11 | v141l 12 | v143l 13 | v147l 14 | v153l 15 | v155l 16 | v159l 17 | v169l 18 | v177l 19 | v179l 20 | v181l 21 | v197l 22 | v199l 23 | v201l 24 | v205l 25 | v207l 26 | -------------------------------------------------------------------------------- /record-files/sample_data/filter_anns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # All the desired types of annotations 3 | records_file="RECORDS_VTVF_LIMIT-5" 4 | limit=5 5 | saved_events=$(tail -n +2 all_anns.csv | cut -d , -f4 | sort | uniq) 6 | 7 | # Get all of the folders with the desired annotation type 8 | folders=$(grep -r --include="*.hea" . | cut -d : -f1 | cut -d / -f1 | uniq | sort -n -t e -k2) 9 | echo $folders | tr " " "\n" > $records_file 10 | 11 | # Clear list of events that can be assigned 12 | for folder in $folders; do 13 | echo $folder > $folder/$records_file 14 | done 15 | 16 | # Give priority to events that already have annotations 17 | saved_events=($saved_events) 18 | for folder in $folders; do 19 | rec_events=() 20 | for e in "${saved_events[@]}"; do 21 | [[ $e == "$folder"* ]] && rec_events+=("$e") 22 | done 23 | shuffled_events=$(shuf -n $limit -e ${rec_events[@]} | sort) 24 | for event in $shuffled_events; do 25 | echo $event >> $folder/$records_file 26 | done 27 | done 28 | 29 | # Randomly select events such that only 30 | for folder in $folders; do 31 | # Create a new RECORDS file for each subfolder 32 | lines=$(wc -l $folder/$records_file | cut -d ' ' -f1) 33 | if [ $lines -le $limit ]; then 34 | add=$(expr $limit - $lines + 1) 35 | events=$(grep -ir --include="*.hea" "$ann_types" $folder | cut -d : -f1 | cut -d / -f2 | cut -d . -f1 | sort -n -t _ -k2 | uniq) 36 | n_events=$(echo $events | tr " " "\n" | wc -l) 37 | if [[ $lines -eq 1 || $n_events -le $limit ]]; then 38 | echo $folder > $folder/$records_file 39 | shuf -n $add -e $events | sort >> $folder/$records_file 40 | else 41 | present=$(tail -n +2 $folder/$records_file) 42 | rand=$(shuf -n $add -e $events) 43 | assign_set=$(echo $present $rand) 44 | repeats=$(echo $assign_set | tr ' ' '\n' | sort -rn | uniq -c | sort -rn | head -n 1 | cut -d' ' -f7) 45 | while [ $repeats -gt 1 ]; do 46 | rand=$(shuf -n $add -e $events) 47 | assign_set=$(echo $present $rand) 48 | assign_len=$(echo $assign_set | wc -w) 49 | if [ $assign_len -le $limit ]; then 50 | break 51 | fi 52 | repeats=$(echo $assign_set | tr ' ' '\n' | sort -rn | uniq -c | sort -rn | head -n 1 | cut -d' ' -f7) 53 | done 54 | echo $rand | tr ' ' '\n' | sort >> $folder/$records_file 55 | fi 56 | fi 57 | # Add the folder name to the top of the RECORDS file 58 | done 59 | -------------------------------------------------------------------------------- /record-files/sample_data/get_anns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Get folder .hea and .cba files based on record name 3 | # Replace &&& with username and $$$ with password 4 | username = &&&; 5 | password = $$$; 6 | for folder in */; 7 | do 8 | cd $folder; 9 | wget --http-user $username --http-password $password https://archive.physionet.org/works/Challenge2015ReferenceAnnotations/files/ge/${folder%%/}.hea; 10 | wget --http-user $username --http-password $password https://archive.physionet.org/works/Challenge2015ReferenceAnnotations/files/ge/${folder%%/}.cba; 11 | cd ..; 12 | done 13 | -------------------------------------------------------------------------------- /record-files/sample_data/sub_records.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Create RECORDS file for each subfolder based on record names 3 | records_file="RECORDS_VTVF_LIMIT-5" 4 | for folder in */; do 5 | cd $folder 6 | rm -f $records_file 7 | for record in `ls -v1 *\.hea | sort -n -t'_' -k2 | cut -d "." -f1`; do 8 | echo $record 9 | done > $records_file 10 | cd .. 11 | done 12 | -------------------------------------------------------------------------------- /record-files/sample_data/subfolder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Create subfolders based on record name 3 | for file in *; 4 | do 5 | dir = $(echo $file | cut -d"_" -f1); 6 | echo $dir; 7 | mkdir -p $dir; 8 | mv $file $dir; 9 | done 10 | -------------------------------------------------------------------------------- /record-files/sample_data/top_records.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Create top-level RECORDS file based on folders 3 | records_file="RECORDS_VTVF_LIMIT-5" 4 | rm -f $records_file 5 | for folder in `ls -d */ | sort -n -k1.3`; do 6 | echo ${folder%%/} 7 | done > $records_file 8 | -------------------------------------------------------------------------------- /record-files/sample_data/user_assignments.csv: -------------------------------------------------------------------------------- 1 | Events,Users Assigned 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v101l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v101l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v101l/v101l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v101l/v101l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v101l/v101l_1m.hea: -------------------------------------------------------------------------------- 1 | v101l 4 250 82500 2 | v101l_1m.mat 16+24 2277/mV 16 0 -90 13816 0 II 3 | v101l_1m.mat 16+24 1849/mV 16 0 -145 30667 0 V 4 | v101l_1m.mat 16+24 1251/NU 16 0 2160 -7937 0 PLETH 5 | v101l_1m.mat 16+24 3.886e+04/NU 16 0 -452 -11354 0 RESP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v101l/v101l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v101l/v101l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v111l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v111l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v111l/v111l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v111l/v111l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v111l/v111l_1m.hea: -------------------------------------------------------------------------------- 1 | v111l 4 250 82500 2 | v111l_1m.mat 16+24 4017/mV 16 0 2628 1645 0 II 3 | v111l_1m.mat 16+24 4692/mV 16 0 1906 3311 0 aVF 4 | v111l_1m.mat 16+24 8387/NU 16 0 2269 -6081 0 PLETH 5 | v111l_1m.mat 16+24 4106/NU 16 0 3865 30114 0 RESP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v111l/v111l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v111l/v111l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v113l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v113l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v113l/v113l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v113l/v113l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v113l/v113l_1m.hea: -------------------------------------------------------------------------------- 1 | v113l 3 250 82500 2 | v113l_1m.mat 16+24 6143/mV 16 0 3120 -311 0 II 3 | v113l_1m.mat 16+24 6567/mV 16 0 1034 -3107 0 V 4 | v113l_1m.mat 16+24 7080/NU 16 0 3526 28389 0 PLETH 5 | #Ventricular_Tachycardia 6 | #False alarm 7 | -------------------------------------------------------------------------------- /record-files/sample_data/v113l/v113l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v113l/v113l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v115l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v115l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v115l/v115l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v115l/v115l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v115l/v115l_1m.hea: -------------------------------------------------------------------------------- 1 | v115l 4 250 82500 2 | v115l_1m.mat 16+24 1248/mV 16 0 807 -10133 0 II 3 | v115l_1m.mat 16+24 3483/mV 16 0 -673 27418 0 V 4 | v115l_1m.mat 16+24 1568/NU 16 0 1615 19117 0 PLETH 5 | v115l_1m.mat 16+24 1.178e+04/mmHg 16 0 32767 -16964 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v115l/v115l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v115l/v115l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v119l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v119l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v119l/v119l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v119l/v119l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v119l/v119l_1m.hea: -------------------------------------------------------------------------------- 1 | v119l 4 250 82500 2 | v119l_1m.mat 16+24 1.21e+04/mV 16 0 359 -26989 0 II 3 | v119l_1m.mat 16+24 1.395e+04/mV 16 0 -1140 -4343 0 V 4 | v119l_1m.mat 16+24 6551/NU 16 0 -1622 -6629 0 PLETH 5 | v119l_1m.mat 16+24 556.2/mmHg 16 0 8676 -5995 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v119l/v119l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v119l/v119l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v127l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v127l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v127l/v127l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v127l/v127l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v127l/v127l_1m.hea: -------------------------------------------------------------------------------- 1 | v127l 3 250 82500 2 | v127l_1m.mat 16+24 1.413e+04/mV 16 0 4857 -9446 0 II 3 | v127l_1m.mat 16+24 2.33e+04/mV 16 0 15964 18760 0 V 4 | v127l_1m.mat 16+24 8289/NU 16 0 2828 -13181 0 PLETH 5 | #Ventricular_Tachycardia 6 | #False alarm 7 | -------------------------------------------------------------------------------- /record-files/sample_data/v127l/v127l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v127l/v127l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v131l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v131l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v131l/v131l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v131l/v131l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v131l/v131l_1m.hea: -------------------------------------------------------------------------------- 1 | v131l 4 250 82500 2 | v131l_1m.mat 16+24 9218/mV 16 0 2679 18547 0 II 3 | v131l_1m.mat 16+24 5420/mV 16 0 5251 -26008 0 V 4 | v131l_1m.mat 16+24 52.65/mmHg 16 0 6663 -21133 0 ABP 5 | v131l_1m.mat 16+24 6470/NU 16 0 1675 -16861 0 RESP 6 | #Ventricular_Tachycardia 7 | #True alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v131l/v131l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v131l/v131l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v133l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v133l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v133l/v133l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v133l/v133l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v133l/v133l_1m.hea: -------------------------------------------------------------------------------- 1 | v133l 4 250 82500 2 | v133l_1m.mat 16+24 1.551e+04/mV 16 0 3438 -11850 0 II 3 | v133l_1m.mat 16+24 1.008e+04/mV 16 0 2682 18451 0 MCL 4 | v133l_1m.mat 16+24 80.12/mmHg 16 0 5946 -14214 0 ABP 5 | v133l_1m.mat 16+24 5338/NU 16 0 5192 30509 0 RESP 6 | #Ventricular_Tachycardia 7 | #True alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v133l/v133l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v133l/v133l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v135l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v135l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v135l/v135l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v135l/v135l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v135l/v135l_1m.hea: -------------------------------------------------------------------------------- 1 | v135l 4 250 82500 2 | v135l_1m.mat 16+24 4390/mV 16 0 -22 26341 0 II 3 | v135l_1m.mat 16+24 5513/mV 16 0 55 -20863 0 V 4 | v135l_1m.mat 16+24 5248/NU 16 0 -2859 -6447 0 PLETH 5 | v135l_1m.mat 16+24 41.44/mmHg 16 0 5068 -21327 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v135l/v135l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v135l/v135l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v139l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v139l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v139l/v139l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v139l/v139l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v139l/v139l_1m.hea: -------------------------------------------------------------------------------- 1 | v139l 3 250 82500 2 | v139l_1m.mat 16+24 3763/mV 16 0 -59 18369 0 II 3 | v139l_1m.mat 16+24 2771/mV 16 0 435 -856 0 V 4 | v139l_1m.mat 16+24 6826/NU 16 0 3935 -21951 0 PLETH 5 | #Ventricular_Tachycardia 6 | #True alarm 7 | -------------------------------------------------------------------------------- /record-files/sample_data/v139l/v139l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v139l/v139l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v141l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v141l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v141l/v141l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v141l/v141l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v141l/v141l_1m.hea: -------------------------------------------------------------------------------- 1 | v141l 4 250 82500 2 | v141l_1m.mat 16+24 3402/mV 16 0 253 20096 0 II 3 | v141l_1m.mat 16+24 2412/mV 16 0 -1636 14986 0 V 4 | v141l_1m.mat 16+24 5610/NU 16 0 0 -25063 0 PLETH 5 | v141l_1m.mat 16+24 1.681/mmHg 16 0 0 3720 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v141l/v141l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v141l/v141l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v143l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v143l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v143l/v143l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v143l/v143l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v143l/v143l_1m.hea: -------------------------------------------------------------------------------- 1 | v143l 4 250 82500 2 | v143l_1m.mat 16+24 3451/mV 16 0 179 5801 0 II 3 | v143l_1m.mat 16+24 962.7/mV 16 0 -1656 -6543 0 V 4 | v143l_1m.mat 16+24 608.3/NU 16 0 -1349 5433 0 PLETH 5 | v143l_1m.mat 16+24 679.4/NU 16 0 696 -15417 0 RESP 6 | #Ventricular_Tachycardia 7 | #True alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v143l/v143l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v143l/v143l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v147l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v147l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v147l/v147l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v147l/v147l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v147l/v147l_1m.hea: -------------------------------------------------------------------------------- 1 | v147l 4 250 82500 2 | v147l_1m.mat 16+24 5595/mV 16 0 -263 16754 0 II 3 | v147l_1m.mat 16+24 5998/mV 16 0 104 1098 0 V 4 | v147l_1m.mat 16+24 1760/NU 16 0 -157 3120 0 PLETH 5 | v147l_1m.mat 16+24 1.09e+04/mmHg 16 0 -13499 10031 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v147l/v147l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v147l/v147l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v153l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v153l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v153l/v153l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v153l/v153l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v153l/v153l_1m.hea: -------------------------------------------------------------------------------- 1 | v153l 4 250 82500 2 | v153l_1m.mat 16+24 3029/mV 16 0 142 -26439 0 II 3 | v153l_1m.mat 16+24 2431/mV 16 0 0 0 0 V 4 | v153l_1m.mat 16+24 1456/NU 16 0 1443 23468 0 PLETH 5 | v153l_1m.mat 16+24 2.051e+04/NU 16 0 390 -8424 0 RESP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v153l/v153l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v153l/v153l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v155l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v155l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v155l/v155l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v155l/v155l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v155l/v155l_1m.hea: -------------------------------------------------------------------------------- 1 | v155l 4 250 82500 2 | v155l_1m.mat 16+24 2872/mV 16 0 139 -19950 0 II 3 | v155l_1m.mat 16+24 2261/mV 16 0 0 0 0 V 4 | v155l_1m.mat 16+24 1465/NU 16 0 3221 -9699 0 PLETH 5 | v155l_1m.mat 16+24 2.024e+04/NU 16 0 212 7086 0 RESP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v155l/v155l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v155l/v155l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v159l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v159l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v159l/v159l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v159l/v159l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v159l/v159l_1m.hea: -------------------------------------------------------------------------------- 1 | v159l 3 250 82500 2 | v159l_1m.mat 16+24 1.121e+04/mV 16 0 8122 -5556 0 II 3 | v159l_1m.mat 16+24 1.121e+04/mV 16 0 3974 2299 0 V 4 | v159l_1m.mat 16+24 8042/NU 16 0 2145 -25663 0 PLETH 5 | #Ventricular_Tachycardia 6 | #True alarm 7 | -------------------------------------------------------------------------------- /record-files/sample_data/v159l/v159l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v159l/v159l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v169l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v169l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v169l/v169l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v169l/v169l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v169l/v169l_1m.hea: -------------------------------------------------------------------------------- 1 | v169l 4 250 82500 2 | v169l_1m.mat 16+24 6622/mV 16 0 -588 -17976 0 II 3 | v169l_1m.mat 16+24 5895/mV 16 0 200 10974 0 V 4 | v169l_1m.mat 16+24 1648/NU 16 0 3503 -29253 0 PLETH 5 | v169l_1m.mat 16+24 41.72/mmHg 16 0 2838 -7992 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v169l/v169l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v169l/v169l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v177l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v177l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v177l/v177l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v177l/v177l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v177l/v177l_1m.hea: -------------------------------------------------------------------------------- 1 | v177l 4 250 82500 2 | v177l_1m.mat 16+24 2843/mV 16 0 -210 -17361 0 II 3 | v177l_1m.mat 16+24 2752/mV 16 0 -306 2463 0 III 4 | v177l_1m.mat 16+24 1363/NU 16 0 1424 -15387 0 PLETH 5 | v177l_1m.mat 16+24 54.83/mmHg 16 0 2625 30540 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v177l/v177l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v177l/v177l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v179l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v179l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v179l/v179l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v179l/v179l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v179l/v179l_1m.hea: -------------------------------------------------------------------------------- 1 | v179l 4 250 82500 2 | v179l_1m.mat 16+24 1297/mV 16 0 69 1573 0 II 3 | v179l_1m.mat 16+24 4072/mV 16 0 32767 23710 0 V 4 | v179l_1m.mat 16+24 4267/NU 16 0 11229 18112 0 PLETH 5 | v179l_1m.mat 16+24 55.29/mmHg 16 0 2511 30381 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v179l/v179l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v179l/v179l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v181l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v181l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v181l/v181l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v181l/v181l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v181l/v181l_1m.hea: -------------------------------------------------------------------------------- 1 | v181l 4 250 82500 2 | v181l_1m.mat 16+24 5815/mV 16 0 -316 -3073 0 II 3 | v181l_1m.mat 16+24 4415/mV 16 0 -514 21361 0 V 4 | v181l_1m.mat 16+24 1032/NU 16 0 332 -2766 0 PLETH 5 | v181l_1m.mat 16+24 152.2/mmHg 16 0 4032 29354 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v181l/v181l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v181l/v181l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v197l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v197l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v197l/v197l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v197l/v197l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v197l/v197l_1m.hea: -------------------------------------------------------------------------------- 1 | v197l 4 250 82500 2 | v197l_1m.mat 16+24 1.226e+04/mV 16 0 5000 -3854 0 II 3 | v197l_1m.mat 16+24 1.55e+04/mV 16 0 8995 -32680 0 V 4 | v197l_1m.mat 16+24 67.45/mmHg 16 0 3531 -22035 0 ABP 5 | v197l_1m.mat 16+24 3.014e+04/NU 16 0 6856 12628 0 RESP 6 | #Ventricular_Tachycardia 7 | #True alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v197l/v197l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v197l/v197l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v199l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v199l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v199l/v199l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v199l/v199l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v199l/v199l_1m.hea: -------------------------------------------------------------------------------- 1 | v199l 3 250 82500 2 | v199l_1m.mat 16+24 7912/mV 16 0 4821 22366 0 II 3 | v199l_1m.mat 16+24 3155/mV 16 0 2859 101 0 V 4 | v199l_1m.mat 16+24 8549/NU 16 0 3554 24814 0 PLETH 5 | #Ventricular_Tachycardia 6 | #True alarm 7 | -------------------------------------------------------------------------------- /record-files/sample_data/v199l/v199l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v199l/v199l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v201l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v201l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v201l/v201l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v201l/v201l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v201l/v201l_1m.hea: -------------------------------------------------------------------------------- 1 | v201l 4 250 82500 2 | v201l_1m.mat 16+24 1.522e+04/mV 16 0 -454 -19639 0 II 3 | v201l_1m.mat 16+24 1.13e+04/mV 16 0 170 8426 0 V 4 | v201l_1m.mat 16+24 5885/NU 16 0 0 19027 0 PLETH 5 | v201l_1m.mat 16+24 81.52/mmHg 16 0 5732 19835 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v201l/v201l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v201l/v201l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v205l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v205l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v205l/v205l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v205l/v205l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v205l/v205l_1m.hea: -------------------------------------------------------------------------------- 1 | v205l 4 250 82500 2 | v205l_1m.mat 16+24 2478/mV 16 0 1065 -3562 0 II 3 | v205l_1m.mat 16+24 1622/mV 16 0 -547 18812 0 V 4 | v205l_1m.mat 16+24 2063/NU 16 0 3283 -8051 0 PLETH 5 | v205l_1m.mat 16+24 56.81/mmHg 16 0 3801 13505 0 ABP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v205l/v205l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v205l/v205l_1m.mat -------------------------------------------------------------------------------- /record-files/sample_data/v207l/RECORDS_VTVF_LIMIT-5: -------------------------------------------------------------------------------- 1 | v207l_1m 2 | -------------------------------------------------------------------------------- /record-files/sample_data/v207l/v207l_1m.alm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v207l/v207l_1m.alm -------------------------------------------------------------------------------- /record-files/sample_data/v207l/v207l_1m.hea: -------------------------------------------------------------------------------- 1 | v207l 4 250 82500 2 | v207l_1m.mat 16+24 283.6/mV 16 0 1131 -19834 0 II 3 | v207l_1m.mat 16+24 820.5/mV 16 0 -1000 -14390 0 V 4 | v207l_1m.mat 16+24 1462/NU 16 0 1804 -25400 0 PLETH 5 | v207l_1m.mat 16+24 1.954e+04/NU 16 0 171 -30167 0 RESP 6 | #Ventricular_Tachycardia 7 | #False alarm 8 | -------------------------------------------------------------------------------- /record-files/sample_data/v207l/v207l_1m.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/record-files/sample_data/v207l/v207l_1m.mat -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | #### Requirements for running the server #### 2 | chardet==3.0.4 \ 3 | --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 4 | Django==2.2.13 \ 5 | --hash=sha256:e8fe3c2b2212dce6126becab7a693157f1a441a07b62ec994c046c76af5bb66d \ 6 | --hash=sha256:84f370f6acedbe1f3c41e1a02de44ac206efda3355e427139ecb785b5f596d80 7 | django-autocomplete-light==3.3.4 \ 8 | --hash=sha256:cff0b1cad0e233e49c8cce08dff22868951123cbb79a7c1768eda78845044568 9 | django-background-tasks==1.2.0 \ 10 | --hash=sha256:35a9a54961f3e4486ab2f9482d1e8ac63ab4f47e5e0b7e654a22f7002299ffae 11 | django-ckeditor==5.7.1 \ 12 | --hash=sha256:0147f8905dc64747e45157a185feedee4e39973fa4b571c9c82ad10d9d4b8974 13 | Pillow==7.0.0 \ 14 | --hash=sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9 \ 15 | --hash=sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f \ 16 | --hash=sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be \ 17 | --hash=sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946 18 | python-decouple==3.1 \ 19 | --hash=sha256:1317df14b43efee4337a4aa02914bf004f010cd56d6c4bd894e6474ec8c4fe2d 20 | pytz==2018.3 \ 21 | --hash=sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd 22 | django-crontab==0.7.1 \ 23 | --hash=sha256:1201810a212460aaaa48eb6a766738740daf42c1a4f6aafecfb1525036929236 \ 24 | --hash=sha256:64e9aa766220173aae5e4f027ed83a834886676004083de10501b4868154c49e 25 | 26 | #### Requirements for development and testing #### 27 | django-debug-toolbar==1.9.1 \ 28 | --hash=sha256:4af2a4e1e932dadbda197b18585962d4fc20172b4e5a479490bc659fe998864d 29 | requests==2.26.0 \ 30 | --hash=sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24 31 | 32 | #### Indirect requirements #### 33 | six==1.12.0 \ 34 | --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c 35 | 36 | # required by Django 2.2.3 37 | sqlparse==0.2.4 \ 38 | --hash=sha256:d9cf190f51cbb26da0412247dfe4fb5f4098edb73db84e02f9fc21fdca31fed4 39 | 40 | # required by django-ckeditor 5.7.1 41 | django-js-asset==0.1.1 \ 42 | --hash=sha256:0dd2c5f64f2b24eb8a7270a6a59cb914a03f205335bd0eb6207bf61cf7410828 43 | 44 | # required by django-background-tasks 1.2.0 45 | django-compat==1.0.15 \ 46 | --hash=sha256:3ac9a3bedc56b9365d9eb241bc5157d0c193769bf995f9a78dc1bc24e7c2331b 47 | 48 | # required by requests 2.21.0 49 | certifi==2019.6.16 \ 50 | --hash=sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939 51 | idna==2.8 \ 52 | --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c 53 | urllib3==1.24.3 \ 54 | --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb 55 | 56 | # required for wfdb 57 | wfdb==3.1.0 \ 58 | --hash=sha256:0c28d1be15c6202309ac07ceafe83820ec8fe793d91cef978b6388e8b9a85771 59 | cycler==0.10.0 \ 60 | --hash=sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d 61 | joblib==0.16.0 \ 62 | --hash=sha256:d348c5d4ae31496b2aa060d6d9b787864dd204f9480baaa52d18850cb43e9f49 63 | kiwisolver==1.2.0 \ 64 | --hash=sha256:c31bc3c8e903d60a1ea31a754c72559398d91b5929fcb329b1c3a3d3f6e72113 \ 65 | --hash=sha256:603162139684ee56bcd57acc74035fceed7dd8d732f38c0959c8bd157f913fec 66 | matplotlib==3.3.0 \ 67 | --hash=sha256:ebb6168c9330309b1f3360d36c481d8cd621a490cf2a69c9d6625b2a76777c12 \ 68 | --hash=sha256:19cf4db0272da286863a50406f6430101af129f288c421b1a7f33ddfc8d0180f 69 | mne==0.20.7 \ 70 | --hash=sha256:c6aea11d7b3a37f6ad8ca63c177b311a4eb3f057f995fe0417b8535dadfd35a9 71 | nose==1.3.7 \ 72 | --hash=sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac 73 | numpy==1.19.1 \ 74 | --hash=sha256:e45f8e981a0ab47103181773cc0a54e650b2aef8c7b6cd07405d0fa8d869444a \ 75 | --hash=sha256:ef71a1d4fd4858596ae80ad1ec76404ad29701f8ca7cdcebc50300178db14dfc \ 76 | --hash=sha256:b8456987b637232602ceb4d663cb34106f7eb780e247d51a260b84760fd8f491 77 | pandas==1.1.0 \ 78 | --hash=sha256:0bc440493cf9dc5b36d5d46bbd5508f6547ba68b02a28234cd8e81fdce42744d \ 79 | --hash=sha256:16504f915f1ae424052f1e9b7cd2d01786f098fbb00fa4e0f69d42b22952d798 \ 80 | --hash=sha256:b39508562ad0bb3f384b0db24da7d68a2608b9ddc85b1d931ccaaa92d5e45273 81 | pyparsing==2.4.7 \ 82 | --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b 83 | python-dateutil==2.8.1 \ 84 | --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a 85 | scikit-learn==0.23.1 \ 86 | --hash=sha256:0c3464e46ef8bd4f1bfa5c009648c6449412c8f7e9b3fc0c9e3d800139c48827 \ 87 | --hash=sha256:0e7b55f73b35537ecd0d19df29dd39aa9e076dba78f3507b8136c819d84611fd 88 | scipy==1.5.2 \ 89 | --hash=sha256:fc98f3eac993b9bfdd392e675dfe19850cc8c7246a8fd2b42443e506344be7d9 \ 90 | --hash=sha256:eecf40fa87eeda53e8e11d265ff2254729d04000cd40bae648e76ff268885d66 \ 91 | --hash=sha256:066c513d90eb3fd7567a9e150828d39111ebd88d3e924cdfc9f8ce19ab6f90c9 92 | sklearn==0.0 \ 93 | --hash=sha256:e23001573aa194b834122d2b9562459bf5ae494a2d59ca6b8aa22c85a44c0e31 94 | threadpoolctl==2.1.0 \ 95 | --hash=sha256:38b74ca20ff3bb42caca8b00055111d74159ee95c4370882bbff2b93d24da725 96 | 97 | # required for data visualization 98 | django_plotly_dash==1.6.6 \ 99 | --hash=sha256:fbb844292237416983f38a01cfb531154abefe8d4bd78994bba8ff03c9b77876 100 | dpd-components==0.1.0 \ 101 | --hash=sha256:613a6b17d3d7dd449be060e739e4ce36692b46fa012c3a86ee947f6337d09548 102 | dash-html-components==1.1.3 \ 103 | --hash=sha256:88adb77a674d5d7d0835d71c469f6e7b4aa692f9673808a474d244b71863c58a 104 | dash==1.20.0 \ 105 | --hash=sha256:127c16f71d3c8345dd29ab2aed099330aafd6d558734bec5bbcccadd0a7e6b29 106 | dash-core-components==1.16.0 \ 107 | --hash=sha256:e8cdfaf3580577670bb2d1c3168efa06f5a7b439fbe5527cfaefa3e32394542f 108 | dash-renderer==1.9.1 \ 109 | --hash=sha256:73a69e3d145880e68e42723ad10182251d92b44f3efe92b8763145cfd2158e7e 110 | dash-table==4.11.3 \ 111 | --hash=sha256:0a4f22a5cf5120882a252a3348fc15ef45a1b75bf900934783e338aceac52f56 112 | flask==1.1.2 \ 113 | --hash=sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557 114 | plotly==5.4.0 \ 115 | --hash=sha256:1e5c1a5c87caaf68ce0d9872d4636e3ce1f82c7f6988eb20905ff5b58e57525c 116 | 117 | # required by dash 118 | flask-compress==1.5.0 \ 119 | --hash=sha256:f367b2b46003dd62be34f7fb1379938032656dca56377a9bc90e7188e4289a7c 120 | future==0.18.2 \ 121 | --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d 122 | Jinja2==2.11.2 \ 123 | --hash=sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035 124 | itsdangerous==1.1.0 \ 125 | --hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749 126 | Werkzeug==1.0.1 \ 127 | --hash=sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43 128 | click==7.1.2 \ 129 | --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc 130 | retrying==1.3.3 \ 131 | --hash=sha256:08c039560a6da2fe4f2c426d0766e284d3b736e355f8dd24b37367b0bb41973b 132 | 133 | # required for dash cache 134 | django-redis==5.2.0 \ 135 | --hash=sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026 136 | redis==4.1.1 \ 137 | --hash=sha256:bc97d18938ca18d66737d0ef88584a2073069589e4026813cfba9ad6df9a9f40 138 | Deprecated==1.2.13 \ 139 | --hash=sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d 140 | packaging==21.3 \ 141 | --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 142 | wrapt==1.13.3 \ 143 | --hash=sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9 \ 144 | --hash=sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7 145 | 146 | # required by flask-compress 147 | brotli==1.0.7 \ 148 | --hash=sha256:71ceee286ea7ec613f1c36f1c6181864a6ca24ebb55e371276f33d6af8742834 \ 149 | --hash=sha256:f192e6d3556714105c10486bbd6d045e38a0c04d9da3cef21e0a8dfd8e162df4 150 | 151 | # required by Jinja2 152 | MarkupSafe==1.1.1 \ 153 | --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ 154 | --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ 155 | --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 156 | 157 | # required by GraphQL API 158 | aniso8601==7.0.0 \ 159 | --hash=sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b 160 | graphene==2.1.8 \ 161 | --hash=sha256:09165f03e1591b76bf57b133482db9be6dac72c74b0a628d3c93182af9c5a896 162 | graphql-core==2.3.2 \ 163 | --hash=sha256:44c9bac4514e5e30c5a595fac8e3c76c1975cae14db215e8174c7fe995825bad 164 | graphql-relay==2.0.1 \ 165 | --hash=sha256:ac514cb86db9a43014d7e73511d521137ac12cf0101b2eaa5f0a3da2e10d913d 166 | promise==2.3 \ 167 | --hash=sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0 168 | Rx==1.6.1 \ 169 | --hash=sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105 170 | asgiref==3.2.10 \ 171 | --hash=sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed 172 | graphene-django==2.12.1 \ 173 | --hash=sha256:03dfe2081c256e56d94d90b33b0bf6fa46ec274186023ccffb9c3aa46a856587 174 | singledispatch==3.4.0.3 \ 175 | --hash=sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8 176 | Unidecode==1.1.1 \ 177 | --hash=sha256:1d7a042116536098d05d599ef2b8616759f02985c85b4fef50c78a5aaf10822a 178 | django-filter==2.4.0 \ 179 | --hash=sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1 180 | 181 | # Extra 182 | charset-normalizer==2.0.0 \ 183 | --hash=sha256:76fd234253352853909a367630ea0040001df0b4f6e9cb655a7bf861e81a6d32 184 | tenacity==6.2.0 \ 185 | --hash=sha256:5a5d3dcd46381abe8b4f82b5736b8726fd3160c6c7161f53f8af7f1eb9b82173 186 | whitenoise==5.3.0 \ 187 | --hash=sha256:d963ef25639d1417e8a247be36e6aedd8c7c6f0a08adcb5a89146980a96b577c -------------------------------------------------------------------------------- /waveform-django/README.md: -------------------------------------------------------------------------------- 1 | # waveform-django 2 | This directory houses all of the code for the annotator and static content for the website. 3 | 4 | ## export 5 | 6 | - Contains all of the setup and testing for the GraphQL API. 7 | 8 | ## static 9 | 10 | - Contains all of the static content for the website (e.g. packages, favicons, CSS, JS, etc.) 11 | 12 | ## templates 13 | 14 | - Contains all of the base Django templates (augmented HTML) for the website. 15 | 16 | ## waveforms 17 | 18 | - Contains the waveform annotator code and Django template for its rendering. 19 | 20 | ## website 21 | 22 | - Contains all of the Django templates for the visible website except for the annotator itself. 23 | 24 | ## manage.py 25 | 26 | - Allows the user to interact with the Django database. Examples include: 27 |
./manage.py runserver
28 | ./manage.py migrate
29 | ./manage.py makemigrations
30 | 
31 | 32 | ## schema.py 33 | 34 | - Sets up the interaction between the Django models / database and the GraphQL API. For example, this line specifies that we should base our queries off of the Query class and prevent the conversion of snake_case class names to CamelCase: 35 |
schema = graphene.Schema(query=Query, auto_camelcase=False)
36 | - The URL defined to interact with this endpoint is waveform-django/export/urls.py. 37 | -------------------------------------------------------------------------------- /waveform-django/cron.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import os 3 | 4 | import pandas as pd 5 | import pytz 6 | 7 | from waveforms.models import Annotation 8 | from website.settings import base 9 | 10 | 11 | def update_annotations(): 12 | """ 13 | Automatically download the annotations to a CSV file as a cron job. 14 | 15 | Parameters 16 | ---------- 17 | N/A 18 | 19 | Returns 20 | ------- 21 | N/A 22 | 23 | """ 24 | # Update the annotation CSV file each night at midnight. 25 | all_anns = Annotation.objects.all() 26 | csv_df = pd.DataFrame.from_dict({ 27 | 'username': [a.user.username for a in all_anns], 28 | 'project' : [a.project for a in all_anns], 29 | 'record': [a.record for a in all_anns], 30 | 'event': [a.event for a in all_anns], 31 | 'decision': [a.decision for a in all_anns], 32 | 'comments': [a.comments for a in all_anns], 33 | 'decision_date': [str(a.decision_date) for a in all_anns], 34 | 'is_adjudication': [a.is_adjudication for a in all_anns], 35 | }) 36 | file_name = datetime.now(pytz.timezone(base.TIME_ZONE)).strftime('all-anns_%H_%M_%d_%m_%Y.csv') 37 | out_file = os.path.join(base.HEAD_DIR, 'backups', file_name) 38 | csv_df.to_csv(out_file, sep=',', index=False) 39 | -------------------------------------------------------------------------------- /waveform-django/export/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/export/__init__.py -------------------------------------------------------------------------------- /waveform-django/export/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ExportConfig(AppConfig): 5 | name = 'export' 6 | -------------------------------------------------------------------------------- /waveform-django/export/schema.py: -------------------------------------------------------------------------------- 1 | import graphene 2 | from graphene_django.types import DjangoObjectType 3 | from graphene_django.filter import DjangoFilterConnectionField 4 | 5 | from waveforms.models import User, Annotation, UserSettings 6 | 7 | 8 | # View results at: 9 | # http://localhost:8000/waveform-annotation/graphql?query={all_users{edges{node{username,join_date}}}} 10 | class UserType(DjangoObjectType): 11 | class Meta: 12 | model = User 13 | filter_fields = ['username', 'join_date'] 14 | interfaces = (graphene.relay.Node, ) 15 | 16 | 17 | # View results at: 18 | # http://localhost:8000/waveform-annotation/graphql?query={all_annotations{edges{node{user{username},project,record,event,decision,comments,decision_date}}}} 19 | class AnnotationType(DjangoObjectType): 20 | class Meta: 21 | model = Annotation 22 | user = graphene.Field(UserType) 23 | filter_fields = ['user', 'project', 'record', 'event', 'decision', 24 | 'comments', 'decision_date'] 25 | interfaces = (graphene.relay.Node, ) 26 | 27 | 28 | # View results at: 29 | # http://localhost:8000/waveform-annotation/graphql?query={all_user_settings{edges{node{user{username},fig_height,fig_width,margin_left,margin_top,margin_right,margin_bottom,grid_color,sig_color,sig_thickness,ann_color,grid_delta_major,max_y_labels,n_ekg_sigs,down_sample_ekg,down_sample,time_range_min,time_range_max,window_size_min,window_size_max}}}} 30 | class UserSettingsType(DjangoObjectType): 31 | class Meta: 32 | model = UserSettings 33 | user = graphene.Field(UserType) 34 | filter_fields = ['user', 'fig_height', 'fig_width', 'margin_left', 35 | 'margin_top', 'margin_right', 'margin_bottom', 36 | 'grid_color', 'sig_color', 'sig_thickness', 37 | 'ann_color', 'grid_delta_major', 'max_y_labels', 38 | 'n_ekg_sigs', 'down_sample_ekg', 'down_sample', 39 | 'time_range_min', 'time_range_max', 40 | 'window_size_min', 'window_size_max'] 41 | interfaces = (graphene.relay.Node, ) 42 | 43 | 44 | class Query(graphene.ObjectType): 45 | all_users = DjangoFilterConnectionField(UserType) 46 | all_annotations = DjangoFilterConnectionField(AnnotationType) 47 | all_user_settings = DjangoFilterConnectionField(UserSettingsType) 48 | -------------------------------------------------------------------------------- /waveform-django/export/tests.py: -------------------------------------------------------------------------------- 1 | from django.test.testcases import TestCase 2 | import graphene 3 | import json 4 | from schema import Query 5 | 6 | from waveforms.models import Annotation 7 | 8 | 9 | class TestGraphQL(TestCase): 10 | """ 11 | Test the GraphQL API queries. 12 | """ 13 | def test_all_annotations(self): 14 | """ 15 | Test querying for annotations and their attributes. 16 | 17 | Parameters 18 | ---------- 19 | N/A 20 | 21 | Returns 22 | ------- 23 | N/A 24 | 25 | """ 26 | schema = graphene.Schema(query=Query, auto_camelcase=False) 27 | query_correct = """ 28 | query { 29 | all_annotations { 30 | user 31 | project 32 | record 33 | event 34 | decision 35 | comments 36 | decision_date 37 | } 38 | } 39 | """ 40 | query_incorrect = """ 41 | query { 42 | all_annotations { 43 | user 44 | project 45 | record 46 | event 47 | decision 48 | comments 49 | } 50 | } 51 | """ 52 | correct_output = [] 53 | for p in Annotation.objects.all(): 54 | correct_output.append({ 55 | 'user': p.user, 56 | 'project': p.project, 57 | 'record': p.record, 58 | 'event': p.event, 59 | 'slug': p.slug, 60 | 'decision': p.decision, 61 | 'comments': p.comments, 62 | 'decision_date': p.decision_date, 63 | }) 64 | result_correct = schema.execute(query_correct) 65 | self.assertIsNone(result_correct.errors) 66 | result_incorrect = schema.execute(query_incorrect) 67 | self.assertIsNotNone(result_incorrect.errors) 68 | result_correct = json.loads(json.dumps(result_correct.to_dict()))['data']['all_annotations'] 69 | matches = [i for i in result_correct if i not in correct_output] 70 | self.assertEqual(matches, []) 71 | -------------------------------------------------------------------------------- /waveform-django/export/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from graphene_django.views import GraphQLView 3 | from schema import schema 4 | 5 | 6 | urlpatterns = [ 7 | path('graphql', GraphQLView.as_view(graphiql=False, schema=schema), 8 | name='graphql'), 9 | ] 10 | -------------------------------------------------------------------------------- /waveform-django/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "website.settings.base")#sqlite") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /waveform-django/schema.py: -------------------------------------------------------------------------------- 1 | import export.schema 2 | import graphene 3 | 4 | 5 | class Query(export.schema.Query, graphene.ObjectType): 6 | # This class will inherit from multiple Queries 7 | # as we begin to add more apps to our project 8 | pass 9 | 10 | schema = graphene.Schema(query=Query, auto_camelcase=False) 11 | -------------------------------------------------------------------------------- /waveform-django/static/bootstrap/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 3.5rem tall */ 6 | body { 7 | padding-top: 3.5rem; 8 | } 9 | 10 | /* 11 | * Typography 12 | */ 13 | 14 | h1 { 15 | margin-bottom: 20px; 16 | padding-bottom: 9px; 17 | border-bottom: 1px solid #eee; 18 | } 19 | 20 | /* 21 | * Sidebar 22 | */ 23 | 24 | .sidebar { 25 | position: fixed; 26 | top: 51px; 27 | bottom: 0; 28 | left: 0; 29 | z-index: 1000; 30 | padding: 20px; 31 | overflow-x: hidden; 32 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 33 | border-right: 1px solid #eee; 34 | } 35 | 36 | /* Sidebar navigation */ 37 | .sidebar { 38 | padding-left: 0; 39 | padding-right: 0; 40 | } 41 | 42 | .sidebar .nav { 43 | margin-bottom: 20px; 44 | } 45 | 46 | .sidebar .nav-item { 47 | width: 100%; 48 | } 49 | 50 | .sidebar .nav-item + .nav-item { 51 | margin-left: 0; 52 | } 53 | 54 | .sidebar .nav-link { 55 | border-radius: 0; 56 | } 57 | 58 | /* 59 | * Dashboard 60 | */ 61 | 62 | /* Placeholders */ 63 | .placeholders { 64 | padding-bottom: 3rem; 65 | } 66 | 67 | .placeholder img { 68 | padding-top: 1.5rem; 69 | padding-bottom: 1.5rem; 70 | } 71 | -------------------------------------------------------------------------------- /waveform-django/static/bootstrap/js/ie10-viewport-bug-workaround.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014-2017 The Bootstrap Authors 4 | * Copyright 2014-2017 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | */ 7 | 8 | // See the Getting Started docs for more information: 9 | // https://getbootstrap.com/getting-started/#support-ie10-width 10 | 11 | (function () { 12 | 'use strict' 13 | 14 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 15 | var msViewportStyle = document.createElement('style') 16 | msViewportStyle.appendChild( 17 | document.createTextNode( 18 | '@-ms-viewport{width:auto!important}' 19 | ) 20 | ) 21 | document.head.appendChild(msViewportStyle) 22 | } 23 | 24 | }()) 25 | -------------------------------------------------------------------------------- /waveform-django/static/bootstrap/js/offcanvas.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $('[data-toggle="offcanvas"]').click(function () { 3 | $('.row-offcanvas').toggleClass('active') 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /waveform-django/static/caliper.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/caliper.mp4 -------------------------------------------------------------------------------- /waveform-django/static/caliper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/caliper.png -------------------------------------------------------------------------------- /waveform-django/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/favicon.ico -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | @import "screen-reader.less"; 19 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | .sr-only() { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | .sr-only-focusable() { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | @mixin sr-only { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | @mixin sr-only-focusable { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { @include sr-only(); } 5 | .sr-only-focusable { @include sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /waveform-django/static/font-awesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | @import "screen-reader"; 19 | -------------------------------------------------------------------------------- /waveform-django/static/interface.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/interface.mp4 -------------------------------------------------------------------------------- /waveform-django/static/jquery-ui/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery-ui 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | Copyright and related rights for sample code are waived via CC0. Sample 34 | code is defined as all source code contained within the demos directory. 35 | 36 | CC0: http://creativecommons.org/publicdomain/zero/1.0/ 37 | 38 | ==== 39 | 40 | All files located in the node_modules and external directories are 41 | externally maintained libraries used by this software which have their 42 | own licenses; we recommend you read them, as their terms may differ from 43 | the terms above. 44 | -------------------------------------------------------------------------------- /waveform-django/static/jquery-ui/images/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/jquery-ui/images/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /waveform-django/static/jquery-ui/images/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/jquery-ui/images/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /waveform-django/static/jquery-ui/images/ui-icons_777620_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/jquery-ui/images/ui-icons_777620_256x240.png -------------------------------------------------------------------------------- /waveform-django/static/jquery-ui/images/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/jquery-ui/images/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /waveform-django/static/jquery-ui/images/ui-icons_cc0000_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/jquery-ui/images/ui-icons_cc0000_256x240.png -------------------------------------------------------------------------------- /waveform-django/static/jquery-ui/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/jquery-ui/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /waveform-django/static/jquery-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-ui", 3 | "title": "jQuery UI", 4 | "description": "A curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library.", 5 | "version": "1.13.0", 6 | "homepage": "http://jqueryui.com", 7 | "author": { 8 | "name": "jQuery Foundation and other contributors", 9 | "url": "https://github.com/jquery/jquery-ui/blob/1.13.0/AUTHORS.txt" 10 | }, 11 | "main": "ui/widget.js", 12 | "maintainers": [ 13 | { 14 | "name": "Jörn Zaefferer", 15 | "email": "joern.zaefferer@gmail.com", 16 | "url": "http://bassistance.de" 17 | }, 18 | { 19 | "name": "Mike Sherov", 20 | "email": "mike.sherov@gmail.com", 21 | "url": "http://mike.sherov.com" 22 | }, 23 | { 24 | "name": "TJ VanToll", 25 | "email": "tj.vantoll@gmail.com", 26 | "url": "http://tjvantoll.com" 27 | }, 28 | { 29 | "name": "Felix Nagel", 30 | "email": "info@felixnagel.com", 31 | "url": "http://www.felixnagel.com" 32 | }, 33 | { 34 | "name": "Alex Schmitz", 35 | "email": "arschmitz@gmail.com", 36 | "url": "https://github.com/arschmitz" 37 | } 38 | ], 39 | "repository": { 40 | "type": "git", 41 | "url": "git://github.com/jquery/jquery-ui.git" 42 | }, 43 | "bugs": "https://bugs.jqueryui.com/", 44 | "license": "MIT", 45 | "scripts": { 46 | "test": "grunt" 47 | }, 48 | "dependencies": { 49 | "jquery": ">=1.8.0 <4.0.0" 50 | }, 51 | "devDependencies": { 52 | "commitplease": "3.2.0", 53 | "eslint-config-jquery": "3.0.0", 54 | "glob": "7.1.7", 55 | "grunt": "1.4.1", 56 | "grunt-bowercopy": "1.2.5", 57 | "grunt-cli": "1.4.3", 58 | "grunt-compare-size": "0.4.2", 59 | "grunt-contrib-concat": "1.0.1", 60 | "grunt-contrib-csslint": "2.0.0", 61 | "grunt-contrib-qunit": "5.0.1", 62 | "grunt-contrib-requirejs": "1.0.0", 63 | "grunt-contrib-uglify": "5.0.1", 64 | "grunt-eslint": "23.0.0", 65 | "grunt-git-authors": "3.2.0", 66 | "grunt-html": "14.5.0", 67 | "load-grunt-tasks": "5.1.0", 68 | "rimraf": "3.0.2", 69 | "testswarm": "1.1.2" 70 | }, 71 | "keywords": [] 72 | } 73 | -------------------------------------------------------------------------------- /waveform-django/static/practice-test.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/practice-test.mp4 -------------------------------------------------------------------------------- /waveform-django/static/self-assignment.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/self-assignment.mp4 -------------------------------------------------------------------------------- /waveform-django/static/tutorial_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/static/tutorial_image.png -------------------------------------------------------------------------------- /waveform-django/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load static %} 3 | 4 | 5 | 6 | {% block title %}{% endblock %} 7 | {% block meta %}{% endblock %} 8 | {% include "base_css.html" %} 9 | {% block local_css %} 10 | 36 | {% endblock %} 37 | 38 | 39 | 40 | {% block navbar %} 41 |
42 | 43 | User: {{user.username}}... Logout 44 | 45 | 46 | 47 | {% if user.is_admin %} 48 | {% url 'admin_console' as url %} 49 | 56 | {% endif %} 57 | {% if user.is_adjudicator %} 58 | 69 | {% endif %} 70 | 83 | 96 | {% url 'leaderboard' as url %} 97 | 104 | {% url 'practice_test' as url %} 105 | 118 | {% url 'viewer_tutorial' as url %} 119 | 126 | {% url 'viewer_settings' as url %} 127 | 134 | 135 |
50 | 55 | 59 | 68 | 71 | 82 | 84 | 95 | 98 | 103 | 106 | 117 | 120 | 125 | 128 | 133 |
136 |
137 | {% endblock %} 138 | 139 | {% block body %} 140 | 141 |
{% block content %}{% endblock %}
142 | 143 | {% endblock %} 144 | 145 | 146 | -------------------------------------------------------------------------------- /waveform-django/templates/base_css.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /waveform-django/waveforms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/waveforms/__init__.py -------------------------------------------------------------------------------- /waveform-django/waveforms/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class WaveformsConfig(AppConfig): 5 | # This is the name of the folder which holds the Plotly app. 6 | name = 'waveforms' 7 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-10-29 18:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Annotation', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('record', models.CharField(max_length=50)), 19 | ('event', models.CharField(max_length=50)), 20 | ('decision', models.CharField(max_length=9)), 21 | ('comments', models.TextField(default='')), 22 | ('decision_date', models.DateTimeField(null=True)), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0002_annotation_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-11-10 17:27 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='annotation', 15 | name='user', 16 | field=models.CharField(default='', max_length=150, unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0003_auto_20201110_1244.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-11-10 17:44 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0002_annotation_user'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='annotation', 15 | name='user', 16 | field=models.CharField(default='', max_length=150), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0004_usersettings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-03-04 13:50 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0003_auto_20201110_1244'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='UserSettings', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('user', models.CharField(default='', max_length=150, unique=True)), 18 | ('fig_height', models.FloatField(default=725)), 19 | ('fig_width', models.FloatField(default=875)), 20 | ('margin_left', models.FloatField(default=0)), 21 | ('margin_top', models.FloatField(default=25)), 22 | ('margin_right', models.FloatField(default=0)), 23 | ('margin_bottom', models.FloatField(default=0)), 24 | ('grid_color', models.TextField(default='#ff3c3c')), 25 | ('sig_color', models.TextField(default='#000000')), 26 | ('sig_thickness', models.FloatField(default=1.5)), 27 | ('ann_color', models.TextField(default='#3c3cc8')), 28 | ('grid_delta_major', models.FloatField(default=0.2)), 29 | ('max_y_labels', models.IntegerField(default=8)), 30 | ('down_sample_ekg', models.IntegerField(default=8)), 31 | ('down_sample', models.IntegerField(default=16)), 32 | ('time_range_min', models.FloatField(default=40)), 33 | ('time_range_max', models.FloatField(default=10)), 34 | ('window_size_min', models.FloatField(default=10)), 35 | ('window_size_max', models.FloatField(default=1)), 36 | ], 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0005_auto_20210323_1427.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-03-23 18:27 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('waveforms', '0004_usersettings'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='User', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('username', models.CharField(default='', max_length=150, unique=True)), 19 | ('join_date', models.DateField(auto_now_add=True)), 20 | ], 21 | ), 22 | migrations.AlterField( 23 | model_name='annotation', 24 | name='user', 25 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='annotation', to='waveforms.User'), 26 | ), 27 | migrations.AlterField( 28 | model_name='usersettings', 29 | name='user', 30 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='waveforms.User'), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0006_user_is_admin.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-03-25 17:34 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0005_auto_20210323_1427'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='is_admin', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0007_auto_20210326_1510.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-03-26 19:10 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0006_user_is_admin'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='user', 15 | name='join_date', 16 | field=models.DateTimeField(auto_now_add=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0008_usersettings_background_color.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-04-07 12:10 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0007_auto_20210326_1510'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='usersettings', 15 | name='background_color', 16 | field=models.TextField(default='#ffffff'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0009_usersettings_signal_std.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-04-07 12:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0008_usersettings_background_color'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='usersettings', 15 | name='signal_std', 16 | field=models.FloatField(default=2.0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0010_auto_20210716_2244.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-07-17 02:44 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('waveforms', '0009_usersettings_signal_std'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='user', 16 | name='email', 17 | field=models.EmailField(max_length=255, null=True, unique=True, validators=[django.core.validators.EmailValidator()]), 18 | ), 19 | migrations.AlterField( 20 | model_name='user', 21 | name='username', 22 | field=models.CharField(max_length=150, unique=True), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0011_invitedemails.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-08-07 22:47 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('waveforms', '0010_auto_20210716_2244'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='InvitedEmails', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('email', models.EmailField(max_length=255, null=True, unique=True, validators=[django.core.validators.EmailValidator()])), 19 | ('last_invite_date', models.DateTimeField()), 20 | ('joined', models.BooleanField(default=False)), 21 | ('joined_username', models.CharField(max_length=150, null=True, unique=True)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0012_usersettings_n_ekg_sigs.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-07-27 05:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0011_invitedemails'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='usersettings', 15 | name='n_ekg_sigs', 16 | field=models.IntegerField(default=2), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0013_annotation_project.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-07-30 03:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0012_usersettings_n_ekg_sigs'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='annotation', 15 | name='project', 16 | field=models.CharField(default='2015_data', max_length=50), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0014_auto_20210802_2133.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-08-03 01:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0013_annotation_project'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='signal_std', 16 | field=models.FloatField(default=3.0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0015_user_last_login.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-08-15 06:06 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('waveforms', '0014_auto_20210802_2133'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='user', 16 | name='last_login', 17 | field=models.DateTimeField(default=django.utils.timezone.now), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0016_user_date_assigned.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-08-15 19:02 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('waveforms', '0015_user_last_login'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='user', 16 | name='date_assigned', 17 | field=models.DateTimeField(default=django.utils.timezone.now), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0017_auto_20210918_0716.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-09-18 11:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0016_user_date_assigned'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='window_size_min', 16 | field=models.FloatField(default=15.0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0018_auto_20210919_1130.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-09-19 15:30 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0017_auto_20210918_0716'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='margin_bottom', 16 | field=models.FloatField(default=35.0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0019_auto_20211019_1156.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-10-19 15:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0018_auto_20210919_1130'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='window_size_min', 16 | field=models.FloatField(default=10.0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0020_auto_20211025_1539.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-10-25 19:39 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0019_auto_20211019_1156'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='down_sample', 16 | field=models.IntegerField(default=1), 17 | ), 18 | migrations.AlterField( 19 | model_name='usersettings', 20 | name='down_sample_ekg', 21 | field=models.IntegerField(default=1), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0021_auto_20211030_1812.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-10-30 22:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0020_auto_20211025_1539'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='signal_std', 16 | field=models.FloatField(default=6.0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0022_user_practice_status.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-10-31 07:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0021_auto_20211030_1812'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='practice_status', 16 | field=models.CharField(choices=[('BG', 'Began'), ('CO', 'Completed'), ('ED', 'Ended')], default='ED', max_length=2), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0023_auto_20211116_1221.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-11-16 17:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0022_user_practice_status'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='fig_height', 16 | field=models.FloatField(default=690.0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0024_user_is_adjudicator.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-11-08 14:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0023_auto_20211116_1221'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='is_adjudicator', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/0025_annotation_is_adjudication.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-12-20 20:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('waveforms', '0024_user_is_adjudicator'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='annotation', 15 | name='is_adjudication', 16 | field=models.BooleanField(default=False, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /waveform-django/waveforms/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/waveforms/migrations/__init__.py -------------------------------------------------------------------------------- /waveform-django/waveforms/models.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import os 3 | 4 | from django.core.validators import EmailValidator 5 | from django.db import models 6 | from django.utils import timezone 7 | 8 | from website.settings import base 9 | 10 | 11 | class User(models.Model): 12 | """ 13 | The model for each user on the platform. 14 | """ 15 | username = models.CharField(max_length=150, unique=True, blank=False) 16 | email = models.EmailField(max_length=255, unique=True, null=True, 17 | blank=False, validators=[EmailValidator()]) 18 | join_date = models.DateTimeField(auto_now_add=True) 19 | is_adjudicator = models.BooleanField(default=False) 20 | is_admin = models.BooleanField(default=False) 21 | last_login = models.DateTimeField(default=timezone.now) 22 | date_assigned = models.DateTimeField(default=timezone.now) 23 | # Completion status of the practice test 24 | BEGAN = 'BG' 25 | COMPLETED = 'CO' 26 | ENDED = 'ED' 27 | practice_modes = [ 28 | (BEGAN, 'Began'), 29 | (COMPLETED, 'Completed'), 30 | (ENDED, 'Ended') 31 | ] 32 | practice_status = models.CharField( 33 | max_length=2, 34 | choices=practice_modes, 35 | default=ENDED, 36 | ) 37 | 38 | def num_annotations(self, project=None): 39 | """ 40 | Determine the number of annotations for the current user. 41 | 42 | Parameters 43 | ---------- 44 | project : str, optional 45 | The desired project from which to query for user annotations. 46 | 47 | Returns 48 | ------- 49 | N/A : int 50 | The number of annotations for the current user. 51 | 52 | """ 53 | if project: 54 | return len(Annotation.objects.filter(user=self, project=project, 55 | is_adjudication=False)) 56 | else: 57 | return len(Annotation.objects.filter(user=self, 58 | is_adjudication=False)) 59 | 60 | def new_settings(self): 61 | """ 62 | Returns the changes in the user settings from default values. 63 | 64 | Parameters 65 | ---------- 66 | N/A 67 | 68 | Returns 69 | ------- 70 | diff_settings : dict 71 | All of the user settings changes from default in the form of: 72 | { 73 | 'field1': [default1, user_change1], 74 | 'field2': [default2, user_change2], 75 | ... 76 | } 77 | 78 | """ 79 | diff_settings = {} 80 | all_fields = [f.name for f in UserSettings._meta.fields][2:] 81 | for field in all_fields: 82 | user_set = UserSettings.objects.get(user=self).__dict__[field] 83 | default = UserSettings._meta.get_field(field).get_default() 84 | if user_set != default: 85 | diff_settings[field] = [default, user_set] 86 | return diff_settings 87 | 88 | def events_remaining(self): 89 | """ 90 | Return the total number of event remaining from the user's assignment. 91 | 92 | Parameters 93 | ---------- 94 | N/A 95 | 96 | Returns 97 | ------- 98 | count : int 99 | The total number of events remaining from the user's assignment. 100 | 101 | """ 102 | BASE_DIR = base.BASE_DIR 103 | FILE_ROOT = os.path.abspath(os.path.join(BASE_DIR, os.pardir)) 104 | FILE_LOCAL = os.path.join('record-files') 105 | PROJECT_PATH = os.path.join(FILE_ROOT, FILE_LOCAL) 106 | 107 | count = 0 108 | for project in base.ALL_PROJECTS: 109 | event_list = [] 110 | csv_path = os.path.join(PROJECT_PATH, project, base.ASSIGNMENT_FILE) 111 | with open(csv_path, 'r') as csv_file: 112 | csvreader = csv.reader(csv_file, delimiter=',') 113 | next(csvreader) 114 | for row in csvreader: 115 | if self.username in row[1:]: 116 | event_list.append(row[0]) 117 | complete_events = Annotation.objects.filter( 118 | user=self, project=project, is_adjudication=False).exclude( 119 | decision='Save for Later').values_list('event', flat=True) 120 | event_list = [e for e in event_list if e not in complete_events] 121 | count += len(event_list) 122 | return count 123 | 124 | 125 | class InvitedEmails(models.Model): 126 | """ 127 | The emails which have been invited and their associated account. 128 | """ 129 | email = models.EmailField(max_length=255, unique=True, null=True, 130 | blank=False, validators=[EmailValidator()]) 131 | last_invite_date = models.DateTimeField() 132 | joined = models.BooleanField(default=False) 133 | joined_username = models.CharField(max_length=150, unique=True, 134 | blank=False, null=True) 135 | 136 | 137 | class Annotation(models.Model): 138 | """ 139 | """ 140 | user = models.ForeignKey('User', related_name='annotation', 141 | on_delete=models.CASCADE) 142 | project = models.CharField(max_length=50, blank=False) 143 | record = models.CharField(max_length=50, blank=False) 144 | event = models.CharField(max_length=50, blank=False) 145 | decision = models.CharField(max_length=9, blank=False) 146 | comments = models.TextField(default='') 147 | decision_date = models.DateTimeField(null=True, blank=False) 148 | is_adjudication = models.BooleanField(default=False, null=True) 149 | 150 | def update(self): 151 | """ 152 | Update the user's annotation if it exists, else create a new one. 153 | 154 | Parameters 155 | ---------- 156 | N/A 157 | 158 | Returns 159 | ------- 160 | N/A 161 | 162 | """ 163 | all_annotations = Annotation.objects.filter( 164 | user=self.user, project=self.project, record=self.record, 165 | event=self.event, is_adjudication=False 166 | ) 167 | if all_annotations: 168 | for a in all_annotations: 169 | a.decision = self.decision 170 | a.comments = self.comments 171 | a.decision_date = self.decision_date 172 | a.save(update_fields=['decision', 'comments', 173 | 'decision_date']) 174 | else: 175 | self.save() 176 | 177 | 178 | class UserSettings(models.Model): 179 | """ 180 | The settings for the user to adjust their graph display. 181 | """ 182 | user = models.ForeignKey('User', related_name='settings', 183 | on_delete=models.CASCADE) 184 | fig_height = models.FloatField(blank=False, default=690.0) 185 | fig_width = models.FloatField(blank=False, default=875.0) 186 | margin_left = models.FloatField(blank=False, default=0.0) 187 | margin_top = models.FloatField(blank=False, default=25.0) 188 | margin_right = models.FloatField(blank=False, default=0.0) 189 | margin_bottom = models.FloatField(blank=False, default=35.0) 190 | grid_color = models.TextField(blank=False, default='#ff3c3c') 191 | background_color = models.TextField(blank=False, default='#ffffff') 192 | sig_color = models.TextField(blank=False, default='#000000') 193 | sig_thickness = models.FloatField(blank=False, default=1.5) 194 | ann_color = models.TextField(blank=False, default='#3c3cc8') 195 | grid_delta_major = models.FloatField(blank=False, default=0.2) 196 | max_y_labels = models.IntegerField(blank=False, default=8) 197 | n_ekg_sigs = models.IntegerField(blank=False, default=2) 198 | down_sample_ekg = models.IntegerField(blank=False, default=1) 199 | down_sample = models.IntegerField(blank=False, default=1) 200 | signal_std = models.FloatField(blank=False, default=6.0) 201 | time_range_min = models.FloatField(blank=False, default=40.0) 202 | time_range_max = models.FloatField(blank=False, default=10.0) 203 | window_size_min = models.FloatField(blank=False, default=10.0) 204 | window_size_max = models.FloatField(blank=False, default=1.0) 205 | -------------------------------------------------------------------------------- /waveform-django/waveforms/templates/waveforms/adjudications.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 | 6 |
7 |

All Adjudications

8 |
9 | Total Complete 10 | {{ all_anns_frac }} 11 |

12 |
13 |
14 |

Search for record

15 | {% if messages %} 16 |
    17 | {% for message in messages %} 18 |

    {{ message }}

    19 | {% endfor %} 20 |
21 | {% endif %} 22 |
23 | 24 |

25 |
26 | {% if search %} 27 | {% for k,info in search.items %} 28 | {% if k == "com" %} 29 | {% for batch in info %} 30 |
31 | 32 | 33 | 34 | {% for cat in categories %} 35 | 38 | {% endfor %} 39 | 40 | 43 | 46 | 47 | 48 | {% for val in batch %} 49 | 50 | 51 | {% if forloop.last %} 52 | {% for v in val|slice:"2:" %} 53 | 56 | {% endfor %} 57 | {% else %} 58 | {% for v in val|slice:"2:" %} 59 | 62 | {% endfor %} 63 | {% endif %} 64 | 65 | {% endfor %} 66 |
36 | {{ cat }} 37 | 41 | Edit adjudication 42 | 44 | Delete adjudication 45 |
54 | {{ v }} 55 | 60 | {{ v }} 61 |
67 |
68 |
69 | {% endfor %} 70 | {% elif k == "inc" %} 71 | {% for batch in info %} 72 |
73 | 74 | 75 | 76 | {% for cat in categories %} 77 | 80 | {% endfor %} 81 | 84 | 85 | 86 | {% for val in batch %} 87 | 88 | {% for v in val|slice:"2:" %} 89 | 92 | {% endfor %} 93 | 94 | {% endfor %} 95 |
78 | {{ cat }} 79 | 82 | Edit adjudication 83 |
90 | {{ v }} 91 |
96 |
97 |
98 | {% endfor %} 99 | {% endif %} 100 | {% endfor %} 101 | {% endif %} 102 |
103 |
104 |

Complete Adjudications

105 |
106 | {% if complete_page %} 107 | 128 | {% endif %} 129 | {% for batch in complete_adjudications %} 130 |
131 | 132 | 133 | 134 | {% for cat in categories %} 135 | 138 | {% endfor %} 139 | 140 | 143 | 146 | 147 | 148 | {% for val in batch %} 149 | 150 | 151 | {% if forloop.last %} 152 | {% for v in val|slice:"2:" %} 153 | 156 | {% endfor %} 157 | {% else %} 158 | {% for v in val|slice:"2:" %} 159 | 162 | {% endfor %} 163 | {% endif %} 164 | 165 | {% endfor %} 166 |
136 | {{ cat }} 137 | 141 | Edit adjudication 142 | 144 | Delete adjudication 145 |
154 | {{ v }} 155 | 160 | {{ v }} 161 |
167 |
168 |
169 | {% endfor %} 170 |
171 |
172 |

Incomplete Adjudications

173 |
174 | {% if incomplete_page %} 175 | 196 | {% endif %} 197 | {% for batch in incomplete_adjudications %} 198 |
199 | 200 | 201 | 202 | {% for cat in categories %} 203 | 206 | {% endfor %} 207 | 210 | 211 | 212 | {% for val in batch %} 213 | 214 | {% for v in val|slice:"2:" %} 215 | 218 | {% endfor %} 219 | 220 | {% endfor %} 221 |
204 | {{ cat }} 205 | 208 | Edit adjudication 209 |
216 | {{ v }} 217 |
222 |
223 |
224 | {% endfor %} 225 |
226 |
227 | 228 | {% endblock %} 229 | -------------------------------------------------------------------------------- /waveform-django/waveforms/templates/waveforms/adjudicator_console.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% load plotly_dash %} 4 | {% block content %} 5 | 14 | 15 | 16 |
17 |

Adjudicator Console

18 | 19 | 20 | 23 | 24 |
25 | {% plotly_app_bootstrap name='waveform_graph_adjudicate' initial_arguments=dash_context %} 26 |
27 |
28 | 29 | 66 | {% endblock %} 67 | -------------------------------------------------------------------------------- /waveform-django/waveforms/templates/waveforms/admin_console.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 | 6 |
7 |

Admin Console

8 |
9 | 13 |
14 |

All Users

15 |
16 | {% if messages %} 17 | {% for message in messages %} 18 | {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} 19 |
20 | {% elif message.level == DEFAULT_MESSAGE_LEVELS.INFO %} 21 |
22 | {% else %} 23 |
24 | {% endif %} 25 | 28 | {{ message|safe }} 29 |
30 | {% endfor %} 31 | {% endif %} 32 |

Invite a new user by email address

33 | 59 |

Invited users

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {% for u in invited_users %} 68 | 69 | 70 | 71 | 72 | 73 | 74 | {% endfor %} 75 |
EmailLast Invite DateJoinedJoined Username
{{ u.email }}{{ u.last_invite_date }}{{ u.joined }}{{ u.joined_username }}
76 |
77 |

Active Assignments

78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | {% for u in all_users %} 86 | {% if u.events_remaining > 0 %} 87 | 88 | 89 | 90 | 91 | 98 | 99 | {% endif %} 100 | {% endfor %} 101 |
UsernameEvents RemainingAssignment Start Date
{{ u.username }}{{ u.events_remaining }}{{ u.date_assigned }} 92 |
93 | {% csrf_token %} 94 | 95 | 96 |
97 |
102 |
103 | 104 |

Joined users

105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | {% for u in all_users %} 116 | 117 | 118 | 119 | 120 | 121 | 122 | 139 | 156 | 157 | {% endfor %} 158 |
UsernameJoin DateLast LoginTotal Number of Annotations (across all projects)New Settings <{field: [default,user set], ...}>Adjudicator ControlAdmin Control
{{ u.username }}{{ u.join_date }}{{ u.last_login }}{{ u.num_annotations }}{{ u.new_settings }} 123 | {% if u.is_adjudicator %} 124 |
125 | 129 |
130 | {% else %} 131 |
132 | 136 |
137 | {% endif %} 138 |
140 | {% if u.is_admin %} 141 |
142 | 146 |
147 | {% else %} 148 |
149 | 153 |
154 | {% endif %} 155 |
159 |
160 |
161 | {% for project,k in conflict_anns.items %} 162 |

Project: {{ project }}

163 |

Conflicting Annotations

164 | 165 | 173 | 174 | {% for rec,info in k %} 175 | {% if info|length > 0 %} 176 |

Record: {{ rec }}



177 | {% for evt,nfo in info.items %} 178 |
Event: {{ evt }} (View waveform )
179 | 180 | 181 | {% for cat in categories %} 182 | 185 | {% endfor %} 186 | 187 | {% for val in nfo %} 188 | 189 | {% for v in val %} 190 | 193 | {% endfor %} 194 | 195 | {% endfor %} 196 |
183 | {{ cat }} 184 |
191 | {{ v }} 192 |

197 | {% endfor %} 198 |
199 | {% endif %} 200 |
201 | {% endfor %} 202 | {% endfor %} 203 | {% for project,k in unanimous_anns.items %} 204 |

Project: {{ project }}

205 |

Unanimous Annotations

206 | 207 | 215 | 216 | {% for rec,info in k %} 217 | {% if info|length > 0 %} 218 |

Record: {{ rec }}



219 | {% for evt,nfo in info.items %} 220 |
Event: {{ evt }} (View waveform )
221 | 222 | 223 | {% for cat in categories %} 224 | 227 | {% endfor %} 228 | 229 | {% for val in nfo %} 230 | 231 | {% for v in val %} 232 | 235 | {% endfor %} 236 | 237 | {% endfor %} 238 |
225 | {{ cat }} 226 |
233 | {{ v }} 234 |

239 | {% endfor %} 240 |
241 | {% endif %} 242 |
243 | {% endfor %} 244 | {% endfor %} 245 | {% for project,k in all_anns.items %} 246 |

Project: {{ project }}

247 |

Unfinished Annotations

248 | 249 | 257 | 258 | 259 | {% for rec,info in k %} 260 | {% if info|length > 0 %} 261 |

Record: {{ rec }}



262 | {% for evt,nfo in info.items %} 263 |
Event: {{ evt }} (View waveform )
264 | 265 | 266 | {% for cat in categories %} 267 | 270 | {% endfor %} 271 | 272 | {% for val in nfo %} 273 | 274 | {% for v in val %} 275 | 278 | {% endfor %} 279 | 280 | {% endfor %} 281 |
268 | {{ cat }} 269 |
276 | {{ v }} 277 |

282 | {% endfor %} 283 |
284 | {% endif %} 285 |
286 | {% endfor %} 287 | {% endfor %} 288 |
289 | 290 | {% endblock %} 291 | -------------------------------------------------------------------------------- /waveform-django/waveforms/templates/waveforms/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% load plotly_dash %} 4 | {% block content %} 5 | 14 | 15 | 16 |
17 | 18 | 19 | 22 | 23 |
24 | {% plotly_app_bootstrap name='waveform_graph' initial_arguments=dash_context %} 25 |
26 |
27 | 28 | 65 | {% endblock %} 66 | -------------------------------------------------------------------------------- /waveform-django/waveforms/templates/waveforms/practice.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 |
6 |

Answer practice questions

7 |
8 |

Instructions

9 |
10 |
    11 |
  • You will be assigned a set of {{ total }} waveform events to complete.
  • 12 |
  • You can come return to this page whenever you are ready to view the answers.
  • 13 |
  • This assessment will not be timed or graded, and may be exited at any time by pressing the "End Practice" button below.
  • 14 |
  • When you wish to continue working on the main dataset, simply press the "End Practice" button below. You will resume working on your active assignment.
  • 15 |
  • You may take the practice test as many times as you wish, but your score will be lost after you end the practice session.
  • 16 |
17 |
18 | {% if user.practice_status == "CO" %} 19 |

Results

20 | {% for project,events in results.items %} 21 | {% for event,responses in events.items %} 22 |

23 | {{ project }} {{ event }}  Correct answer: {{ responses.0 }}  Your answer: {{ responses.1 }} 24 | {% if responses.0 == responses.1 %} ✔️ {% else %} ❌ {% endif %} 25 |

26 | {% endfor %} 27 | {% endfor %} 28 |

Score: {{ correct }}/{{ total }}

29 | {% endif %} 30 |
31 |
32 |
33 | {% csrf_token %} 34 | {% if user.practice_status == "BG" %} 35 | 36 | {% endif %} 37 | {% if user.practice_status == "ED" %} 38 | 39 | {% else %} 40 | 41 | {% endif %} 42 |
43 |
44 |
45 | {% endblock %} 46 | -------------------------------------------------------------------------------- /waveform-django/waveforms/templates/waveforms/settings.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 |
6 |

Form to change graph settings

7 |
8 | {% if messages %} 9 | {% for message in messages %} 10 | {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} 11 |
12 | {% elif message.level == DEFAULT_MESSAGE_LEVELS.INFO %} 13 |
14 | {% else %} 15 |
16 | {% endif %} 17 | 20 | {{ message|safe }} 21 |
22 | {% endfor %} 23 | {% endif %} 24 | 59 |
60 |
61 | {% endblock %} 62 | -------------------------------------------------------------------------------- /waveform-django/waveforms/templates/waveforms/tutorial.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 |
6 |

Basic Usage Instructions

7 |
8 |
    9 |
  • After logging in, you should see your username with the option to logout in the top left corner.
  • 10 |
  • Below that, you should see links to view your current annotations, view a brief tutorial explaining the layout of the annotator (this page), and a way to change the settings / preferences of your annotator (including grid color, time before and after an event, the level of downsampling, etc.).
  • 11 |
  • To begin annotating, go to the annotator home and scroll down to view the entire annotator. 12 |
  • On the top left side, you should see the name of the current annotation record, event, and label (It has been filtered so only VTach and VFib/VFlutter events are displayed. Please let us know if you see otherwise!).
  • 13 |
  • Directly below this is where you can enter your decision for the label (True, False, Uncertain, Reject, or Save for Later) as well as your additional comments explaining your decision (this field is not necessary but may prove useful later on for either self-reference or research purposes).
  • 14 |
  • You can submit the annotation with the "Submit" button, or go to the next or previous annotation without saving using the arrows. Submitting the annotation will automatically send you to the next event.
  • 15 |
  • If you hover over the signals, you will be able to see its value and how it aligns with the other signals.
  • 16 |
  • Also, you can click and drag the signal in any direction to view more of it.
  • 17 |
  • At any point, you can view all of your previous annotations with the "View current annotations" link and either edit or delete them if needed. You will also be able to see your progress on the annotations using the fractions on the far right side. Also note that if you reload the annotator home page at any point, you will be forwarded to your first non-annotated event.
  • 18 |
  • You can also adjust the settings for the annotator using the "Change annotator settings" link; for example, you can reduce the downsampling to view more detail on the EKG signals if needed.
  • 19 |
20 |
21 |
22 |
23 |

Getting Events To Annotate

24 | 28 |
29 |
30 |

Taking The Practice Test

31 | 35 |
36 |
37 |
38 |
39 |
40 |

Interface Overview

41 |
42 |
43 |
    44 |
  1. Current record
  2. 45 |
  3. Current event
  4. 46 |
  5. Triggered alarm annotation
  6. 47 |
  7. Annotation decision
  8. 48 |
  9. Annotator comments
  10. 49 |
  11. Submit new annotation for this record and event
  12. 50 |
  13. Go to the previous annotation without saving annotation decision and comments
  14. 51 |
  15. Go to the next annotation without saving annotation decision and comments
  16. 52 |
  17. Download the current screen (only plotted signals, not sidebar) as a PNG image
  18. 53 |
  19. Pan across signal to view different ranges (click and hold anywhere, drag to the right to move the signal to the right / move backwards in time, drag upwards or downwards to view upper and lower portions of the signal)
  20. 54 |
  21. Zoom in (roughly by a factor of two each time)
  22. 55 |
  23. Zoom out (roughly by a factor of two each time)
  24. 56 |
  25. View the entire signal
  26. 57 |
  27. Go to the Plotly website which is the foundation of this plot
  28. 58 |
  29. The vertical blue line represents the timestamp of the alarm annotation
  30. 59 |
60 |
61 |
62 | Tutorial Diagram 63 |
64 |
65 |
66 |
67 |
68 |
69 |

Adjusting And Scaling The Waveform Viewer

70 | 74 |
75 |
76 |

Using The Caliper Tool

77 | 81 |
82 |
83 |
84 |
85 |
86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /waveform-django/waveforms/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | # Needed to render the graph 3 | from waveforms.dash_apps.finished_apps import waveform_vis 4 | from waveforms.dash_apps.finished_apps import waveform_vis_adjudicate 5 | 6 | from waveforms import views 7 | 8 | 9 | urlpatterns = [ 10 | path('django_plotly_dash/', include('django_plotly_dash.urls')), 11 | path('', views.waveform_published_home, name='waveform_published_home'), 12 | path('///', views.waveform_published_home, name='waveform_published_specific'), 13 | path('adjudicate////', views.adjudicator_console, name='waveform_published_specific_adjudicate'), 14 | path('adjudications/', views.render_adjudications, name='render_adjudications'), 15 | path('adjudications/delete////', views.delete_adjudication, name='delete_adjudication'), 16 | path('admin_console/', views.admin_console, name='admin_console'), 17 | path('adjudicator_console/', views.adjudicator_console, name='adjudicator_console'), 18 | path('annotations/', views.render_annotations, name='render_annotations'), 19 | path('annotations/delete////', views.delete_annotation, name='delete_annotation'), 20 | path('leaderboard/', views.leaderboard, name='leaderboard'), 21 | path('tutorial/', views.viewer_tutorial, name='viewer_tutorial'), 22 | path('practice/', views.practice_test, name='practice_test'), 23 | path('settings/', views.viewer_settings, name='viewer_settings'), 24 | ] 25 | -------------------------------------------------------------------------------- /waveform-django/website/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/waveform-annotation/85f199f421ea0475eddc73bdd46fabeb494afd50/waveform-django/website/__init__.py -------------------------------------------------------------------------------- /waveform-django/website/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import UserCreationForm, UsernameField 3 | from django.contrib.auth.models import User as d_User 4 | from django.contrib.sites.shortcuts import get_current_site 5 | from django.core.exceptions import ValidationError 6 | from django.core.mail import EmailMultiAlternatives 7 | from django.template import loader 8 | from django.utils.encoding import force_bytes 9 | from django.utils.http import urlsafe_base64_encode 10 | from django.utils.translation import gettext_lazy as _ 11 | 12 | from waveforms.models import User 13 | from website.tokens import password_reset_token 14 | 15 | 16 | class CreateUserForm(UserCreationForm): 17 | """ 18 | For creating a new user. 19 | """ 20 | class Meta: 21 | model = d_User 22 | fields = ['username', 'email', 'password1', 'password2'] 23 | 24 | 25 | class ResetPasswordForm(forms.Form): 26 | """ 27 | For changing the user's password. 28 | """ 29 | username = UsernameField( 30 | widget = forms.TextInput(attrs={'autofocus': True}) 31 | ) 32 | email = forms.EmailField( 33 | label=_('Email'), 34 | max_length=254, 35 | widget=forms.EmailInput(attrs={'autocomplete': 'email'}) 36 | ) 37 | class Meta: 38 | model = User 39 | fields = ['username', 'email'] 40 | 41 | def clean(self): 42 | """ 43 | Check for any invalid values that may break the code later. 44 | 45 | Parameters 46 | ---------- 47 | N/A 48 | 49 | Returns 50 | ------- 51 | N/A 52 | 53 | """ 54 | if self.errors: 55 | return 56 | 57 | user = self.get_user(self.cleaned_data['username']) 58 | if self.cleaned_data['email'] != user.email: 59 | raise forms.ValidationError("""That email does not match the email 60 | for that username's account""") 61 | 62 | def send_mail(self, subject_template_name, email_template_name, context, 63 | from_email, to_email, html_email_template_name=None): 64 | """ 65 | Send a django.core.mail.EmailMultiAlternatives to `to_email`. 66 | 67 | Parameters 68 | ---------- 69 | subject_template_name : str 70 | The template for the email subject. 71 | email_template_name : str 72 | The template for the email. 73 | context : str 74 | The extra information (headers) needed for the email. 75 | from_email : str 76 | The email of the sender. 77 | to_email : str 78 | The email of the receiver. 79 | html_email_template_name : str, optional 80 | The template for the HTML email. 81 | 82 | Returns 83 | ------- 84 | N/A 85 | 86 | """ 87 | subject = loader.render_to_string(subject_template_name, context) 88 | # Email subject *must not* contain newlines 89 | subject = ''.join(subject.splitlines()) 90 | body = loader.render_to_string(email_template_name, context) 91 | 92 | email_message = EmailMultiAlternatives(subject, body, from_email, 93 | [to_email]) 94 | if html_email_template_name is not None: 95 | html_email = loader.render_to_string(html_email_template_name, 96 | context) 97 | email_message.attach_alternative(html_email, 'text/html') 98 | 99 | email_message.send() 100 | 101 | def get_user(self, username): 102 | """ 103 | Given a username, return matching user who should receive a reset. 104 | 105 | Parameters 106 | ---------- 107 | username : str 108 | The user's username. 109 | 110 | Returns 111 | ------- 112 | user : User object 113 | The object from the User model that matches the requested 114 | username. 115 | 116 | """ 117 | try: 118 | user = User.objects.get(username__exact=username) 119 | return user 120 | except User.DoesNotExist: 121 | self.add_error( 122 | 'username', 123 | ValidationError(_('That username was not found'), 124 | code='invalid') 125 | ) 126 | return None 127 | 128 | def save(self, domain_override=None, 129 | subject_template_name='registration/password_reset_subject.txt', 130 | email_template_name='registration/password_reset_email.html', 131 | from_email=None, request=None, html_email_template_name=None, 132 | extra_email_context=None): 133 | """ 134 | Generate a one-use only link for resetting password and send it to the 135 | user. 136 | 137 | Parameters 138 | ---------- 139 | domain_override : str, optional 140 | The name of the site. 141 | subject_template_name : str, optional 142 | The template for the email subject. 143 | email_template_name : str, optional 144 | The template for the email. 145 | from_email : str, optional 146 | The email of the sender. 147 | request : Request object, optional 148 | The current HTTP request. 149 | html_email_template_name : str, optional 150 | The template for the HTML email. 151 | extra_email_context : str, optional 152 | The extra information (headers) needed for the email. 153 | 154 | Returns 155 | ------- 156 | N/A 157 | 158 | """ 159 | username = self.cleaned_data['username'] 160 | email = self.cleaned_data['email'] 161 | user = self.get_user(username) 162 | 163 | if not domain_override: 164 | current_site = get_current_site(request) 165 | site_name = current_site.name 166 | domain = current_site.domain 167 | else: 168 | site_name = domain = domain_override 169 | 170 | context = { 171 | 'email': email, 172 | 'domain': domain, 173 | 'site_name': site_name, 174 | 'uid': urlsafe_base64_encode(force_bytes(user.pk)), 175 | 'user': user, 176 | 'token': password_reset_token.make_token(user), 177 | 'protocol': 'http' if domain.startswith('localhost') else 'https', 178 | **(extra_email_context or {}), 179 | } 180 | self.send_mail( 181 | subject_template_name, email_template_name, context, from_email, 182 | email, html_email_template_name=html_email_template_name 183 | ) 184 | 185 | 186 | class ChangePasswordForm(forms.Form): 187 | """ 188 | The form to change the user's password. 189 | """ 190 | username = UsernameField( 191 | widget = forms.TextInput(attrs={'autofocus': True}) 192 | ) 193 | password1 = forms.CharField(label=_('Password'), 194 | widget=forms.PasswordInput 195 | ) 196 | password2 = forms.CharField(label=_('Password'), 197 | widget=forms.PasswordInput 198 | ) 199 | class Meta: 200 | model = User 201 | fields = [] 202 | 203 | def clean(self): 204 | """ 205 | Check for any invalid values that may break the code later. 206 | 207 | Parameters 208 | ---------- 209 | N/A 210 | 211 | Returns 212 | ------- 213 | N/A 214 | 215 | """ 216 | if self.errors: 217 | return 218 | 219 | if self.cleaned_data['password1'] != self.cleaned_data['password2']: 220 | raise forms.ValidationError("""Your two passwords do not 221 | match. Please try again.""") 222 | 223 | def save(self): 224 | """ 225 | Save the new user's password. 226 | 227 | Parameters 228 | ---------- 229 | N/A 230 | 231 | Returns 232 | ------- 233 | N/A 234 | 235 | """ 236 | user = d_User.objects.get(username__exact=self.cleaned_data['username']) 237 | user.set_password(self.cleaned_data['password1']) 238 | user.save() 239 | -------------------------------------------------------------------------------- /waveform-django/website/middleware.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | try: 4 | from threading import local 5 | except ImportError: 6 | from django.utils._threading_local import local 7 | 8 | _thread_locals = local() 9 | 10 | 11 | def get_current_request(): 12 | """ returns the request object for this thread """ 13 | return getattr(_thread_locals, "request", None) 14 | 15 | 16 | def get_current_user(): 17 | """ returns the current user, if exist, otherwise returns None """ 18 | request = get_current_request() 19 | if request: 20 | temp_user = getattr(request, "user", None) 21 | return temp_user.username 22 | 23 | 24 | def thread_local_middleware(get_response): 25 | # One-time configuration and initialization. 26 | def middleware(request): 27 | # Code to be executed for each request before 28 | # the view (and later middleware) are called. 29 | _thread_locals.request = request 30 | response = get_response(request) 31 | # Code to be executed for each request/response after 32 | # the view is called. 33 | if hasattr(_thread_locals, "request"): 34 | del _thread_locals.request 35 | return response 36 | return middleware 37 | -------------------------------------------------------------------------------- /waveform-django/website/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for waveform annotation project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | import os 13 | 14 | from decouple import config 15 | 16 | # Basic settings based on development environment 17 | DEBUG = config('DEBUG', default=False, cast=bool) 18 | CACHE = config('CACHE', default=False, cast=bool) 19 | SESSION_COOKIE_SECURE = False 20 | SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' 21 | 22 | ALLOWED_HOSTS = ['*'] 23 | 24 | def custom_show_toolbar(request): 25 | return True 26 | 27 | DEBUG_TOOLBAR_CONFIG = { 28 | 'JQUERY_URL': '', 29 | # 'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar, 30 | } 31 | 32 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 33 | PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 34 | BASE_DIR = os.path.dirname(PROJECT_DIR) 35 | HEAD_DIR = os.path.dirname(BASE_DIR) 36 | LOGIN_URL = 'login' 37 | 38 | # For password resets 39 | # Check output in another terminal window here: 40 | # python -m smtpd -n -c DebuggingServer localhost:1025 41 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 42 | EMAIL_HOST = config('EMAIL_HOST', default='localhost') 43 | EMAIL_PORT = config('EMAIL_PORT', default=1025, cast=int) 44 | EMAIL_HOST_USER = '' 45 | EMAIL_HOST_PASSWORD = '' 46 | EMAIL_USE_TLS = False 47 | EMAIL_FROM = 'help@waveform-annotation.com' 48 | 49 | # Quick-start development settings - unsuitable for production 50 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 51 | 52 | SECRET_KEY = config('SECRET_KEY') 53 | 54 | # Application definition 55 | INSTALLED_APPS = [ 56 | 'django_crontab', 57 | 'debug_toolbar', 58 | 'django.contrib.auth', 59 | 'django.contrib.contenttypes', 60 | 'django.contrib.staticfiles', 61 | 'django_plotly_dash.apps.DjangoPlotlyDashConfig', 62 | 'django.contrib.sessions', 63 | 'django.contrib.messages', 64 | 65 | 'graphene_django', 66 | 67 | 'export', 68 | 'waveforms', 69 | 'website' 70 | ] 71 | 72 | MIDDLEWARE = [ 73 | 'django.middleware.security.SecurityMiddleware', 74 | 'whitenoise.middleware.WhiteNoiseMiddleware', 75 | 'django.contrib.sessions.middleware.SessionMiddleware', 76 | 'django.middleware.common.CommonMiddleware', 77 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 78 | 'django_plotly_dash.middleware.BaseMiddleware', 79 | 'django_plotly_dash.middleware.ExternalRedirectionMiddleware', 80 | 'django.middleware.csrf.CsrfViewMiddleware', 81 | 'django.contrib.sessions.middleware.SessionMiddleware', 82 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 83 | 'django.contrib.messages.middleware.MessageMiddleware', 84 | 'debug_toolbar.middleware.DebugToolbarMiddleware', 85 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 86 | 'website.middleware.thread_local_middleware' 87 | ] 88 | 89 | DATABASES = { 90 | 'default': { 91 | 'ENGINE': 'django.db.backends.sqlite3', 92 | 'NAME': os.path.join(HEAD_DIR,'db','db.sqlite3') 93 | } 94 | } 95 | 96 | # Cache 97 | if CACHE: 98 | # Requires `redis-server` to be running in another terminal tab 99 | CACHES = { 100 | 'default': { 101 | 'BACKEND': 'django_redis.cache.RedisCache', 102 | 'LOCATION': 'redis://localhost:6379', 103 | 'OPTIONS': { 104 | 'CLIENT_CLASS': 'django_redis.client.DefaultClient' 105 | } 106 | } 107 | } 108 | 109 | # .---------------- minute (0 - 59) 110 | # | .------------- hour (0 - 23) 111 | # | | .---------- day of month (1 - 31) 112 | # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... 113 | # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat 114 | # | | | | | 115 | # * * * * * user-name command to be executed 116 | CRONJOBS = [ 117 | ('0 0 * * *', 'waveform-django.cron.update_annotations') 118 | ] 119 | 120 | ROOT_URLCONF = 'website.urls' 121 | 122 | if not DEBUG: 123 | LOGGING = { 124 | 'version': 1, 125 | 'disable_existing_loggers': False, 126 | 'formatters': { 127 | 'timestamp': { 128 | 'format': '{asctime} {levelname} {message}', 129 | 'style': '{', 130 | }, 131 | }, 132 | 'handlers': { 133 | 'file': { 134 | 'level': 'DEBUG', 135 | 'class': 'logging.FileHandler', 136 | 'filename': os.path.join(HEAD_DIR, 'debug', 'debug.log'), 137 | 'formatter': 'timestamp' 138 | } 139 | }, 140 | 'loggers': { 141 | 'django': { 142 | 'handlers': ['file'], 143 | 'level': 'DEBUG', 144 | }, 145 | }, 146 | } 147 | 148 | TEMPLATES = [ 149 | { 150 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 151 | 'DIRS': [os.path.join(BASE_DIR,'templates')], 152 | 'APP_DIRS': True, 153 | 'OPTIONS': { 154 | 'context_processors': [ 155 | 'django.template.context_processors.debug', 156 | 'django.template.context_processors.request', 157 | 'django.contrib.auth.context_processors.auth', 158 | 'django.contrib.messages.context_processors.messages', 159 | ], 160 | }, 161 | }, 162 | ] 163 | 164 | X_FRAME_OPTIONS = 'SAMEORIGIN' 165 | 166 | PLOTLY_COMPONENTS = [ 167 | 'dash_core_components', 168 | 'dash_html_components', 169 | 'dash_renderer', 170 | 'dpd_components', 171 | 'dpd_static_support', 172 | 'dash_bootstrap_components' 173 | ] 174 | 175 | PLOTLY_DASH = { 176 | # Route used for the message pipe websocket connection 177 | 'ws_route': 'dpd/ws/channel', 178 | # Route used for direct http insertion of pipe messages 179 | 'http_route': 'dpd/views', 180 | # Flag controlling existince of http poke endpoint 181 | 'http_poke_enabled': True, 182 | # Insert data for the demo when migrating 183 | 'insert_demo_migrations': False, 184 | # Timeout for caching of initial arguments in seconds 185 | 'cache_timeout_initial_arguments': 60, 186 | # Name of view wrapping function 187 | 'view_decorator': None, 188 | # Flag to control location of initial argument storage 189 | 'cache_arguments': CACHE, 190 | # Flag controlling local serving of assets 191 | 'serve_locally': False 192 | } 193 | 194 | # Session management 195 | 196 | # Internationalization 197 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 198 | 199 | LANGUAGE_CODE = 'en-us' 200 | 201 | TIME_ZONE = 'America/New_York' 202 | 203 | USE_I18N = True 204 | 205 | USE_L10N = True 206 | 207 | USE_TZ = True 208 | 209 | # Django background tasks max attempts 210 | MAX_ATTEMPTS = 5 211 | 212 | # Static files (CSS, JavaScript, Images) 213 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 214 | #USE_X_FORWARDED_HOST = True 215 | #FORCE_SCRIPT_NAME = '/waveform-annotation' 216 | 217 | if DEBUG: 218 | STATIC_URL = '/static/' 219 | STATIC_ROOT = 'assets' 220 | STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] 221 | else: 222 | STATIC_URL = '/waveform-annotation/static/' 223 | STATIC_ROOT = os.path.join(HEAD_DIR, 'static') 224 | STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] 225 | STATICFILE_FINDERS = [ 226 | 'django.contrib.staticfiles.finder.FileSystemFinder', 227 | 'django.contrib.staticfiles.finder.AppDirectoriesFinder', 228 | 'django_plotly_dash.finders.DashAssetFinder', 229 | 'django_plotly_dash.finders.DashComponentFinder', 230 | 'django_plotly_dash.finders.DashAppDirectoryFinder' 231 | ] 232 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 233 | 234 | RECORDS_FILE = 'RECORDS_VTVF_LIMIT-5' 235 | ASSIGNMENT_FILE = 'user_assignments.csv' 236 | ALL_PROJECTS = ['sample_data'] 237 | 238 | # Projects in blacklist cannot be automatically assigned to users 239 | BLACKLIST = [] 240 | 241 | # The minimum amount of events to assign 242 | MIN_ASSIGNED = 10 243 | 244 | # Events to be used in the practice data set 245 | PRACTICE_SET = { 246 | 'sample_data': { 247 | 'v101l': False, 248 | 'v111l': False, 249 | 'v131l': True, 250 | 'v135l': False, 251 | 'v139l': True 252 | } 253 | } 254 | 255 | # List of permitted HTML tags and attributes for rich text fields. 256 | # The 'default' configuration permits all of the tags below. Other 257 | # configurations may be added that permit different sets of tags. 258 | 259 | # Attributes that can be added to any HTML tag 260 | _generic_attributes = ['lang', 'title'] 261 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/invite_user_email.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | 3 | Thank you for agreeing to help evaluate our new tool for expert-annotation of 4 | Ventricular Tachycardia (VT) and Ventricular Fibrillation (VF) alarms. We 5 | would like you to classify VT/VF alarms that have been generated by patient 6 | monitors as either “True” or “False”. You will find log-in here: 7 | 8 | {{ protocol }}://{{ domain }}{% url 'register' %} 9 | 10 | You can also find usage instructions here: 11 | 12 | {{ protocol }}://{{ domain }}{% url 'viewer_tutorial' %} 13 | 14 | If clicking any of the links above doesn't work, please copy and paste the URL 15 | in a new browser window instead. 16 | 17 | We would also love to set up a follow-up meeting to hear your feedback and 18 | suggestions for improvement. In particular, we are interested in understanding 19 | whether the tool provides all of the features that you need to perform the 20 | annotation task. If not, what are the specific features that are missing or 21 | could be improved upon? Please comment on usability, resolution, navigation, 22 | etc. 23 | 24 | Thank you again for your collaboration and happy annotating! 25 | 26 | Sincerely, 27 | The MIT VT/VF Annotation Team 28 | 29 | {% endautoescape %} 30 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/invite_user_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% autoescape off %} 2 | {% blocktrans %}Invitation to join {{ site_name }}{% endblocktrans %} 3 | {% endautoescape %} 4 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/new_invite_email.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | 3 | A new user has recently been invited with email: {{ email }}! 4 | You can find more details here: 5 | 6 | {{ protocol }}://{{ domain }}{% url 'admin_console' %} 7 | 8 | Thank you for your continued support! 9 | 10 | Sincerely, 11 | The MIT VT/VF Annotation Team 12 | 13 | {% endautoescape %} 14 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/new_invite_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% autoescape off %} 2 | {% blocktrans %}New user invited {{ site_name }}{% endblocktrans %} 3 | {% endautoescape %} 4 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/new_user_email.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | 3 | A new user has recently joined with email: {{ email }}! 4 | You can find more details here: 5 | 6 | {{ protocol }}://{{ domain }}{% url 'admin_console' %} 7 | 8 | Thank you for your continued support! 9 | 10 | Sincerely, 11 | The MIT VT/VF Annotation Team 12 | 13 | {% endautoescape %} 14 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/new_user_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% autoescape off %} 2 | {% blocktrans %}New user joined {{ site_name }}{% endblocktrans %} 3 | {% endautoescape %} 4 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block navbar%} {% endblock %} 3 | {% block content %} 4 | 5 | 6 |
7 |
8 |
9 |
10 |
11 |

Reset Password Final

12 |
13 |

14 | Your password has been reset. You may go ahead and sign in now. 15 |

16 |
17 |
18 |
19 | 20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block navbar%} {% endblock %} 3 | {% block content %} 4 | 5 | 6 |
7 |
8 |
9 |
10 |
11 |

Change Password

12 |
13 |
14 | {% csrf_token %} 15 |
16 |

Enter you username here: {{ form.username }}

17 |
18 |
19 |

Enter you new password here: {{ form.password1 }}

20 |
21 |
22 |

Re-enter you new password here: {{ form.password2 }}

23 |
24 | 25 |
26 | {% comment %} 27 |

28 | The password reset link was invalid, possibly because it has already been used. 29 | Please request a new password reset. 30 |

31 | {% endcomment %} 32 |

33 | Return to login page 34 |

35 |
36 |
37 |
38 | 39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block navbar%} {% endblock %} 3 | {% block content %} 4 | 5 | 6 |
7 |
8 |
9 |
10 |
11 |

Reset Password Confirm

12 |
13 |

14 | We've emailed you instructions for resetting your password, if an account exists with the username you entered. 15 | You should receive them shortly. 16 |

17 |

18 | If you don't receive an email, please make sure you've entered the username you registered with, 19 | and check your email's spam folder. 20 |

21 |

22 | Return to login page 23 |

24 |
25 |
26 |
27 | 28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/password_reset_email.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | 3 | To initiate the password reset process for your Account {{ user.username }}, 4 | click the link below: 5 | 6 | {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} 7 | 8 | If clicking the link above doesn't work, please copy and paste the URL in a new browser 9 | window instead. 10 | 11 | You will have one hour to reset your password, else please try again. 12 | 13 | Sincerely, 14 | The MIT VT/VF Annotation Team 15 | 16 | {% endautoescape %} 17 | -------------------------------------------------------------------------------- /waveform-django/website/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block navbar%} {% endblock %} 3 | {% block content %} 4 | 5 | 6 |
7 |
8 |
9 |
10 |
11 |

Reset Password

12 |
13 |
14 | {% csrf_token %} 15 |
16 |

Enter username here: {{ form.username }}

17 |
18 |
19 |

Enter your account's email here: {{ form.email }}

20 |
21 | 22 |
23 | {{ form.errors }} 24 |
25 |

26 | Remember your password? Return to login page 27 |

28 |
29 |
30 |
31 | 32 | 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /waveform-django/website/templates/website/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block navbar%} {% endblock %} 3 | {% block content %} 4 | 5 |
6 |
7 |
8 |
9 |
10 |

Login

11 |
12 |
13 |
14 | {% csrf_token %} 15 |
16 |

Enter username here:

17 |
18 |
19 |

Enter password here:

20 |
21 |
22 | 23 |
24 |
25 |
26 | {% for message in messages %} 27 |

28 | {{ message }} 29 |

30 | {% endfor %} 31 |
32 |
33 | Don't have an account? Sign Up 34 |
35 |
36 | Forgot your password? Reset Password 37 |
38 |
39 |
40 |
41 |

Instructions

42 |

    43 |
  1. To start, click on the "Sign Up" link below the "Login" button to create an account.
  2. 44 |
  3. Enter your desired username and password. You will be asked to enter your password twice to confirm.
  4. 45 |
  5. After creating an account, you can now sign in here.
  6. 46 |

47 |
48 |
49 |
50 |
51 | 52 | 53 | {% endblock %} 54 | -------------------------------------------------------------------------------- /waveform-django/website/templates/website/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block navbar%} {% endblock %} 3 | {% block content %} 4 | 5 | 6 |
7 |
8 |
9 |
10 |
11 |

Register Account

12 |
13 |
14 | {% csrf_token %} 15 |
16 |

Enter username here: {{ form.username }}

17 |
18 |
19 |

Enter email here: {{ form.email }}

20 |
21 |
22 |

Enter password here: {{ form.password1 }}

23 |
24 |
25 |

Re-enter password here: {{ form.password2 }}

26 |
27 |
28 | 29 |
30 |
31 | {{form.errors}} 32 |
33 |
34 |
35 | Already have an account? Login 36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /waveform-django/website/tokens.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.contrib.auth.tokens import PasswordResetTokenGenerator 4 | from django.utils import six 5 | 6 | 7 | class UserTokenGenerator(PasswordResetTokenGenerator): 8 | """ 9 | Generate a token for a user's new password. 10 | """ 11 | def _make_hash_value(self, user, timestamp): 12 | # Reset the link every hour 13 | # TODO: Add a last_login field and incorporate this to the token in 14 | # order to more robustly inactivate the link. 15 | t_now = datetime.datetime.now() 16 | t_epoch = datetime.datetime(1970, 1, 1) 17 | timestamp = int((t_now - t_epoch).total_seconds()/60/60) 18 | return ( 19 | six.text_type(user.pk) + user.username + 20 | six.text_type(timestamp) 21 | ) 22 | 23 | password_reset_token = UserTokenGenerator() 24 | -------------------------------------------------------------------------------- /waveform-django/website/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include 2 | from django.contrib.auth import views as auth_views 3 | from django.http import HttpResponse 4 | from django.urls import path 5 | 6 | import debug_toolbar 7 | from website import views 8 | 9 | 10 | urlpatterns = [ 11 | # Debug toolbar 12 | path('__debug__/', include(debug_toolbar.urls)), 13 | # Account creation and handling pages 14 | path('waveform-annotation/register/', 15 | views.register_page, name='register'), 16 | path('waveform-annotation/login/', 17 | views.login_page, name='login'), 18 | path('waveform-annotation/logout/', 19 | views.logout_user, name='logout'), 20 | path('waveform-annotation/password_reset/', 21 | views.reset_password, name='password_reset'), 22 | path('waveform-annotation/password_reset/done/', 23 | auth_views.PasswordResetCompleteView.as_view( 24 | template_name='registration/password_reset_done.html'), 25 | name='password_reset_done'), 26 | path('waveform-annotation/reset///', 27 | views.change_password, name='password_reset_confirm'), 28 | path('waveform-annotation/reset/done/', 29 | auth_views.PasswordResetCompleteView.as_view( 30 | template_name='registration/password_reset_complete.html'), 31 | name='password_reset_complete'), 32 | # Waveform annotator dashboard 33 | path('', views.redirect_home), 34 | path('waveform-annotation/waveforms/', include('waveforms.urls')), 35 | # GraphQL API interface 36 | path('waveform-annotation/', include('export.urls')), 37 | # Robots.txt for crawlers 38 | path('waveform-annotation/robots.txt', 39 | lambda x: HttpResponse('User-Agent: *\Allow: /', 40 | content_type='text/plain'), name='robots_file'), 41 | ] 42 | -------------------------------------------------------------------------------- /waveform-django/website/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib import messages 2 | from django.contrib.auth import authenticate, login, logout 3 | from django.contrib.auth.decorators import login_required 4 | from django.contrib.sites.shortcuts import get_current_site 5 | from django.shortcuts import render, redirect 6 | from django.utils import timezone 7 | 8 | from .forms import CreateUserForm, ResetPasswordForm, ChangePasswordForm 9 | from waveforms.models import InvitedEmails, User, UserSettings 10 | from website.settings import base 11 | 12 | 13 | def redirect_home(request): 14 | """ 15 | Redirects users to home page 16 | """ 17 | return redirect(login_page) 18 | 19 | 20 | def register_page(request): 21 | """ 22 | Create a new account upon request. 23 | 24 | Parameters 25 | ---------- 26 | request : Request object 27 | The current HTTP request. 28 | 29 | Returns 30 | ------- 31 | N/A 32 | 33 | """ 34 | if request.user.is_authenticated: 35 | return redirect('waveform_published_home') 36 | else: 37 | form = CreateUserForm() 38 | if request.method == 'POST': 39 | form = CreateUserForm(request.POST) 40 | if form.is_valid(): 41 | form.save() 42 | username = form.cleaned_data.get('username') 43 | email = form.cleaned_data.get('email') 44 | try: 45 | invited_user = InvitedEmails.objects.get(email=email) 46 | invited_user.joined = True 47 | invited_user.joined_username = username 48 | invited_user.save() 49 | except InvitedEmails.DoesNotExist: 50 | pass 51 | messages.success(request, 52 | f'Account was created for {username}') 53 | # Create the default profile and settings for that user 54 | new_user = User(username=username, email=email, 55 | is_admin=False) 56 | new_user.save() 57 | UserSettings(user=new_user).save() 58 | # Send email to all admins notifying about new user 59 | admin_users = User.objects.filter(is_admin=True) 60 | current_site = get_current_site(request) 61 | domain = current_site.domain 62 | context = { 63 | 'protocol': 'http' if domain.startswith('localhost') else 'https', 64 | 'domain': domain, 65 | 'site_name': current_site.name, 66 | 'email': email 67 | } 68 | email_form = ResetPasswordForm() 69 | for admin_user in admin_users: 70 | email_form.send_mail( 71 | 'registration/new_user_subject.txt', 72 | 'registration/new_user_email.html', context, 73 | base.EMAIL_FROM, admin_user.email 74 | ) 75 | return redirect('login') 76 | return render(request, 'website/register.html', {'form': form}) 77 | 78 | 79 | def login_page(request): 80 | """ 81 | Login the user upon request. 82 | 83 | Parameters 84 | ---------- 85 | request : Request object 86 | The current HTTP request. 87 | 88 | Returns 89 | ------- 90 | N/A 91 | 92 | """ 93 | if request.user.is_authenticated: 94 | return redirect('waveform_published_home') 95 | else: 96 | if request.method == 'POST': 97 | username = request.POST.get('username') 98 | password =request.POST.get('password') 99 | user = authenticate(request, username=username, password=password) 100 | if user is not None: 101 | login(request, user) 102 | user = User.objects.get(username=username) 103 | user.last_login = timezone.now() 104 | user.save() 105 | if 'annotations' in request.environ['QUERY_STRING']: 106 | return redirect('render_annotations') 107 | else: 108 | return redirect('waveform_published_home') 109 | else: 110 | messages.info(request, 'Username OR password is incorrect') 111 | return render(request, 'website/login.html', {}) 112 | else: 113 | if (request.GET.dict() == {}) or not (request.user.is_authenticated): 114 | return render(request, 'website/login.html', {}) 115 | elif 'annotations' in request.GET.dict()['next']: 116 | return redirect('render_annotations') 117 | else: 118 | return redirect('waveform_published_home') 119 | 120 | 121 | def reset_password(request): 122 | """ 123 | Reset the user's password. 124 | 125 | Parameters 126 | ---------- 127 | request : Request object 128 | The current HTTP request. 129 | 130 | Returns 131 | ------- 132 | N/A 133 | 134 | """ 135 | form = ResetPasswordForm() 136 | if request.method == 'POST': 137 | form = ResetPasswordForm(request.POST) 138 | if form.is_valid(): 139 | form.save( 140 | from_email = base.EMAIL_FROM, 141 | request = request 142 | ) 143 | return redirect('password_reset_done') 144 | return render(request, 'registration/password_reset_form.html', 145 | {'form': form}) 146 | 147 | 148 | def change_password(request): 149 | """ 150 | Change the user's password. 151 | 152 | Parameters 153 | ---------- 154 | request : Request object 155 | The current HTTP request. 156 | 157 | Returns 158 | ------- 159 | N/A 160 | 161 | """ 162 | form = ChangePasswordForm() 163 | if request.method == 'POST': 164 | form = ChangePasswordForm(request.POST) 165 | if form.is_valid(): 166 | form.save() 167 | messages.info(request, 'Changed password successfully!') 168 | return redirect('login') 169 | return render(request, 'registration/password_reset_confirm.html', 170 | {'form': form}) 171 | 172 | 173 | @login_required 174 | def logout_user(request): 175 | """ 176 | Logout the user upon request. 177 | 178 | Parameters 179 | ---------- 180 | request : Request object 181 | The current HTTP request. 182 | 183 | Returns 184 | ------- 185 | N/A 186 | 187 | """ 188 | logout(request) 189 | messages.info(request, 'Logged out successfully!') 190 | return redirect('login') 191 | -------------------------------------------------------------------------------- /waveform-django/website/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for website project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "website.settings.base") 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------