Skip to content

Commit 42e7399

Browse files
committed
public
0 parents  commit 42e7399

Some content is hidden

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

59 files changed

+4284
-0
lines changed

Dockerfile

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
FROM python:3.7-slim
2+
3+
# Create a group and user to run our app
4+
ARG APP_USER=appuser
5+
RUN groupadd -r ${APP_USER} && useradd --no-log-init -r -g ${APP_USER} ${APP_USER}
6+
7+
# Install packages needed to run your application (not build deps):
8+
# mime-support -- for mime types when serving static files
9+
# postgresql-client -- for running database commands
10+
# We need to recreate the /usr/share/man/man{1..8} directories first because
11+
# they were clobbered by a parent image.
12+
RUN set -ex \
13+
&& RUN_DEPS=" \
14+
libcurl4-openssl-dev \
15+
libssl-dev \
16+
libpcre3 \
17+
mime-support \
18+
postgresql-client \
19+
" \
20+
&& seq 1 8 | xargs -I{} mkdir -p /usr/share/man/man{} \
21+
&& apt-get update && apt-get install -y --no-install-recommends $RUN_DEPS \
22+
&& rm -rf /var/lib/apt/lists/*
23+
24+
# Copy in your requirements file
25+
ADD requirements.txt /requirements.txt
26+
27+
# OR, if you're using a directory for your requirements, copy everything (comment out the above and uncomment this if so):
28+
# ADD requirements /requirements
29+
30+
# Install build deps, then run `pip install`, then remove unneeded build deps all in a single step.
31+
# Correct the path to your production requirements file, if needed.
32+
RUN set -ex \
33+
&& BUILD_DEPS=" \
34+
build-essential \
35+
libpcre3-dev \
36+
libpq-dev \
37+
" \
38+
&& apt-get update && apt-get install -y --no-install-recommends $BUILD_DEPS \
39+
&& pip install --no-cache-dir -r /requirements.txt \
40+
&& pip install --no-cache-dir uwsgi \
41+
\
42+
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $BUILD_DEPS \
43+
&& rm -rf /var/lib/apt/lists/*
44+
45+
# Copy your application code to the container (make sure you create a .dockerignore file if any large files or directories should be excluded)
46+
RUN mkdir /code/
47+
WORKDIR /code/
48+
ADD . /code/
49+
50+
# uWSGI will listen on this port
51+
EXPOSE 80
52+
53+
# Add any static environment variables needed by Django or your settings file here:
54+
ENV DJANGO_SETTINGS_MODULE=motionlab.settings
55+
56+
# Call collectstatic (customize the following line with the minimal environment variables needed for manage.py to run):
57+
# RUN DATABASE_URL='' python manage.py collectstatic --noinput
58+
59+
RUN mkdir data ; python manage.py migrate ; python manage.py loaddata fixtures/startup-data.json
60+
61+
# Tell uWSGI where to find your wsgi file (change this):
62+
ENV UWSGI_WSGI_FILE=motionlab/wsgi.py
63+
64+
# Base uWSGI configuration (you shouldn't need to change these):
65+
ENV UWSGI_MASTER=1 UWSGI_HTTP_AUTO_CHUNKED=1 UWSGI_HTTP_KEEPALIVE=1 UWSGI_LAZY_APPS=1 UWSGI_WSGI_ENV_BEHAVIOR=holy
66+
ENV UWSGI_HTTP=0.0.0.0:80
67+
#ENV UWSGI_HTTPS=0.0.0.0:443,/keys/fullchain.pem,/keys/privkey.pem
68+
69+
# Number of uWSGI workers and threads per worker (customize as needed):
70+
ENV UWSGI_WORKERS=2 UWSGI_THREADS=4
71+
72+
# uWSGI static file serving configuration (customize or comment out if not needed):
73+
# ENV UWSGI_STATIC_MAP="/static/=/code/static/" UWSGI_STATIC_EXPIRES_URI="/static/.*\.[a-f0-9]{12,}\.(css|js|png|jpg|jpeg|gif|ico|woff|ttf|otf|svg|scss|map|txt) 315360000"
74+
75+
# Deny invalid hosts before they get to Django (uncomment and change to your hostname(s)):
76+
# ENV UWSGI_ROUTE_HOST="^(?!localhost:8000$) break:400"
77+
78+
# Change to a non-root user
79+
# USER ${APP_USER}:${APP_USER}
80+
81+
# Uncomment after creating your docker-entrypoint.sh
82+
# ENTRYPOINT ["/code/docker-entrypoint.sh"]
83+
84+
# Start uWSGI
85+
CMD ["uwsgi", "--show-config"]

Makefile

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.PHONY: docker
2+
docker:
3+
docker build . -t stanfordnmbl/motionlab
4+
5+
.PHONY: push
6+
push:
7+
docker push stanfordnmbl/motionlab
8+
9+
.PHONY: stop
10+
stop:
11+
docker kill motionlab_openpose 2> /dev/null
12+
docker rm motionlab_openpose 2> /dev/null
13+
docker-compose down
14+
15+
.PHONY: start
16+
start:
17+
docker-compose up -d
18+
# -v $(pwd)/.env:/code/.env
19+
docker run --name motionlab_openpose --link motionlab_redis_1:redis --link motionlab_www_1:www --net motionlab_default --gpus all -d --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 stanfordnmbl/motionlab-worker /bin/sh -c 'celery -A worker worker --loglevel=info --concurrency=1 --pool=solo'
20+
21+
run: docker stop start

README.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# sit2stand.ai
2+
3+
[Sit2stand.ai](https://sit2stand.ai/) is a web-app for deriving health-related metrics from a mobile phone video.
4+
5+
## Quick start
6+
7+
The platform is developped in django so it can be deployed as any other django application
8+
9+
1. Install requirements with
10+
```
11+
pip install -r requirements.txt
12+
```
13+
2. Check settings in `motionlab/settings.py`. In particular, make sure to configure it to run on your database and AWS S3 servers.
14+
3. Migrate the database with
15+
```
16+
python manage.py migrate
17+
```
18+
4. Start the server with
19+
```
20+
python manage.py runserver
21+
```
22+
5. Visit http://127.0.0.1:8000/ in your browser

docker-compose.yaml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: '3.3'
2+
3+
services:
4+
www:
5+
image: stanfordnmbl/motionlab
6+
ports:
7+
- "80:80"
8+
env_file:
9+
- ".env"
10+
depends_on:
11+
- redis
12+
volumes:
13+
- ./data:/code/data
14+
- ./keys:/keys
15+
# http2https:
16+
# image: http2https
17+
# ports:
18+
# - "80:80"
19+
redis:
20+
image: redis
21+
command: redis-server --requirepass ZXdLkEtzvjsye6
22+
ports:
23+
- "6379:6379"

fixtures/startup-data.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "motionlab", "model": "video"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "motionlab", "model": "annotation"}}, {"model": "motionlab.video", "pk": 1, "fields": {"slug": "T8h7xpSW", "file": "output.mp4", "email": null}}, {"model": "motionlab.annotation", "pk": 1, "fields": {"video": 1, "file": "video.mp4", "response": {}, "status": "done"}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add log entry", "content_type": 1, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change log entry", "content_type": 1, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete log entry", "content_type": 1, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can view log entry", "content_type": 1, "codename": "view_logentry"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can add permission", "content_type": 2, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can change permission", "content_type": 2, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can delete permission", "content_type": 2, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can view permission", "content_type": 2, "codename": "view_permission"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can add group", "content_type": 3, "codename": "add_group"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can change group", "content_type": 3, "codename": "change_group"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can delete group", "content_type": 3, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can view group", "content_type": 3, "codename": "view_group"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add user", "content_type": 4, "codename": "add_user"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change user", "content_type": 4, "codename": "change_user"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete user", "content_type": 4, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can view user", "content_type": 4, "codename": "view_user"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can add content type", "content_type": 5, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can change content type", "content_type": 5, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can delete content type", "content_type": 5, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can view content type", "content_type": 5, "codename": "view_contenttype"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can add session", "content_type": 6, "codename": "add_session"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can change session", "content_type": 6, "codename": "change_session"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can delete session", "content_type": 6, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can view session", "content_type": 6, "codename": "view_session"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add video", "content_type": 7, "codename": "add_video"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change video", "content_type": 7, "codename": "change_video"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete video", "content_type": 7, "codename": "delete_video"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can view video", "content_type": 7, "codename": "view_video"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can add annotation", "content_type": 8, "codename": "add_annotation"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can change annotation", "content_type": 8, "codename": "change_annotation"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can delete annotation", "content_type": 8, "codename": "delete_annotation"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can view annotation", "content_type": 8, "codename": "view_annotation"}}, {"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$180000$1QXPT26MLfPo$nEdMIhCaK3jGxxGdWCWQ/x3cGJbehqlRfCRxyVxnBtk=", "last_login": null, "is_superuser": true, "username": "admin", "first_name": "", "last_name": "", "email": "", "is_staff": true, "is_active": true, "date_joined": "2020-10-03T18:51:28.156Z", "groups": [], "user_permissions": []}}]

manage.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env python
2+
"""Django's command-line utility for administrative tasks."""
3+
import os
4+
import sys
5+
6+
7+
def main():
8+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'motionlab.settings')
9+
try:
10+
from django.core.management import execute_from_command_line
11+
except ImportError as exc:
12+
raise ImportError(
13+
"Couldn't import Django. Are you sure it's installed and "
14+
"available on your PYTHONPATH environment variable? Did you "
15+
"forget to activate a virtual environment?"
16+
) from exc
17+
execute_from_command_line(sys.argv)
18+
19+
20+
if __name__ == '__main__':
21+
main()

motionlab/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from __future__ import absolute_import, unicode_literals
2+
3+
# This will make sure the app is always imported when
4+
# Django starts so that shared_task will use this app.
5+
from .celery import app as celery_app
6+
7+
__all__ = ('celery_app',)

motionlab/admin.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.contrib import admin
2+
from motionlab.models import Video, Annotation
3+
4+
class AnnotationAdmin(admin.ModelAdmin):
5+
list_display = ('video', 'file', 'status')
6+
7+
class VideoAdmin(admin.ModelAdmin):
8+
readonly_fields = ["slug",]
9+
fields = ["slug","recordid","file","email",]
10+
list_display = ('pk','recordid', 'slug', 'file', 'email')
11+
12+
admin.site.register(Annotation, AnnotationAdmin)
13+
admin.site.register(Video, VideoAdmin)

motionlab/asgi.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
ASGI config for gaitlab project.
3+
4+
It exposes the ASGI callable as a module-level variable named ``application``.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
8+
"""
9+
10+
import os
11+
12+
from django.core.asgi import get_asgi_application
13+
14+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'motionlab.settings')
15+
16+
application = get_asgi_application()

motionlab/celery.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import os
2+
from celery import Celery
3+
4+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')
5+
6+
app = Celery('motionlab')
7+
8+
app.config_from_object('django.conf:settings', namespace='CELERY')
9+
10+
app.autodiscover_tasks()

motionlab/custom_storages.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from django.conf import settings
2+
from storages.backends.s3boto3 import S3Boto3Storage
3+
4+
class StaticStorage(S3Boto3Storage):
5+
location = settings.STATICFILES_LOCATION
6+
7+
class MediaStorage(S3Boto3Storage):
8+
location = settings.MEDIAFILES_LOCATION

motionlab/forms.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from django import forms
2+
from django.forms import ModelForm
3+
from motionlab.models import Video
4+
from django import forms
5+
from django.utils.safestring import mark_safe
6+
from django.forms import ModelForm, FileInput
7+
8+
class VideoForm(ModelForm):
9+
# terms_confirmed = forms.BooleanField(label=mark_safe("I've read and accept <a href='#license'>the terms of use</a>."))
10+
11+
class Meta:
12+
model = Video
13+
fields = ["file", "recordid"]
14+
widgets = {
15+
# 'file': FileInput(attrs={'class': "dropzone"}),
16+
}
17+
18+
class ContactForm(forms.Form):
19+
your_email = forms.EmailField(required=True)
20+
subject = forms.CharField(required=True)
21+
message = forms.CharField(widget=forms.Textarea(attrs={'style': 'min-height: 7em;'}), required=True)
22+
23+
class ApplicationForm(forms.Form):
24+
your_name = forms.CharField(required=True)
25+
your_email = forms.EmailField(required=True)
26+
your_state = forms.CharField(required=True)
27+
how = forms.CharField(widget=forms.Textarea(attrs={'style': 'min-height: 7em;'}), required=True, label="How you heard about the study?")

motionlab/migrations/0001_initial.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by Django 3.0.8 on 2021-01-27 19:44
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
import jsonfield.fields
6+
import randomslugfield.fields
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
initial = True
12+
13+
dependencies = [
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name='Video',
19+
fields=[
20+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21+
('slug', randomslugfield.fields.RandomSlugField(blank=True, editable=False, length=8, max_length=8, unique=True)),
22+
('file', models.FileField(upload_to='', verbose_name='Video file')),
23+
('email', models.EmailField(max_length=254, null=True)),
24+
('recordid', models.CharField(max_length=32, unique=True)),
25+
],
26+
),
27+
migrations.CreateModel(
28+
name='Annotation',
29+
fields=[
30+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
31+
('file', models.FileField(null=True, upload_to='')),
32+
('response', jsonfield.fields.JSONField(blank=True, null=True)),
33+
('status', models.CharField(choices=[('submitted', 'Submitted'), ('processing', 'Processing'), ('done', 'Done'), ('error', 'Error')], default='submitted', max_length=10)),
34+
('video', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='motionlab.Video')),
35+
],
36+
),
37+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.0.8 on 2021-03-10 05:03
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('motionlab', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='video',
15+
name='recordid',
16+
field=models.CharField(max_length=32),
17+
),
18+
]

motionlab/migrations/__init__.py

Whitespace-only changes.

motionlab/models.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from django.db import models
2+
import jsonfield
3+
from randomslugfield import RandomSlugField
4+
from django.urls import reverse
5+
6+
import uuid
7+
import os
8+
9+
def get_file_path(directory):
10+
def func(instance, filename):
11+
ext = filename.split('.')[-1]
12+
filename = "%s.%s" % (uuid.uuid4(), ext)
13+
return os.path.join(directory, filename)
14+
return func
15+
16+
STATUS_CHOICES = [
17+
("submitted","Submitted"),
18+
("processing","Processing"),
19+
("done","Done"),
20+
("error","Error"),
21+
]
22+
23+
class Video(models.Model):
24+
slug = RandomSlugField(length=8)
25+
file = models.FileField("Video file", upload_to=get_file_path("inputs"))
26+
email = models.EmailField(null=True)
27+
recordid = models.CharField(null=False, max_length=32)
28+
29+
def get_absolute_url(self):
30+
return reverse('analysis', args=[str(self.slug)])
31+
32+
def __str__(self):
33+
return self.slug.__str__()
34+
35+
class Annotation(models.Model):
36+
video = models.ForeignKey(Video, on_delete=models.CASCADE)
37+
file = models.FileField(upload_to=get_file_path("outputs"),
38+
null=True)
39+
response = jsonfield.JSONField(null=True, blank=True)
40+
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='submitted')

0 commit comments

Comments
 (0)