Raykarr commited on
Commit
0e679c9
·
verified ·
1 Parent(s): a56241c

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +243 -0
app.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ # --------------------------------------
6
+ # Utility functions
7
+ # --------------------------------------
8
+
9
+ def generate_random_pile(num_points=50, max_height=10.0, noise=0.5):
10
+ """
11
+ Generates a random 'pile shape' with a certain maximum height.
12
+ We'll shape it as a bell-curve-like mound plus random noise.
13
+
14
+ Args:
15
+ num_points (int): Number of discrete x-points to model the silo cross-section.
16
+ max_height (float): Approximate max possible height.
17
+ noise (float): Random fluctuations around the main shape.
18
+
19
+ Returns:
20
+ x_array (np.array): X-coordinates from 0..1 (normalized).
21
+ height_array (np.array): Random mound shape values at each x.
22
+ """
23
+ x_array = np.linspace(0, 1, num_points)
24
+ center = 0.5
25
+ sigma = 0.15 # controls how wide the mound is
26
+ base_curve = max_height * np.exp(-0.5 * ((x_array - center) / sigma)**2)
27
+
28
+ random_variation = noise * np.random.randn(num_points)
29
+ height_array = np.clip(base_curve + random_variation, 0, None)
30
+ return x_array, height_array
31
+
32
+ def simulate_sensor_readings(x_array, height_array, sensor_type="LiDAR", num_sensors=5):
33
+ """
34
+ Simulates sensor readings from the height array.
35
+ We randomly pick a few positions along x_array,
36
+ then record the height + some noise depending on sensor_type.
37
+
38
+ Args:
39
+ x_array (np.array): X-coordinates for the silo cross-section
40
+ height_array (np.array): True pile height at each x
41
+ sensor_type (str): "LiDAR" or "Ultrasonic" - determines noise level
42
+ num_sensors (int): number of sensors/points to read
43
+
44
+ Returns:
45
+ sensor_positions (np.array): Chosen positions
46
+ sensor_heights (np.array): Measured heights (with noise)
47
+ """
48
+ sensor_positions = np.random.choice(x_array, size=num_sensors, replace=False)
49
+ sensor_positions = np.sort(sensor_positions)
50
+
51
+ sensor_heights = []
52
+ for pos in sensor_positions:
53
+ idx = np.argmin(np.abs(x_array - pos))
54
+ true_height = height_array[idx]
55
+
56
+ if sensor_type.lower() == "lidar":
57
+ noise_std = 0.1
58
+ else:
59
+ noise_std = 0.2
60
+
61
+ measured = true_height + np.random.randn() * noise_std
62
+ sensor_heights.append(max(measured, 0.0))
63
+
64
+ return sensor_positions, np.array(sensor_heights)
65
+
66
+ def estimate_peak_height(sensor_positions, sensor_heights):
67
+ """
68
+ Simple approach: take the max sensor reading as approximate peak.
69
+ """
70
+ return np.max(sensor_heights)
71
+
72
+ def estimate_volume(x_array, height_array, silo_width=5.0, silo_depth=5.0):
73
+ """
74
+ Approximates volume of the pile using trapezoidal integration
75
+ across the 2D cross-section, then multiplying by depth.
76
+
77
+ Args:
78
+ x_array: normalized x from 0..1
79
+ height_array: height at each x
80
+ silo_width (float): actual width of silo in meters
81
+ silo_depth (float): depth into the page in meters
82
+
83
+ Returns:
84
+ volume (float): approximate volume in cubic meters
85
+ """
86
+ x_meters = x_array * silo_width
87
+ area_2d = np.trapz(height_array, x_meters) # m^2
88
+ volume = area_2d * silo_depth # m^3
89
+ return volume
90
+
91
+ def plot_pile_shape(x_array, height_array, sensor_positions=None, sensor_heights=None):
92
+ """
93
+ Creates and returns a matplotlib figure showing
94
+ the random pile shape and optionally sensor readings.
95
+ """
96
+ fig, ax = plt.subplots(figsize=(5,3))
97
+ ax.plot(x_array, height_array, label='Pile Shape', linewidth=2, color='blue')
98
+
99
+ if sensor_positions is not None and sensor_heights is not None:
100
+ ax.scatter(sensor_positions, sensor_heights, color='red',
101
+ label='Sensor Readings', s=50, zorder=5)
102
+
103
+ ax.set_title("Silo Cross-Section (2D) - Wood Chip Pile")
104
+ ax.set_xlabel("Normalized Silo Width (0..1)")
105
+ ax.set_ylabel("Height (m)")
106
+ ax.legend()
107
+ fig.tight_layout()
108
+ return fig
109
+
110
+ def plot_sensor_bar(sensor_positions, sensor_heights):
111
+ """
112
+ Creates and returns a bar chart showing sensor heights
113
+ vs. sensor ID or position.
114
+ """
115
+ fig, ax = plt.subplots(figsize=(4,3))
116
+ # Let's use sensor index as x-ticks
117
+ indices = np.arange(len(sensor_positions))
118
+ ax.bar(indices, sensor_heights, color='green')
119
+ ax.set_title("Sensor Readings Bar Chart")
120
+ ax.set_xlabel("Sensor Index")
121
+ ax.set_ylabel("Measured Height (m)")
122
+ ax.set_xticks(indices)
123
+ ax.set_xticklabels([f"{pos:.2f}" for pos in sensor_positions], rotation=45)
124
+ fig.tight_layout()
125
+ return fig
126
+
127
+ # --------------------------------------
128
+ # Streamlit App
129
+ # --------------------------------------
130
+
131
+ st.set_page_config(page_title="ITC PSPD - Wood Chip Silo Demo", layout="centered")
132
+
133
+ st.title("Wood Chip Silo Monitoring - ITC PSPD Demo")
134
+
135
+ st.markdown("""
136
+ **Welcome to a simple demonstration of ** *Wood-chip silo* height profile Detection and Volume Estimation*.
137
+ This is an **Industry 4.0** inspired solution to **digitally** track the *height* and *volume*
138
+ of wood chips in a silo, supporting **paperboard and specialty paper** operations at **ITC PSPD**.
139
+
140
+ ---
141
+ """)
142
+
143
+ # Sidebar controls
144
+ st.sidebar.header("Simulation Controls")
145
+
146
+ sensor_type = st.sidebar.selectbox("Select Sensor Type:", ["LiDAR", "Ultrasonic"])
147
+ max_height = st.sidebar.slider("Max Potential Height (m):", 5.0, 30.0, 10.0, 1.0)
148
+ noise_level = st.sidebar.slider("Random Noise Level:", 0.0, 3.0, 0.5, 0.1)
149
+ num_points = st.sidebar.slider("Pile Resolution (Points):", 20, 200, 50, 10)
150
+ num_sensors = st.sidebar.slider("Number of Sensors:", 1, 12, 5)
151
+ silo_width = st.sidebar.slider("Silo Width (m):", 1.0, 20.0, 5.0, 1.0)
152
+ silo_depth = st.sidebar.slider("Silo Depth (m):", 1.0, 20.0, 5.0, 1.0)
153
+
154
+ # Button to generate new random pile
155
+ if "x_array" not in st.session_state or "height_array" not in st.session_state:
156
+ st.session_state["x_array"] = None
157
+ st.session_state["height_array"] = None
158
+
159
+ if st.sidebar.button("Generate New Random Pile"):
160
+ x_array, height_array = generate_random_pile(
161
+ num_points=num_points,
162
+ max_height=max_height,
163
+ noise=noise_level
164
+ )
165
+ st.session_state["x_array"] = x_array
166
+ st.session_state["height_array"] = height_array
167
+
168
+ # If we have data, proceed
169
+ if st.session_state["x_array"] is not None and st.session_state["height_array"] is not None:
170
+ x_array = st.session_state["x_array"]
171
+ height_array = st.session_state["height_array"]
172
+
173
+ # Simulate sensor readings
174
+ sensor_positions, sensor_heights = simulate_sensor_readings(
175
+ x_array, height_array, sensor_type, num_sensors
176
+ )
177
+
178
+ # Estimate peak height from sensor
179
+ approx_peak = estimate_peak_height(sensor_positions, sensor_heights)
180
+ # Estimate volume
181
+ volume_est = estimate_volume(x_array, height_array, silo_width, silo_depth)
182
+
183
+ st.subheader("1) Silo Cross-Section Visualization")
184
+ fig_pile = plot_pile_shape(x_array, height_array, sensor_positions, sensor_heights)
185
+ st.pyplot(fig_pile)
186
+
187
+ # Show measured peak height
188
+ col1, col2 = st.columns(2)
189
+ with col1:
190
+ st.markdown(f"**Approx Peak Height (from sensors):** `{approx_peak:.2f} m`")
191
+ st.progress(min(approx_peak/max_height, 1.0))
192
+ with col2:
193
+ st.markdown(f"**Estimated Volume:** `{volume_est:.2f} m³`")
194
+ st.progress(min(volume_est/((max_height*silo_width*silo_depth)), 1.0))
195
+
196
+ st.subheader("2) Sensor Data Overview")
197
+
198
+ col3, col4 = st.columns(2)
199
+ with col3:
200
+ # Show bar chart of sensor readings
201
+ fig_bars = plot_sensor_bar(sensor_positions, sensor_heights)
202
+ st.pyplot(fig_bars)
203
+ with col4:
204
+ st.write("### Sensor Data Table")
205
+ table_data = {
206
+ "Position (norm)": [f"{pos:.2f}" for pos in sensor_positions],
207
+ "Height (m)": [f"{h:.2f}" for h in sensor_heights]
208
+ }
209
+ st.table(table_data)
210
+
211
+ st.markdown("---")
212
+
213
+ st.markdown("""
214
+ ### Interpretation & Industry 4.0 Connection
215
+
216
+ - **Digital Twins**: This simulation mimics how a *digital twin* of the silo tracks
217
+ wood-chip heights. The real system could have *LiDAR* sensors scanning
218
+ or an array of *ultrasonic* sensors measuring the pile at intervals.
219
+ - **Data-Driven Insights**: Automated volume estimation helps production planning,
220
+ ensuring continuous supply for **paperboard** manufacturing while minimizing
221
+ waste or silo overflow.
222
+ - **ITC PSPD**: As a leading division in paper & packaging, adopting *Industry 4.0*
223
+ solutions supports sustainability and operational efficiency—aligned with ITC’s
224
+ triple bottom line.
225
+ - **Big Data & AI**: If multiple silos or sites feed data into a cloud platform,
226
+ advanced analytics/predictive models can optimize inventory or detect anomalies.
227
+
228
+ ---
229
+ """)
230
+ else:
231
+ st.info("Use the **sidebar** to generate a new random pile and view the results.")
232
+
233
+ st.markdown("""
234
+ ## Made By:
235
+ - Name: Kaustubh Raykar
236
+ - PRN: 21070126048
237
+ - Btech AIML 2021-25
238
+ - Contact: +91 7020524609
239
+ - Symbiosis Institute Of Technology, Pune
240
241
242
+ **Thank you** for exploring this Wood Chip Silo Demo for **ITC PSPD**.
243
+ """)