Upload 2 files
Browse files- app.py +229 -0
- requirements.txt +8 -0
app.py
ADDED
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import plotly.graph_objects as go
|
4 |
+
from fpdf import FPDF
|
5 |
+
import tempfile
|
6 |
+
import kaleido
|
7 |
+
|
8 |
+
# Set page configuration
|
9 |
+
st.set_page_config(
|
10 |
+
page_title="Building Acoustics Analysis Tool",
|
11 |
+
page_icon="🏫",
|
12 |
+
initial_sidebar_state="expanded" # Set sidebar to be expanded by default
|
13 |
+
)
|
14 |
+
|
15 |
+
# Title and instructional information
|
16 |
+
st.title("🔊 Building Acoustics Analysis (RT60)")
|
17 |
+
|
18 |
+
# Helper functions
|
19 |
+
def calculate_absorption_area(volume, reverberation_time):
|
20 |
+
if reverberation_time == 0:
|
21 |
+
return float('inf')
|
22 |
+
return 0.161 * volume / reverberation_time
|
23 |
+
|
24 |
+
def generate_pdf(volume, current_rt, existing_materials, min_rt, max_rt, desired_rt, new_materials, fig1, fig2):
|
25 |
+
pdf = FPDF()
|
26 |
+
pdf.add_page()
|
27 |
+
pdf.set_font("Arial", size=12)
|
28 |
+
pdf.cell(200, 10, txt="Room Acoustics Analysis Report", ln=True, align="C")
|
29 |
+
pdf.cell(200, 10, txt="Room Volume: {:.2f} m³".format(volume), ln=True)
|
30 |
+
pdf.cell(200, 10, txt="Current Reverberation Times (s):", ln=True)
|
31 |
+
for freq, rt in current_rt.items():
|
32 |
+
pdf.cell(200, 10, txt="{} Hz: {:.2f}".format(freq, rt), ln=True)
|
33 |
+
pdf.cell(200, 10, txt="Existing Materials and Absorption Coefficients:", ln=True)
|
34 |
+
for material in existing_materials:
|
35 |
+
pdf.cell(200, 10, txt="Material: {}, Area: {:.2f} m²".format(material['name'], material['area']), ln=True)
|
36 |
+
for freq, coeff in material['coefficients'].items():
|
37 |
+
pdf.cell(200, 10, txt="{} Hz: {:.2f}".format(freq, coeff), ln=True)
|
38 |
+
pdf.cell(200, 10, txt="Standard Reverberation Time Range (s):", ln=True)
|
39 |
+
for freq in min_rt.keys():
|
40 |
+
pdf.cell(200, 10, txt="{} Hz: {:.2f} - {:.2f}".format(freq, min_rt[freq], max_rt[freq]), ln=True)
|
41 |
+
pdf.cell(200, 10, txt="Desired Reverberation Times (s):", ln=True)
|
42 |
+
for freq, rt in desired_rt.items():
|
43 |
+
pdf.cell(200, 10, txt="{} Hz: {:.2f}".format(freq, rt), ln=True)
|
44 |
+
pdf.cell(200, 10, txt="New Materials and Absorption Coefficients:", ln=True)
|
45 |
+
for material in new_materials:
|
46 |
+
pdf.cell(200, 10, txt="Material: {}, Area: {:.2f} m²".format(material['name'], material['area']), ln=True)
|
47 |
+
for freq, coeff in material['coefficients'].items():
|
48 |
+
pdf.cell(200, 10, txt="{} Hz: {:.2f}".format(freq, coeff), ln=True)
|
49 |
+
# Save the figures as temporary files and add to the PDF
|
50 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile1:
|
51 |
+
fig1.write_image(tmpfile1.name, engine='kaleido')
|
52 |
+
pdf.image(tmpfile1.name, x=10, y=None, w=180)
|
53 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile2:
|
54 |
+
fig2.write_image(tmpfile2.name, engine='kaleido')
|
55 |
+
pdf.image(tmpfile2.name, x=10, y=None, w=180)
|
56 |
+
return pdf.output(dest='S').encode('latin1')
|
57 |
+
|
58 |
+
# Layout customizations
|
59 |
+
hide_menu_style = """
|
60 |
+
<style>
|
61 |
+
#MainMenu {visibility: hidden;}
|
62 |
+
footer {visibility: hidden;}
|
63 |
+
</style>
|
64 |
+
"""
|
65 |
+
st.markdown(hide_menu_style, unsafe_allow_html=True)
|
66 |
+
|
67 |
+
# Sidebar with tabs
|
68 |
+
with st.sidebar:
|
69 |
+
st.header("Tabs")
|
70 |
+
tab = st.radio("Choose a tab", ["Instructions", "Initial Data Entry", "Initial RT60 Compliance Check",
|
71 |
+
"Desired RT60", "Acoustic Treatment", "Final RT60 Compliance Check"])
|
72 |
+
|
73 |
+
if tab == "Instructions":
|
74 |
+
st.write("""
|
75 |
+
### Instructions for formatting input data
|
76 |
+
#### Current reverberation times
|
77 |
+
**File must be:** `current_reverberation_times.csv` or `current_reverberation_times.xlsx`\n
|
78 |
+
**Data format in file:**
|
79 |
+
- The first row should contain the frequency values as column headings.
|
80 |
+
- The second row should contain the reverberation times corresponding to each frequency.\n
|
81 |
+
**Example:**
|
82 |
+
```
|
83 |
+
125,250,500,1000,2000,4000
|
84 |
+
0.8,0.7,0.6,0.5,0.4,0.3
|
85 |
+
```
|
86 |
+
#### Existing materials absorption coefficients and areas
|
87 |
+
**File must be:** `existing_materials.csv` or `existing_materials.xlsx`\n
|
88 |
+
**Data format in file:**
|
89 |
+
- The first column should contain the material names.
|
90 |
+
- The second column should contain the surface areas of the materials (in square meters).
|
91 |
+
- The subsequent columns should contain absorption coefficients for different frequencies.\n
|
92 |
+
**Example:**
|
93 |
+
```
|
94 |
+
Material Name,Area (m²),125,250,500,1000,2000,4000
|
95 |
+
Carpet,50,0.1,0.15,0.2,0.3,0.4,0.5
|
96 |
+
Curtain,30,0.2,0.25,0.3,0.35,0.45,0.55
|
97 |
+
```
|
98 |
+
#### New materials absorption coefficients and areas
|
99 |
+
**File Name:** `new_materials.csv` or `new_materials.xlsx`\n
|
100 |
+
**Format:**
|
101 |
+
- The first column should contain the material names.
|
102 |
+
- The second column should contain the surface areas of the materials (in square meters).
|
103 |
+
- The subsequent columns should contain absorption coefficients for different frequencies.\n
|
104 |
+
**Example:**
|
105 |
+
```
|
106 |
+
Material Name,Area (m²),125,250,500,1000,2000,4000
|
107 |
+
Acoustic Panel,40,0.3,0.35,0.4,0.45,0.5,0.6
|
108 |
+
Foam,20,0.25,0.3,0.35,0.4,0.45,0.55
|
109 |
+
```
|
110 |
+
### General tips
|
111 |
+
- Ensure the files are saved in CSV or Excel format.
|
112 |
+
- The column headings must be spelled correctly and consistently.
|
113 |
+
- Avoid including any extra rows or columns outside of the specified format.
|
114 |
+
- Check for and correct any typographical errors in the column headings.
|
115 |
+
### Uploading the files
|
116 |
+
- **Current Reverberation Times:** Upload the file in the "Upload Current Reverberation Times" section on Initial Data Entry tab.
|
117 |
+
- **Existing Materials Data:** Upload the file in the "Upload Existing Materials Data" section on Initial Data Entry tab.
|
118 |
+
- **New Materials Data:** Upload the file in the "Upload New Materials Data" section on the Acoustic Treatment tab.
|
119 |
+
""")
|
120 |
+
|
121 |
+
elif tab == "Initial Data Entry":
|
122 |
+
st.write('''The primary objective here is to provide all the initial input data needed to start RT60 analysis.
|
123 |
+
See the 'Instructions' on the left for formatting information''')
|
124 |
+
st.write("#### Upload current reverberation times")
|
125 |
+
current_rt_file = st.file_uploader("Upload CSV or Excel file for current reverberation times", type=['csv', 'xlsx'],
|
126 |
+
help='Go to Instructions on the sidebar for formatting information.')
|
127 |
+
|
128 |
+
current_rt = {}
|
129 |
+
frequencies = []
|
130 |
+
if current_rt_file:
|
131 |
+
try:
|
132 |
+
if current_rt_file.name.endswith('.csv'):
|
133 |
+
df_current_rt = pd.read_csv(current_rt_file, header=None)
|
134 |
+
else:
|
135 |
+
df_current_rt = pd.read_excel(current_rt_file, header=None)
|
136 |
+
frequencies = df_current_rt.iloc[0].tolist()
|
137 |
+
current_rt = dict(zip(frequencies, df_current_rt.iloc[1].tolist()))
|
138 |
+
st.write("Current reverberation times:")
|
139 |
+
st.dataframe(df_current_rt, use_container_width=True)
|
140 |
+
except Exception as e:
|
141 |
+
st.error(f"Error reading current reverberation times file: {e}")
|
142 |
+
|
143 |
+
st.write("#### Upload existing materials data")
|
144 |
+
existing_materials_file = st.file_uploader("Upload CSV or Excel file for existing materials", type=['csv', 'xlsx'],
|
145 |
+
help='Go to Instructions on the sidebar for formatting information.')
|
146 |
+
|
147 |
+
existing_materials = []
|
148 |
+
if existing_materials_file:
|
149 |
+
try:
|
150 |
+
if existing_materials_file.name.endswith('.csv'):
|
151 |
+
df_existing_materials = pd.read_csv(existing_materials_file)
|
152 |
+
else:
|
153 |
+
df_existing_materials = pd.read_excel(existing_materials_file)
|
154 |
+
st.write("Existing Materials Data:")
|
155 |
+
st.dataframe(df_existing_materials, use_container_width=True)
|
156 |
+
for _, row in df_existing_materials.iterrows():
|
157 |
+
material = {
|
158 |
+
'name': row[0],
|
159 |
+
'area': row[1],
|
160 |
+
'coefficients': dict(zip(frequencies, row[2:].tolist()))
|
161 |
+
}
|
162 |
+
existing_materials.append(material)
|
163 |
+
except Exception as e:
|
164 |
+
st.error(f"Error reading existing materials file: {e}")
|
165 |
+
|
166 |
+
elif tab == "Initial RT60 Compliance Check":
|
167 |
+
st.write("Performing initial RT60 compliance check...")
|
168 |
+
if not current_rt:
|
169 |
+
st.warning("Please upload current reverberation times in the Initial Data Entry tab.")
|
170 |
+
else:
|
171 |
+
min_rt = {freq: 0.2 for freq in frequencies} # Placeholder values
|
172 |
+
max_rt = {freq: 0.6 for freq in frequencies} # Placeholder values
|
173 |
+
out_of_bounds = {freq: rt for freq, rt in current_rt.items() if rt < min_rt.get(freq, 0) or rt > max_rt.get(freq, 0)}
|
174 |
+
st.write("Initial RT60 Compliance Check Results:")
|
175 |
+
if out_of_bounds:
|
176 |
+
st.warning("Some frequencies are out of the standard RT60 range:")
|
177 |
+
st.write(out_of_bounds)
|
178 |
+
else:
|
179 |
+
st.success("All frequencies are within the standard RT60 range.")
|
180 |
+
|
181 |
+
elif tab == "Desired RT60":
|
182 |
+
st.write("Specify the desired reverberation times for different frequencies.")
|
183 |
+
desired_rt = {}
|
184 |
+
for freq in frequencies:
|
185 |
+
rt = st.number_input(f"Desired RT60 at {freq} Hz", value=0.5, format="%.2f")
|
186 |
+
desired_rt[freq] = rt
|
187 |
+
st.write("Desired RT60 Values:")
|
188 |
+
st.write(desired_rt)
|
189 |
+
|
190 |
+
elif tab == "Acoustic Treatment":
|
191 |
+
st.write("Upload new materials data for acoustic treatment.")
|
192 |
+
new_materials_file = st.file_uploader("Upload CSV or Excel file for new materials", type=['csv', 'xlsx'],
|
193 |
+
help='Go to Instructions on the sidebar for formatting information.')
|
194 |
+
|
195 |
+
new_materials = []
|
196 |
+
if new_materials_file:
|
197 |
+
try:
|
198 |
+
if new_materials_file.name.endswith('.csv'):
|
199 |
+
df_new_materials = pd.read_csv(new_materials_file)
|
200 |
+
else:
|
201 |
+
df_new_materials = pd.read_excel(new_materials_file)
|
202 |
+
st.write("New Materials Data:")
|
203 |
+
st.dataframe(df_new_materials, use_container_width=True)
|
204 |
+
for _, row in df_new_materials.iterrows():
|
205 |
+
material = {
|
206 |
+
'name': row[0],
|
207 |
+
'area': row[1],
|
208 |
+
'coefficients': dict(zip(frequencies, row[2:].tolist()))
|
209 |
+
}
|
210 |
+
new_materials.append(material)
|
211 |
+
except Exception as e:
|
212 |
+
st.error(f"Error reading new materials file: {e}")
|
213 |
+
|
214 |
+
elif tab == "Final RT60 Compliance Check":
|
215 |
+
st.write("Perform final RT60 compliance check after applying acoustic treatment.")
|
216 |
+
if not desired_rt:
|
217 |
+
st.warning("Please specify desired RT60 values in the Desired RT60 tab.")
|
218 |
+
elif not new_materials:
|
219 |
+
st.warning("Please upload new materials data in the Acoustic Treatment tab.")
|
220 |
+
else:
|
221 |
+
# Placeholder calculations
|
222 |
+
final_rt = {freq: 0.5 for freq in frequencies} # Replace with actual calculation
|
223 |
+
st.write("Final RT60 Compliance Check Results:")
|
224 |
+
out_of_bounds = {freq: rt for freq, rt in final_rt.items() if rt < 0.2 or rt > 0.6}
|
225 |
+
if out_of_bounds:
|
226 |
+
st.warning("Some frequencies are out of the desired RT60 range:")
|
227 |
+
st.write(out_of_bounds)
|
228 |
+
else:
|
229 |
+
st.success("All frequencies are within the desired RT60 range.")
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit==1.24.0
|
2 |
+
pandas==2.0.3
|
3 |
+
plotly==5.15.0
|
4 |
+
fpdf==1.7.2
|
5 |
+
matplotlib==3.7.1
|
6 |
+
altair<5
|
7 |
+
openpyxl==3.1.2
|
8 |
+
kaleido==0.2.1
|