mirror of
https://github.com/aykhans/HuaweiHealthDataVisualization.git
synced 2025-04-08 11:54:01 +00:00
Added 'save as image' feature
This commit is contained in:
parent
8863cbb070
commit
31dca7953c
@ -8,24 +8,36 @@ if str(main_path) not in path:
|
||||
|
||||
import plotly.express as px
|
||||
import streamlit as st
|
||||
from data_access import LocalDataAccess
|
||||
from data_access import (LocalDataAccess,
|
||||
UploadDataAccess)
|
||||
|
||||
|
||||
st.set_page_config(page_title = 'Health Data Visualization',
|
||||
page_icon = ':bar_chart:',
|
||||
layout = 'wide')
|
||||
|
||||
local_data = LocalDataAccess('data')
|
||||
_ = local_data.get_data('huawei_health_data.json')
|
||||
heart_rate = local_data.get_heart_rate()
|
||||
upload_data = st.file_uploader('Choose a Huawei Health data',
|
||||
type=['json'])
|
||||
|
||||
if upload_data is None:
|
||||
st.sidebar.caption('Local test data is being used')
|
||||
data_access = LocalDataAccess('data')
|
||||
data_access.data = 'huawei_health_data.json'
|
||||
|
||||
else:
|
||||
st.sidebar.caption('Uploaded data is being used')
|
||||
data_access = UploadDataAccess()
|
||||
data_access.data = upload_data
|
||||
|
||||
heart_rate = data_access.heart_rate
|
||||
|
||||
if st.sidebar.checkbox(f'All Data ({len(heart_rate)})', False):
|
||||
if st.sidebar.checkbox('Average of Days', False):
|
||||
x, y = local_data.get_average_heart_rate_for_days_as_axis()
|
||||
x, y = data_access.get_average_heart_rate_for_days_as_axis()
|
||||
labels = {'x': 'Date of The Day', 'y': 'Average Heart Rate'}
|
||||
|
||||
else:
|
||||
x, y = local_data.get_heart_rate_for_all_days_as_axis()
|
||||
x, y = data_access.get_heart_rate_for_all_days_as_axis()
|
||||
labels = {'x': 'Date and Time', 'y': 'Heart Rate'}
|
||||
|
||||
else:
|
||||
@ -36,7 +48,7 @@ else:
|
||||
max_value = heart_rate[-1]['time']
|
||||
).strftime("%d-%m-%Y")
|
||||
|
||||
x, y = local_data.get_heart_rate_for_one_day(day)
|
||||
x, y = data_access.get_heart_rate_for_one_day(day)
|
||||
labels = {'x': 'Date and Time', 'y': 'Heart Rate'}
|
||||
|
||||
st.sidebar.header('Split Data:')
|
||||
@ -48,7 +60,7 @@ average_number = st.sidebar.number_input(
|
||||
)
|
||||
|
||||
if average_number > 1:
|
||||
x, y = local_data.get_averages_of_heart_rates(y, average_number)
|
||||
x, y = data_access.get_averages_of_heart_rates(y, average_number)
|
||||
labels = {'x': 'Number of Heart Rate', 'y': 'Heart Rate'}
|
||||
|
||||
chart_type = st.sidebar.selectbox(
|
||||
@ -56,6 +68,12 @@ chart_type = st.sidebar.selectbox(
|
||||
('Line', 'Scatter', 'Bar')
|
||||
)
|
||||
|
||||
st.sidebar.header('Save as image')
|
||||
st.sidebar.caption('white pixels is min rate, black pixels is max rate, green is empty pixels')
|
||||
st.sidebar.download_button('Download',
|
||||
data=data_access.get_heart_rate_as_img(y),
|
||||
file_name='heart-rate.png')
|
||||
|
||||
st.plotly_chart(
|
||||
{'Line': px.line, 'Scatter': px.scatter, 'Bar': px.bar}[chart_type](
|
||||
x = x, y = y,
|
||||
|
@ -1 +1,2 @@
|
||||
from .local_data_access import LocalDataAccess
|
||||
from .local_data_access import LocalDataAccess
|
||||
from .upload_data_access import UploadDataAccess
|
@ -1,4 +1,8 @@
|
||||
import pandas as pd
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
import numpy as np
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import (datetime,
|
||||
timedelta)
|
||||
from typing import (List,
|
||||
@ -6,13 +10,22 @@ from typing import (List,
|
||||
Union)
|
||||
|
||||
|
||||
class DataOperations:
|
||||
heart_rate = None
|
||||
class DataOperations(ABC):
|
||||
_heart_rate: Union[List, None] = None
|
||||
_data: Union[List, None] = None
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def data(self): ...
|
||||
|
||||
@data.setter
|
||||
@abstractmethod
|
||||
def data(self): ...
|
||||
|
||||
def is_data_none_wrapper(func):
|
||||
def __is_data_none(self, *args, **kwargs) -> Union[None, NameError]:
|
||||
if self.data is None:
|
||||
raise NameError(f'data not found. You must call the get_data function before calling the {func.__name__} function')
|
||||
raise NameError(f'data not found. You must set the data before calling the {func.__name__} function')
|
||||
return func(self, *args, **kwargs)
|
||||
return __is_data_none
|
||||
|
||||
@ -23,13 +36,14 @@ class DataOperations:
|
||||
return func(self, *args, **kwargs)
|
||||
return __is_heart_rate_none
|
||||
|
||||
@property
|
||||
@is_data_none_wrapper
|
||||
def get_heart_rate(self) -> List:
|
||||
def heart_rate(self) -> List:
|
||||
# sourcery skip: inline-immediately-returned-variable, list-comprehension
|
||||
self.heart_rate = []
|
||||
self._heart_rate = []
|
||||
for d in self.data:
|
||||
if d['type'] == 7:
|
||||
self.heart_rate.append(
|
||||
self._heart_rate.append(
|
||||
{
|
||||
'rate': float(d['samplePoints'][0]['value']),
|
||||
'time': (datetime.fromtimestamp(
|
||||
@ -40,11 +54,11 @@ class DataOperations:
|
||||
)
|
||||
}
|
||||
)
|
||||
return self.heart_rate
|
||||
return self._heart_rate
|
||||
|
||||
@is_heart_rate_none_wrapper
|
||||
def get_heart_rate_for_all_days_as_axis(self) -> Tuple:
|
||||
heart_rate = pd.DataFrame(self.heart_rate)
|
||||
heart_rate = pd.DataFrame(self._heart_rate)
|
||||
return (
|
||||
list (
|
||||
map(
|
||||
@ -60,7 +74,7 @@ class DataOperations:
|
||||
heart_rate_grouped = pd.DataFrame(
|
||||
list(
|
||||
map(
|
||||
lambda t: {'rate': t['rate'], 'date': t['time'].strftime("%d-%m-%Y")}, self.heart_rate
|
||||
lambda t: {'rate': t['rate'], 'date': t['time'].strftime("%d-%m-%Y")}, self._heart_rate
|
||||
)
|
||||
)
|
||||
).groupby('date', sort=False).mean()['rate']
|
||||
@ -72,7 +86,7 @@ class DataOperations:
|
||||
heart_rate = pd.DataFrame(
|
||||
list(
|
||||
filter(
|
||||
lambda t: t['time'].strftime("%d-%m-%Y") == day, self.heart_rate
|
||||
lambda t: t['time'].strftime("%d-%m-%Y") == day, self._heart_rate
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -95,4 +109,37 @@ class DataOperations:
|
||||
t = rates[i:i + average_number]
|
||||
heart_rate2.append(sum(t) / len(t))
|
||||
|
||||
return (range(len(heart_rate2)), heart_rate2)
|
||||
return (range(len(heart_rate2)), heart_rate2)
|
||||
|
||||
@classmethod
|
||||
def __array_to_bytes_image(cls, img_array: np.array) -> bytes:
|
||||
buf = BytesIO()
|
||||
Image.fromarray(img_array, 'RGB').save(buf, format="PNG")
|
||||
return buf.getvalue()
|
||||
|
||||
@staticmethod
|
||||
def get_heart_rate_as_img(rates: List) -> bytes:
|
||||
rates_len = len(rates)
|
||||
old_max = max(rates)
|
||||
old_min = min(rates)
|
||||
new_max = 255
|
||||
new_min = 0
|
||||
|
||||
old_range = (old_max - old_min)
|
||||
new_range = (new_max - new_min)
|
||||
|
||||
for max_n in range(rates_len):
|
||||
if max_n * max_n > rates_len: break
|
||||
|
||||
img_array = np.full((max_n, max_n, 3), [0, 255, 0], dtype=np.uint8)
|
||||
|
||||
x, y = 0, 0
|
||||
for n in rates:
|
||||
color = (255 - (((n - old_min) * new_range) / old_range) + new_min)
|
||||
img_array[x][y] = [color, color, color]
|
||||
if y < max_n-1: y += 1
|
||||
else:
|
||||
x += 1
|
||||
y = 0
|
||||
|
||||
return DataOperations.__array_to_bytes_image(img_array)
|
@ -1,16 +1,19 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
from typing import List
|
||||
import json
|
||||
from .data_opeartions import DataOperations
|
||||
|
||||
|
||||
class LocalDataAccess(DataOperations):
|
||||
def __init__(self, data_folder_name: str) -> None:
|
||||
self.data = None
|
||||
self.data_dir: Path =\
|
||||
Path(__file__).resolve().parent.parent.parent / data_folder_name
|
||||
|
||||
def get_data(self, file_name: str) -> List:
|
||||
@property
|
||||
def data(self) -> List:
|
||||
return self._data
|
||||
|
||||
@data.setter
|
||||
def data(self, file_name: str):
|
||||
with open(self.data_dir / file_name) as f:
|
||||
self.data: List = json.load(f)
|
||||
return self.data
|
||||
self._data = json.load(f)
|
17
src/data_access/upload_data_access.py
Normal file
17
src/data_access/upload_data_access.py
Normal file
@ -0,0 +1,17 @@
|
||||
import json
|
||||
from typing import List
|
||||
from streamlit.runtime.uploaded_file_manager import UploadedFile
|
||||
from .data_opeartions import DataOperations
|
||||
|
||||
|
||||
class UploadDataAccess(DataOperations):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def data(self) -> List:
|
||||
return self._data
|
||||
|
||||
@data.setter
|
||||
def data(self, uploaded_file: UploadedFile) -> None:
|
||||
self._data = json.load(uploaded_file)
|
Loading…
x
Reference in New Issue
Block a user