Added 'save as image' feature

This commit is contained in:
ayxan 2022-11-25 19:30:48 +04:00
parent 8863cbb070
commit 31dca7953c
5 changed files with 111 additions and 25 deletions

View File

@ -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,

View File

@ -1 +1,2 @@
from .local_data_access import LocalDataAccess
from .local_data_access import LocalDataAccess
from .upload_data_access import UploadDataAccess

View File

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

View File

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

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