273 lines
12 KiB
Python
273 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
'''
|
|
biomon - Signs monitoring and patient management application
|
|
|
|
Copyright (C) 2015 Entr'ouvert
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
'''
|
|
|
|
|
|
import urllib
|
|
import urllib2
|
|
import urlparse
|
|
import json
|
|
import logging
|
|
import copy
|
|
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
|
|
from django.views.generic import DetailView, View
|
|
from django.http import Http404, HttpResponse
|
|
from django.conf import settings
|
|
from django.utils.decorators import method_decorator
|
|
from django.contrib.auth.decorators import permission_required
|
|
from functools import partial
|
|
|
|
import matplotlib
|
|
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
|
|
|
from .. import whisper_backend
|
|
from .. import models
|
|
from . import graphite_custom
|
|
from ..medibot import models as medibot_models
|
|
from ..medibot import definitions as definition_utils
|
|
|
|
permission_required = partial(permission_required, raise_exception=True)
|
|
|
|
|
|
def get_int_parameter(request, name, default):
|
|
if name in request.GET:
|
|
try:
|
|
return int(request.GET.get(name))
|
|
except:
|
|
pass
|
|
return default
|
|
|
|
|
|
class LoggerMixin(object):
|
|
def __init__(self, *args, **kwargs):
|
|
self.logger = logging.getLogger(__name__)
|
|
super(LoggerMixin, *args, **kwargs)
|
|
|
|
|
|
class ProtectedView(View):
|
|
@method_decorator(permission_required('biomon.delete_patient'))
|
|
def dispatch(self, *args, **kwargs):
|
|
return super(ProtectedView, self).dispatch(*args, **kwargs)
|
|
|
|
|
|
class BasicDetailView(LoggerMixin, ProtectedView, DetailView):
|
|
pass
|
|
|
|
|
|
class RenderView(BasicDetailView):
|
|
model = models.Patient
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
subject = str(self.get_object().id)
|
|
metric = request.GET.get("metric")
|
|
if not metric:
|
|
raise Http404('No metric specified')
|
|
|
|
# Shared ref on a mutable, prevent error when the view is called multiple times in parallel
|
|
qs_dic = copy.deepcopy(graphite_custom.RENDER_PARAMS)
|
|
until = get_int_parameter(request, 'until', None)
|
|
duration = get_int_parameter(request, 'duration', 0)
|
|
if metric == settings.WHISPER_TEMPERATURE_METRIC:
|
|
qs_dic["vtitle"] = "Celsius"
|
|
backend = whisper_backend.WhisperBackend(subject, settings.WHISPER_TEMPERATURE_METRIC)
|
|
margin = 1
|
|
else:
|
|
qs_dic["vtitle"] = "BPM"
|
|
backend = whisper_backend.WhisperBackend(subject, settings.WHISPER_HEARTRATE_METRIC)
|
|
margin = 10
|
|
qs_dic["yMin"] = backend.get_min(duration=duration, until=until) - margin
|
|
qs_dic["yMax"] = backend.get_max(duration=duration, until=until) + margin
|
|
metric_fp = '.'.join([settings.WHISPER_METRIC_PATH, subject, metric])
|
|
target_l = [metric_fp]
|
|
mapping = {v: k for k, v in settings.SENSOR_MAPPING.items()}
|
|
color = "5e9f05"
|
|
if medibot_models.Episode.objects.filter(patient=self.get_object(), level='CRITICAL', opened=True, metric=mapping[metric]).exists():
|
|
color = "9E2125"
|
|
elif medibot_models.Episode.objects.filter(patient=self.get_object(), level='DANGEROUS', opened=True, metric=mapping[metric]).exists():
|
|
color = "E05400"
|
|
color_l = [color]
|
|
|
|
qs = urlparse.parse_qs(request.META.get('QUERY_STRING', ''))
|
|
for key in ("until", "from", "fresh"):
|
|
qs_dic[key] = request.GET.get(key, None)
|
|
|
|
|
|
if self.get_object().alert_profile:
|
|
definitions = definition_utils.get_unique_elementary_definitions(self.get_object().get_alert_profile())
|
|
definitions = definition_utils.definition_list_to_dic(definitions)
|
|
if 'CRITICAL' in definitions:
|
|
if metric in mapping and mapping[metric] in definitions['CRITICAL']:
|
|
operators = definitions['CRITICAL'][mapping[metric]]
|
|
if '<' in operators:
|
|
value = operators['<'][0][0]
|
|
if value > qs_dic["yMin"]:
|
|
target_l.append("constantLine(%s)" % value)
|
|
color_l.append("9E2125")
|
|
if '>' in operators:
|
|
value = operators['>'][0][0]
|
|
if value < qs_dic["yMax"]:
|
|
target_l.append("constantLine(%s)" % value)
|
|
color_l.append("9E2125")
|
|
if 'DANGEROUS' in definitions:
|
|
if metric in mapping and mapping[metric] in definitions['DANGEROUS']:
|
|
operators = definitions['DANGEROUS'][mapping[metric]]
|
|
if '<' in operators:
|
|
value = operators['<'][0][0]
|
|
if value > qs_dic["yMin"]:
|
|
target_l.append("constantLine(%s)" % value)
|
|
color_l.append("E05400")
|
|
if '>' in operators:
|
|
value = operators['>'][0][0]
|
|
if value < qs_dic["yMax"]:
|
|
target_l.append("constantLine(%s)" % value)
|
|
color_l.append("E05400")
|
|
if getattr(settings, 'GRAPHITE_HOST', False):
|
|
qs = urllib.urlencode(qs_dic)
|
|
targets = '&'.join(['='.join(('target', value)) for value in target_l])
|
|
qs += '&' + targets
|
|
qs += '&colorList=' + ','.join(color_l)
|
|
url = "{0}{1}?{2}".format(settings.GRAPHITE_HOST, "/render/", qs)
|
|
rendering = urllib2.urlopen(url).read()
|
|
if rendering:
|
|
return HttpResponse(rendering, content_type='image/png')
|
|
raise Http404('Unable to fetch rendering')
|
|
raise Http404('No graphite host specified')
|
|
|
|
|
|
class InstantsView(BasicDetailView):
|
|
model = models.Patient
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
subject = str(self.get_object().id)
|
|
until = get_int_parameter(request, 'until', None)
|
|
values = list()
|
|
backend = whisper_backend.WhisperBackend(subject, settings.WHISPER_HEARTRATE_METRIC)
|
|
values.append(backend.get_last_value(until=until))
|
|
backend = whisper_backend.WhisperBackend(subject, settings.WHISPER_TEMPERATURE_METRIC)
|
|
values.append(backend.get_last_value(until=until, lookup_range=300))
|
|
return HttpResponse(json.dumps(values), content_type='application/json')
|
|
|
|
|
|
class MeansView(BasicDetailView):
|
|
model = models.Patient
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
subject = str(self.get_object().id)
|
|
until = get_int_parameter(request, 'until', None)
|
|
duration = get_int_parameter(request, 'duration', 0)
|
|
values = list()
|
|
backend = whisper_backend.WhisperBackend(subject, settings.WHISPER_HEARTRATE_METRIC)
|
|
values.append(backend.get_mean(duration=duration, until=until))
|
|
backend = whisper_backend.WhisperBackend(subject, settings.WHISPER_TEMPERATURE_METRIC)
|
|
values.append(backend.get_mean(duration=duration, until=until))
|
|
return HttpResponse(json.dumps(values), content_type='application/json')
|
|
|
|
|
|
def get_ax():
|
|
fig = plt.figure()
|
|
fig.patch.set_facecolor('black')
|
|
ax = fig.add_subplot(111, axisbg='black')
|
|
ax.spines['bottom'].set_color('#26B3F7')
|
|
ax.spines['top'].set_color('#26B3F7')
|
|
ax.spines['left'].set_color('#26B3F7')
|
|
ax.spines['right'].set_color('#26B3F7')
|
|
ax.xaxis.label.set_color('#26B3F7')
|
|
ax.tick_params(axis='x', colors='#26B3F7')
|
|
ax.yaxis.label.set_color('#26B3F7')
|
|
ax.tick_params(axis='y', colors='#26B3F7')
|
|
ax.set_ylabel('Frequence')
|
|
ax.grid(True, color='white')
|
|
return fig, ax
|
|
|
|
|
|
class HistogramView(BasicDetailView):
|
|
model = models.Patient
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
subject = str(self.get_object().id)
|
|
metric = request.GET.get("metric")
|
|
_from = get_int_parameter(request, 'from', None)
|
|
until = get_int_parameter(request, 'until', None)
|
|
if not _from or not until or not metric:
|
|
raise Http404('Missing parameter')
|
|
backend = whisper_backend.WhisperBackend(subject, metric)
|
|
data = backend.get_data(_from, until)
|
|
if not data or len(data) < 2:
|
|
raise Http404('No data')
|
|
fig, ax = get_ax()
|
|
if metric == settings.WHISPER_HEARTRATE_METRIC:
|
|
bins = np.arange(min(data), max(data) + 10, 5)
|
|
else:
|
|
bins = np.arange(min(data), max(data) + 1, 0.5)
|
|
ax.hist(data, bins=bins, color='#26B3F7', align='left')
|
|
ax.set_xlabel(metric)
|
|
ax.set_xticks(bins[:-1])
|
|
ax.axvline(sum(data)/len(data), color='w', linestyle='dashed', linewidth=5)
|
|
canvas = FigureCanvasAgg(fig)
|
|
response = HttpResponse(content_type='image/png')
|
|
canvas.print_png(response)
|
|
matplotlib.pyplot.close(fig)
|
|
return response
|
|
|
|
|
|
class MinMaxView(BasicDetailView):
|
|
model = models.Patient
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
subject = str(self.get_object().id)
|
|
until = get_int_parameter(request, 'until', None)
|
|
duration = get_int_parameter(request, 'duration', 0)
|
|
values = list()
|
|
backend = whisper_backend.WhisperBackend(subject, settings.WHISPER_HEARTRATE_METRIC)
|
|
values.append(backend.get_min(duration=duration, until=until))
|
|
values.append(backend.get_max(duration=duration, until=until))
|
|
backend = whisper_backend.WhisperBackend(subject, settings.WHISPER_TEMPERATURE_METRIC)
|
|
values.append(backend.get_min(duration=duration, until=until))
|
|
values.append(backend.get_max(duration=duration, until=until))
|
|
return HttpResponse(json.dumps(values), content_type='application/json')
|
|
|
|
class PatientAllEpisodesView(BasicDetailView):
|
|
model = models.Patient
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
subject = str(self.get_object().id)
|
|
values = [[], []]
|
|
for episode in medibot_models.Episode.objects.filter(patient=self.get_object(), opened=True).order_by('-end'):
|
|
values[0].append((episode.start.isoformat(), episode.end.isoformat(), json.loads(episode.definition), episode.duration, episode.start.date().isoformat()))
|
|
for episode in medibot_models.Episode.objects.filter(patient=self.get_object()).order_by('-end'):
|
|
values[1].append((episode.start.isoformat(), episode.end.isoformat(), json.loads(episode.definition), episode.duration, episode.end.date().isoformat()))
|
|
return HttpResponse(json.dumps(values), content_type='application/json')
|
|
|
|
class PatientEpisodesStatsView(BasicDetailView):
|
|
model = models.Patient
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
subject = str(self.get_object().id)
|
|
criticals = medibot_models.Episode.objects.filter(patient=self.get_object(), level='CRITICAL')
|
|
criticals_count = criticals.count()
|
|
criticals_active = criticals.filter(opened=True).exists()
|
|
dangerous = medibot_models.Episode.objects.filter(patient=self.get_object(), level='DANGEROUS')
|
|
dangerous_count = dangerous.count()
|
|
dangerous_active = dangerous.filter(opened=True).exists()
|
|
return HttpResponse(json.dumps([criticals_count, criticals_active, dangerous_count, dangerous_active]), content_type='application/json')
|