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

391 lines
17 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 copy
import dateutil.parser
from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
from django.http import Http404, HttpResponse
from django.conf import settings
import matplotlib
from matplotlib.backends.backend_agg import FigureCanvasAgg
from .. import cbv
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
CRITICAL_COLOR = "FF0000"
DANGEROUS_COLOR = "FFA500"
#NORMAL_COLOR = "008000"
NORMAL_COLOR = "B0B0B0"
def get_int_parameter(request, name, default):
if name in request.GET:
try:
return int(request.GET.get(name))
except:
pass
return default
def get_isodate_parameter(request, name, default):
if name in request.GET:
try:
return dateutil.parser.parse(request.GET.get(name))
except:
pass
return default
class RenderView(cbv.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 = 2
else:
qs_dic["vtitle"] = "BPM"
backend = whisper_backend.WhisperBackend(subject, settings.WHISPER_HEARTRATE_METRIC)
margin = 20
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 = NORMAL_COLOR
""" Chaging metric line color when there a running alert is not
very clean for the user, especially because the time frame may be
wider than the episode duration and then the color be not appropriate
at some time.
if medibot_models.Episode.objects.filter(patient=self.get_object(), level='CRITICAL', opened=True, metric=mapping[metric]).exists():
color = CRITICAL_COLOR
elif medibot_models.Episode.objects.filter(patient=self.get_object(), level='DANGEROUS', opened=True, metric=mapping[metric]).exists():
color = DANGEROUS_COLOR"""
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)
sap = self.get_object().get_simple_alert_profile()
if sap:
prefix = ''
if metric == settings.WHISPER_TEMPERATURE_METRIC:
prefix = 't'
else:
prefix = 'hr'
value = sap.get(prefix + '_max_critical', None)
if value and value < qs_dic["yMax"]:
target_l.append("constantLine(%s)" % value)
color_l.append(CRITICAL_COLOR)
value = sap.get(prefix + '_min_critical', None)
if value and value > qs_dic["yMin"]:
target_l.append("constantLine(%s)" % value)
color_l.append(CRITICAL_COLOR)
value = sap.get(prefix + '_max_dangerous', None)
if value and value < qs_dic["yMax"]:
target_l.append("constantLine(%s)" % value)
color_l.append(DANGEROUS_COLOR)
value = sap.get(prefix + '_min_dangerous', None)
if value and value > qs_dic["yMin"]:
target_l.append("constantLine(%s)" % value)
color_l.append(DANGEROUS_COLOR)
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(cbv.BasicDetailView):
model = models.Patient
def get(self, request, *args, **kwargs):
subject = str(self.get_object().id)
until = get_int_parameter(request, 'until', None)
until_dt = datetime.now()
if until:
until_dt = datetime.fromtimestamp(until)
values = list()
mapping = {v: k for k, v in settings.SENSOR_MAPPING.items()}
def process(metric, values, lookup_range=60):
backend = whisper_backend.WhisperBackend(subject, metric)
values.append(backend.get_last_value(until=until, lookup_range=lookup_range))
level = ""
if medibot_models.Episode.objects.filter(patient=self.get_object(), level='CRITICAL', opened=True, metric=mapping[metric], start__lte=until_dt).exists() or medibot_models.Episode.objects.filter(patient=self.get_object(), level='CRITICAL', opened=False, metric=mapping[metric], start__lte=until_dt, end__gte=until_dt).exists():
level = "critical"
elif medibot_models.Episode.objects.filter(patient=self.get_object(), level='DANGEROUS', opened=True, metric=mapping[metric], start__lte=until_dt).exists() or medibot_models.Episode.objects.filter(patient=self.get_object(), level='DANGEROUS', opened=False, metric=mapping[metric], start__lte=until_dt, end__gte=until_dt).exists():
level = "dangerous"
values.append(level)
metric = settings.WHISPER_HEARTRATE_METRIC
process(metric, values)
metric = settings.WHISPER_TEMPERATURE_METRIC
process(metric, values, lookup_range=300)
return HttpResponse(json.dumps(values), content_type='application/json')
class MeansView(cbv.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(cbv.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='#B0B0B0', 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(cbv.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 PatientEpisodesOpened(cbv.BasicDetailView):
model = models.Patient
def get(self, request, *args, **kwargs):
values = [(episode.seq_id, episode.start.isoformat(),
episode.end.isoformat(), json.loads(episode.definition),
episode.duration, episode.start.date().isoformat(),
episode.checked, episode.id)
for episode in medibot_models.Episode.objects.
filter(patient=self.get_object(),
opened=True).order_by('-start')]
return HttpResponse(json.dumps(values),
content_type='application/json')
class PatientEpisodesClosed(cbv.BasicDetailView):
model = models.Patient
def get(self, request, *args, **kwargs):
after = get_isodate_parameter(request, 'after',
datetime.fromtimestamp(0))
values = [(episode.seq_id, episode.start.isoformat(),
episode.end.isoformat(), json.loads(episode.definition),
episode.duration, episode.end.date().isoformat(),
episode.checked, episode.id)
for episode in medibot_models.Episode.objects.
filter(patient=self.get_object(),
opened=False, end__gt=after).order_by('-end')]
return HttpResponse(json.dumps(values),
content_type='application/json')
class PatientNextEpisode(cbv.BasicDetailView):
model = models.Patient
def get(self, request, *args, **kwargs):
after = get_int_parameter(request, 'after', None)
after_dt = datetime.fromtimestamp(after)
nexts = medibot_models.Episode.objects.filter(patient=self.get_object(), start__gt=after_dt).order_by('start')
next = []
if nexts:
episode = nexts[0]
next = [episode.id, episode.seq_id,
json.loads(episode.definition), episode.checked,
episode.start.isoformat()]
if episode.end:
next.append(episode.end.isoformat())
return HttpResponse(json.dumps(next),
content_type='application/json')
class PatientPreviousEpisode(cbv.BasicDetailView):
model = models.Patient
def get(self, request, *args, **kwargs):
before = get_int_parameter(request, 'before', None)
before_dt = datetime.fromtimestamp(before)
nexts = medibot_models.Episode.objects.filter(patient=self.get_object(), start__lt=before_dt).order_by('-start')
next = []
if nexts:
episode = nexts[0]
next = [episode.id, episode.seq_id,
json.loads(episode.definition), episode.checked,
episode.start.isoformat()]
if episode.end:
next.append(episode.end.isoformat())
return HttpResponse(json.dumps(next),
content_type='application/json')
class PatientEpisodesStatsView(cbv.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', checked=False)
criticals_count = criticals.count()
criticals_active = criticals.filter(opened=True).exists()
dangerous = medibot_models.Episode.objects.filter(patient=self.get_object(), level='DANGEROUS', checked=False)
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')
class PatientsEpisodesOpened(cbv.BasicView):
def get(self, request, *args, **kwargs):
values = [(episode.seq_id, episode.start.isoformat(),
episode.end.isoformat(), json.loads(episode.definition),
episode.duration, episode.start.date().isoformat(),
episode.checked, episode.id,
episode.patient.id, episode.patient.display_name,
episode.patient.monitoring_place)
for episode in medibot_models.Episode.objects.
filter(opened=True, checked=False).order_by('-start')]
return HttpResponse(json.dumps(values),
content_type='application/json')
class PatientsEpisodesClosed(cbv.BasicView):
def get(self, request, *args, **kwargs):
after = get_isodate_parameter(request, 'after',
datetime.fromtimestamp(0))
values = [(episode.seq_id, episode.start.isoformat(),
episode.end.isoformat(), json.loads(episode.definition),
episode.duration, episode.end.date().isoformat(),
episode.checked, episode.id,
episode.patient.id, episode.patient.display_name,
episode.patient.monitoring_place)
for episode in medibot_models.Episode.objects.
filter(opened=False, checked=False, end__gt=after).order_by('-end')]
return HttpResponse(json.dumps(values),
content_type='application/json')
class PatientsEpisodesStatsView(cbv.BasicView):
def get(self, request, *args, **kwargs):
criticals = medibot_models.Episode.objects.filter(level='CRITICAL', checked=False)
criticals_count = criticals.count()
criticals_active = criticals.filter(opened=True).exists()
dangerous = medibot_models.Episode.objects.filter(level='DANGEROUS', checked=False)
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')
class PatientsEpisodesNotChecked(cbv.BasicView):
def get(self, request, *args, **kwargs):
after = get_isodate_parameter(request, 'after',
datetime.fromtimestamp(0))
values = [(episode.seq_id, episode.start.isoformat(),
episode.end.isoformat(), json.loads(episode.definition),
episode.duration, episode.end.date().isoformat(),
episode.checked, episode.id,
episode.patient.id, episode.patient.display_name,
episode.patient.monitoring_place)
for episode in medibot_models.Episode.objects.
filter(checked=False).order_by('-end')]
return HttpResponse(json.dumps(values),
content_type='application/json')