This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
biomon/src/biomon/livedata_provider/views.py

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')