/
chnik
/
KamVegDis
Обзор
Документация
Войти
/
chnik
/
KamVegDis
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
visualisation.py
212 строк
11 KB
chnik
upload files
31 янв 2026, 13:27
Верифицирован
31 янв 2026, 13:27
7c5c234
Код
Авторство
О чём код?
import streamlit as st import pandas as pd import numpy as np import plotly.graph_objects as go # --- 1. НАСТРОЙКИ СТРАНИЦЫ --- st.set_page_config(page_title="BioTopography Pro v2.5", layout="wide") CELL_SIZE = 0.3 TOTAL_AREA = CELL_SIZE**2 IDS_GRID = np.array([ [10, 11], [9, 12], [8, 13], [7, 14], [6, 15], [5, 16], [4, 17], [3, 18], [2, 19], [1, 20] ]) # --- 2. ОБРАБОТКА ДАННЫХ --- @st.cache_data def process_file(file): try: try: df = pd.read_csv(file, sep=';', encoding='utf-8-sig') except: file.seek(0) df = pd.read_csv(file, sep=';', encoding='windows-1251') slope_mask = df.iloc[:, 0].astype(str).str.contains('Уклон', case=False, na=False) if not slope_mask.any(): return None, None, None, "Ошибка: 'Уклон' не найден" slope_idx = df[slope_mask].index[0] s_vals = pd.to_numeric(df.iloc[slope_idx, 2:22], errors='coerce').fillna(0).values s_map = {i+1: float(v) for i, v in enumerate(s_vals)} plant_df = df.iloc[slope_idx + 1:].copy() p_data, p_types = {}, {} for _, row in plant_df.iterrows(): name = str(row.iloc[0]).strip() p_type = str(row.iloc[1]).strip().upper() if name and name != 'nan' and not any(x in name for x in ['Ярус', 'Кт', 'Уклон']): vals = pd.to_numeric(row.iloc[2:22], errors='coerce').fillna(0).values p_data[name] = {i+1: float(x) for i, x in enumerate(vals)} p_types[name] = p_type return p_data, p_types, s_map, None except Exception as e: return None, None, None, str(e) def calculate_ecology_indices(p_data, s_map): rows, cols = 10, 2 richness = np.zeros((rows, cols)) shannon = np.zeros((rows, cols)) total_cov = np.zeros((rows, cols)) all_sp = list(p_data.keys()) for r in range(rows): for c in range(cols): cid = IDS_GRID[r, c] cell_vals = [] for sp in all_sp: val = p_data[sp].get(cid, 0) if val > 0: richness[r, c] += 1 total_cov[r, c] += val cell_vals.append(val) if total_cov[r, c] > 0: p_i = np.array(cell_vals) / total_cov[r, c] shannon[r, c] = -np.sum(p_i * np.log(p_i + 1e-9)) twi = np.zeros((rows, cols)) for r in range(rows): upslope_area = (r + 1) * TOTAL_AREA for c in range(cols): cid = IDS_GRID[r, c] angle = np.radians(max(s_map.get(cid, 0), 0.5)) twi[r, c] = np.log(upslope_area / np.tan(angle)) beta = np.where(richness > 0, (len(all_sp) / richness) - 1, 0) return twi, total_cov/100, richness, shannon, beta # --- 3. ИНТЕРФЕЙС --- st.sidebar.title("🌿 Bio-Tech Lab") files = st.sidebar.file_uploader("Загрузите файлы (.csv)", type=["csv"], accept_multiple_files=True) if files: file_map = {f.name: f for f in files} selected_name = st.sidebar.selectbox("Выберите участок:", list(file_map.keys())) p_data, p_types, s_map, error = process_file(file_map[selected_name]) if error: st.error(error) else: all_sp = list(p_data.keys()) t_veg, t_topo, t_eco = st.tabs(["🍀 Покрытие", "⛰️ Рельеф", "🧪 Эко-Индексы"]) # --- ВКЛАДКА 1: ПОКРЫТИЕ --- with t_veg: mode = st.sidebar.radio("Режим:", ["Виды", "Группы (Ярусы)", "Доминанты"]) display_sp = [] if mode == "Виды": display_sp = st.sidebar.multiselect("Выберите виды:", all_sp, default=all_sp[:1]) elif mode == "Группы (Ярусы)": g = st.sidebar.selectbox("Ярус:", ["Всего", "ТКЯ", "МЛЯ"]) display_sp = all_sp if g == "Всего" else [n for n in all_sp if g in p_types.get(n, '')] else: display_sp = all_sp z_map = np.zeros((10, 2)) dom_names = [["" for _ in range(2)] for _ in range(10)] for r in range(10): for c in range(2): cid = IDS_GRID[r, c] if mode == "Доминанты": best_v, best_s = -1, "" for s in all_sp: v = p_data[s].get(cid, 0) if v > best_v: best_v, best_s = v, s z_map[r, c], dom_names[r][c] = best_v, best_s else: for s in display_sp: z_map[r, c] += p_data[s].get(cid, 0) fig_v = go.Figure(data=go.Heatmap(z=z_map, x=['Запад', 'Восток'], y=[f"Ряд {10-i}" for i in range(10)], colorscale='Viridis')) ann = [] for r in range(10): for c in range(2): cid, val = IDS_GRID[r, c], z_map[r, c] txt = f"ID: {cid}<br>∠{s_map.get(cid,0)}°<br><b>{val:.0f}%</b>" if mode == "Доминанты" and val > 0: txt = f"ID: {cid}<br>{dom_names[r][c][:12]}<br><b>{val:.0f}%</b>" ann.append(dict(x=c, y=r, text=txt, showarrow=False, font=dict(color="white" if val < 45 else "black"))) fig_v.update_layout(annotations=ann, height=750, yaxis=dict(autorange='reversed')) st.plotly_chart(fig_v, use_container_width=True) # --- ВКЛАДКА 2: РЕЛЬЕФ (ВОССТАНОВЛЕНО ПОЛНОСТЬЮ) --- with t_topo: # Расчет матрицы высот h_mat = np.zeros((11, 3)) for r in range(1, 11): grid_idx = 10 - r id_w, id_e = IDS_GRID[grid_idx, 0], IDS_GRID[grid_idx, 1] ang_w = np.radians(s_map.get(id_w, 0)) ang_e = np.radians(s_map.get(id_e, 0)) h_mat[r, 0] = h_mat[r-1, 0] + CELL_SIZE * np.sin(ang_w) h_mat[r, 2] = h_mat[r-1, 2] + CELL_SIZE * np.sin(ang_e) h_mat[r, 1] = (h_mat[r, 0] + h_mat[r, 2]) / 2 c1, c2 = st.columns(2) with c1: st.subheader("3D Модель поверхности") fig_3d = go.Figure(data=[go.Surface(z=h_mat, colorscale='gray', showscale=False)]) fig_3d.update_layout( scene=dict(xaxis_title='Ширина', yaxis_title='Ряды', zaxis_title='Высота (м)'), height=600, margin=dict(l=0, r=0, b=0, t=0) ) st.plotly_chart(fig_3d, use_container_width=True) st.caption("Визуализация топографии участка на основе замеров уклона в каждой ячейке.") with c2: st.subheader("Высотный профиль") dist = np.arange(0, 11) * CELL_SIZE fig_p = go.Figure() fig_p.add_trace(go.Scatter(x=dist, y=h_mat[:, 1], name="Линия сочленения (Центр)", line=dict(color='black', width=3))) fig_p.add_trace(go.Scatter(x=dist, y=h_mat[:, 0], name="Западный профиль", line=dict(dash='dot'))) fig_p.add_trace(go.Scatter(x=dist, y=h_mat[:, 2], name="Восточный профиль", line=dict(dash='dot'))) fig_p.update_layout(height=500, xaxis_title="Метры от подножия", yaxis_title="Высота (м)") st.plotly_chart(fig_p, use_container_width=True) st.caption("Разрез участка: сплошная линия показывает среднюю высоту, пунктирные — края трансекты.") # --- ВКЛАДКА 3: ЭКО-ИНДЕКСЫ --- with t_eco: st.header("Комплексный анализ экосистемы") twi, ndvi, rich, alpha, beta = calculate_ecology_indices(p_data, s_map) cols = st.columns(3) with cols[0]: st.markdown("#### 1. TWI (Индекс влажности)") st.latex(r"TWI = \ln\left(\frac{a}{\tan \beta}\right)") st.caption("**Методология:** Оценка топографического увлажнения. $a$ — удельная площадь водосбора, $\beta$ — локальный уклон.") fig = go.Figure(data=go.Heatmap(z=twi, colorscale='Blues', x=['З', 'В'])) st.plotly_chart(fig.update_layout(height=400, yaxis=dict(autorange='reversed')), use_container_width=True) with cols[1]: st.markdown("#### 2. Pseudo-NDVI") st.latex(r"NDVI_{p} = \frac{\sum Cov_{i}}{100}") st.caption("**Методология:** Оценка плотности фитомассы через нормализованную сумму проективного покрытия всех видов.") fig = go.Figure(data=go.Heatmap(z=ndvi, colorscale='YlGn', x=['З', 'В'], zmin=0, zmax=1)) st.plotly_chart(fig.update_layout(height=400, yaxis=dict(autorange='reversed')), use_container_width=True) with cols[2]: st.markdown("#### 3. Видовое богатство") st.latex(r"S = \sum [Cov_i > 0]") st.caption("**Методология:** Общее количество уникальных видов, зафиксированных в данной ячейке.") fig = go.Figure(data=go.Heatmap(z=rich, colorscale='Viridis', x=['З', 'В'])) st.plotly_chart(fig.update_layout(height=400, yaxis=dict(autorange='reversed')), use_container_width=True) st.divider() cols2 = st.columns(2) with cols2[0]: st.markdown("#### 4. Альфа-разнообразие (Индекс Шеннона)") st.latex(r"H' = -\sum_{i=1}^{S} p_i \ln p_i") st.caption("**Методология:** Учитывает не только количество видов, но и равномерность их обилия ($p_i$).") fig = go.Figure(data=go.Heatmap(z=alpha, colorscale='Portland', x=['З', 'В'])) st.plotly_chart(fig.update_layout(height=450, yaxis=dict(autorange='reversed')), use_container_width=True) with cols2[1]: st.markdown("#### 5. Бета-разнообразие (Индекс Уиттекер)") st.latex(r"\beta_w = (S_{total} / \alpha) - 1") st.caption("**Методология:** Степень отклонения состава конкретной ячейки от общего состава всего участка.") fig = go.Figure(data=go.Heatmap(z=beta, colorscale='Electric', x=['З', 'В'])) st.plotly_chart(fig.update_layout(height=450, yaxis=dict(autorange='reversed')), use_container_width=True) else: st.info("👈 Загрузите CSV файлы для начала анализа.")