Spaces:
Runtime error
Runtime error
andrewlee1807
commited on
Commit
ยท
f2be9d4
1
Parent(s):
2c42c41
Publish Wave app to Hugging Face
Browse files- .dockerignore +6 -0
- Dockerfile +43 -0
- app.py +207 -0
- docker-entrypoint.sh +14 -0
- requirements.txt +3 -0
.dockerignore
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.venv/
|
2 |
+
.git/
|
3 |
+
.gitignore
|
4 |
+
.dockerignore
|
5 |
+
Dockerfile
|
6 |
+
README.md
|
Dockerfile
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Accept Python version during build
|
2 |
+
ARG PYTHON_VERSION="3.8"
|
3 |
+
|
4 |
+
FROM python:${PYTHON_VERSION}-slim
|
5 |
+
|
6 |
+
# Install OS dependencies
|
7 |
+
RUN apt-get update && apt-get -y upgrade \
|
8 |
+
&& export DEBIAN_FRONTEND=noninteractive \
|
9 |
+
&& apt-get -y install --no-install-recommends curl build-essential
|
10 |
+
|
11 |
+
# Create a non-root user
|
12 |
+
RUN useradd --create-home appuser
|
13 |
+
USER appuser
|
14 |
+
WORKDIR /home/appuser/app
|
15 |
+
|
16 |
+
# Install Wave
|
17 |
+
ARG WAVE_VERSION="0.20.0"
|
18 |
+
|
19 |
+
ENV WAVE_HOME="/home/appuser/wave"
|
20 |
+
RUN \
|
21 |
+
mkdir -p "${WAVE_HOME}" && \
|
22 |
+
curl -fsSL https://github.com/h2oai/wave/releases/download/v${WAVE_VERSION}/wave-${WAVE_VERSION}-linux-amd64.tar.gz | tar -C ${WAVE_HOME} -xzv 2>&1
|
23 |
+
ENV WAVE_PATH="${WAVE_HOME}/wave-${WAVE_VERSION}-linux-amd64"
|
24 |
+
|
25 |
+
# Create a virtual environment
|
26 |
+
ENV VIRTUAL_ENV=/home/appuser/venv
|
27 |
+
RUN python3 -m venv $VIRTUAL_ENV
|
28 |
+
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
29 |
+
|
30 |
+
# Install Python dependencies
|
31 |
+
COPY requirements.txt .
|
32 |
+
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
|
33 |
+
|
34 |
+
# Copy app code
|
35 |
+
COPY --chown=appuser:appuser . .
|
36 |
+
|
37 |
+
# Set permissions for the Entrypoint script
|
38 |
+
RUN chmod +x docker-entrypoint.sh
|
39 |
+
|
40 |
+
ARG PYTHON_MODULE
|
41 |
+
ENV PYTHON_MODULE="${PYTHON_MODULE}"
|
42 |
+
|
43 |
+
ENTRYPOINT [ "./docker-entrypoint.sh" ]
|
app.py
ADDED
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Note: Main must be imported even though it is not used.
|
2 |
+
from h2o_wave import Q, ui, app, main, data, on
|
3 |
+
import numpy as np
|
4 |
+
import pandas as pd
|
5 |
+
import random
|
6 |
+
import time
|
7 |
+
import asyncio
|
8 |
+
|
9 |
+
NUMBER_SENSORS = 4
|
10 |
+
LIMIT_LEN_SHOWING = 20
|
11 |
+
REALTIME_UPDATE = 5 # seconds
|
12 |
+
list_data = []
|
13 |
+
line_charts_data = [] # [(sensor_id, [data]), ...]
|
14 |
+
for i in range(NUMBER_SENSORS):
|
15 |
+
line_charts_data.append((i, []))
|
16 |
+
start_year = 1991
|
17 |
+
while True:
|
18 |
+
for i in range(NUMBER_SENSORS):
|
19 |
+
random_value = random.uniform(1, 50) # Generate a random value between 1 and 50
|
20 |
+
value = (str(start_year), random_value)
|
21 |
+
line_charts_data[i][1].append(value)
|
22 |
+
start_year += 1
|
23 |
+
if start_year == 2021:
|
24 |
+
break
|
25 |
+
|
26 |
+
|
27 |
+
async def update_value(q: Q):
|
28 |
+
while True:
|
29 |
+
time.sleep(REALTIME_UPDATE)
|
30 |
+
# TODO: Update realtime values for current stats cards
|
31 |
+
# value_counts = random.randint(0, 100)
|
32 |
+
# percentages = random.random()
|
33 |
+
percentages = np.random.random(NUMBER_SENSORS)
|
34 |
+
value_counts = np.random.randint(0, 100, NUMBER_SENSORS)
|
35 |
+
|
36 |
+
show_multiple_stats_cards(q, value_counts, percentages)
|
37 |
+
await q.page.save()
|
38 |
+
|
39 |
+
|
40 |
+
async def api_get_data():
|
41 |
+
global list_data, line_charts_data, start_year
|
42 |
+
while True:
|
43 |
+
for i in range(NUMBER_SENSORS):
|
44 |
+
random_value = random.uniform(1, 50) # Generate a random value between 1 and 50
|
45 |
+
value = (str(start_year), random_value)
|
46 |
+
line_charts_data[i][1].append(value)
|
47 |
+
start_year += 1
|
48 |
+
await asyncio.sleep(REALTIME_UPDATE)
|
49 |
+
|
50 |
+
|
51 |
+
async def update_charts(q: Q):
|
52 |
+
global line_charts_data
|
53 |
+
while True:
|
54 |
+
time.sleep(REALTIME_UPDATE)
|
55 |
+
for sensor_data in line_charts_data:
|
56 |
+
sensor_id = str(sensor_data[0])
|
57 |
+
data = sensor_data[1]
|
58 |
+
limites_showing_data = LIMIT_LEN_SHOWING if len(data) > LIMIT_LEN_SHOWING else len(data)
|
59 |
+
show_line_graph(q, sensor_id, data[-limites_showing_data:])
|
60 |
+
|
61 |
+
percentages = np.random.random(NUMBER_SENSORS)
|
62 |
+
value_counts = np.random.randint(0, 100, NUMBER_SENSORS)
|
63 |
+
show_multiple_stats_cards(q, value_counts, percentages)
|
64 |
+
await q.page.save()
|
65 |
+
|
66 |
+
|
67 |
+
def on_startup():
|
68 |
+
print('App started!')
|
69 |
+
|
70 |
+
|
71 |
+
def on_shutdown():
|
72 |
+
print('App stopped!')
|
73 |
+
|
74 |
+
|
75 |
+
task_data = asyncio.create_task(api_get_data())
|
76 |
+
|
77 |
+
|
78 |
+
@app('/sensor', on_startup=on_startup, on_shutdown=on_shutdown, mode='broadcast')
|
79 |
+
async def sever(q: Q):
|
80 |
+
apply_layout(q)
|
81 |
+
show_homepage(q)
|
82 |
+
# show_histograms(q, df, index=[1, 7, 8])
|
83 |
+
|
84 |
+
# await update_value(q)
|
85 |
+
await update_charts(q)
|
86 |
+
# await q.page.save()
|
87 |
+
|
88 |
+
|
89 |
+
def show_homepage(q: Q):
|
90 |
+
q.page['header'] = ui.header_card(
|
91 |
+
box=ui.box('header', width='100%', height='86px'),
|
92 |
+
icon='Video360Generic',
|
93 |
+
icon_color='White',
|
94 |
+
title='AgroTrack Application',
|
95 |
+
subtitle='๋ด์๋์
๊ธฐ์ ์ผํฐ')
|
96 |
+
|
97 |
+
q.page['footer'] = ui.footer_card(
|
98 |
+
box='footer',
|
99 |
+
caption='Copyright (C) [AISeed](https://sprout-fibre-be6.notion.site/AISEED-40e62bb70c024a2c974c0a7051aca86f?pvs=4) Designed by [Blackhole](https://github.com/andrewlee1807/)')
|
100 |
+
|
101 |
+
|
102 |
+
def apply_layout(q: Q):
|
103 |
+
q.page['meta'] = ui.meta_card(box='', theme='oceanic', title="SmartFarm's Sensor Dashboard",
|
104 |
+
layouts=[
|
105 |
+
ui.layout(
|
106 |
+
breakpoint='xl',
|
107 |
+
width='1600px',
|
108 |
+
zones=[
|
109 |
+
ui.zone('header'),
|
110 |
+
ui.zone('body', direction=ui.ZoneDirection.ROW, zones=[
|
111 |
+
ui.zone('content', direction=ui.ZoneDirection.COLUMN, zones=[
|
112 |
+
ui.zone('section1'),
|
113 |
+
ui.zone('top', size='300px', direction=ui.ZoneDirection.ROW),
|
114 |
+
ui.zone('section2'),
|
115 |
+
ui.zone('bottom', direction=ui.ZoneDirection.ROW),
|
116 |
+
ui.zone('chart1', direction=ui.ZoneDirection.ROW),
|
117 |
+
]),
|
118 |
+
]),
|
119 |
+
ui.zone('footer')
|
120 |
+
]),
|
121 |
+
])
|
122 |
+
|
123 |
+
|
124 |
+
## Making stats cards
|
125 |
+
def make_stats_card_data(q: Q, column):
|
126 |
+
value_counts = column.value_counts()
|
127 |
+
total = value_counts.sum()
|
128 |
+
return value_counts[1], total
|
129 |
+
|
130 |
+
|
131 |
+
# Randomly generate data for stats cards: percentage and value (randomly generated)
|
132 |
+
|
133 |
+
|
134 |
+
def show_stats_card(q: Q, name, value_count, percentage):
|
135 |
+
q.page['stat_' + name] = ui.tall_gauge_stat_card(
|
136 |
+
box=ui.box('top', width='12.5%'),
|
137 |
+
title='Number of houses with ' + name,
|
138 |
+
value='={{intl one}}',
|
139 |
+
aux_value='={{intl perc style="percent" minimum_fraction_digits=2 maximum_fraction_digits=2 }}',
|
140 |
+
progress=percentage,
|
141 |
+
data=dict(one=int(value_count), perc=float(percentage)),
|
142 |
+
plot_color='$red'
|
143 |
+
)
|
144 |
+
|
145 |
+
|
146 |
+
def show_multiple_stats_cards(q: Q, value_counts, percentages):
|
147 |
+
for i, j in enumerate(zip(value_counts, percentages)):
|
148 |
+
name = "feature_" + str(i)
|
149 |
+
value_count, percentage = j
|
150 |
+
show_stats_card(q, name, value_count, percentage)
|
151 |
+
|
152 |
+
|
153 |
+
# Making histograms
|
154 |
+
def make_histogram_data(values):
|
155 |
+
count, division = np.histogram(values)
|
156 |
+
return [(x, y) for x, y in zip(division.tolist(), count.tolist())]
|
157 |
+
|
158 |
+
|
159 |
+
def show_histogram(q: Q, values, variable_name, index):
|
160 |
+
q.page['feat' + str(index)] = ui.tall_series_stat_card(
|
161 |
+
box=ui.box('bottom', width='25%'),
|
162 |
+
title='Linear tall series' + variable_name,
|
163 |
+
value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}',
|
164 |
+
aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}',
|
165 |
+
data=dict(qux=800, quux=80 / 100),
|
166 |
+
plot_type='area',
|
167 |
+
plot_category='foo',
|
168 |
+
plot_value='qux',
|
169 |
+
plot_color='$yellow',
|
170 |
+
plot_data=data('foo qux', 3, rows=[[90, 0.9], [50, 0.5], [80, 0.8]]),
|
171 |
+
plot_zero_value=0,
|
172 |
+
plot_curve='smooth',
|
173 |
+
)
|
174 |
+
|
175 |
+
|
176 |
+
def show_line_graph(q: Q, name, data_rows):
|
177 |
+
q.page['section2'] = ui.section_card(box='section2',
|
178 |
+
title='Plot of the data',
|
179 |
+
subtitle='')
|
180 |
+
position = 'bottom' if int(name) < 2 else 'chart1'
|
181 |
+
q.page[name] = ui.plot_card(
|
182 |
+
box=ui.box(position, width='50%'),
|
183 |
+
title='Sensor ' + name,
|
184 |
+
animate=True,
|
185 |
+
data=data('time value', LIMIT_LEN_SHOWING, rows=data_rows),
|
186 |
+
events=['select_marks'],
|
187 |
+
plot=ui.plot([
|
188 |
+
ui.mark(type='line',
|
189 |
+
x_scale='time',
|
190 |
+
x='=time',
|
191 |
+
y='=value',
|
192 |
+
y_min=0, interactive=True),
|
193 |
+
ui.mark(type='point',
|
194 |
+
x='=time',
|
195 |
+
# x='={{intl year type="time" month="numeric" day="numeric" hour="numeric" minute="numeric" hourCycle="h24" }}',
|
196 |
+
y='=value',
|
197 |
+
size=4,
|
198 |
+
fill_color='#FFFF28',
|
199 |
+
x_title='Time', y_title='Value',
|
200 |
+
interactive=True),
|
201 |
+
]),
|
202 |
+
)
|
203 |
+
|
204 |
+
|
205 |
+
@on('select_marks')
|
206 |
+
async def on_marks_selected(q: Q):
|
207 |
+
print("Click")
|
docker-entrypoint.sh
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env bash
|
2 |
+
|
3 |
+
set -e
|
4 |
+
|
5 |
+
export H2O_WAVE_ADDRESS="http://127.0.0.1:${PORT}"
|
6 |
+
|
7 |
+
printf '\n$ ( cd %s && ./waved -listen ":%s" & )\n\n' "${WAVE_PATH}" "${PORT}"
|
8 |
+
(cd "${WAVE_PATH}" && ./waved -listen ":${PORT}" &)
|
9 |
+
|
10 |
+
sleep 3
|
11 |
+
|
12 |
+
printf '\n$ wave run --no-reload --no-autostart %s\n\n' "$PYTHON_MODULE"
|
13 |
+
|
14 |
+
exec wave run --no-reload --no-autostart "$PYTHON_MODULE"
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
h2o-wave
|
2 |
+
numpy
|
3 |
+
pandas
|