Update app.py
Browse files
app.py
CHANGED
|
@@ -2,101 +2,64 @@ import streamlit as st
|
|
| 2 |
import numpy as np
|
| 3 |
import scipy.io
|
| 4 |
import matplotlib.pyplot as plt
|
|
|
|
|
|
|
| 5 |
import zipfile
|
| 6 |
import os
|
| 7 |
|
| 8 |
-
# --- ตั้งค่า
|
| 9 |
st.set_page_config(layout="wide", page_title="K-Space to MRI")
|
| 10 |
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
@st.cache_data
|
| 13 |
def load_kspace_data():
|
| 14 |
try:
|
| 15 |
-
# ตรวจสอบว่ามีไฟล์ mat หรือ zip ใน directory หรือไม่
|
| 16 |
if os.path.exists('kspace.mat'):
|
| 17 |
mat_data = scipy.io.loadmat('kspace.mat')
|
| 18 |
-
|
| 19 |
with zipfile.ZipFile("kspace.zip", 'r') as zip_ref:
|
| 20 |
zip_ref.extractall("temp_kspace")
|
| 21 |
mat_data = scipy.io.loadmat("temp_kspace/kspace.mat")
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
dummy_image = np.sin(X**2 + Y**2) * np.exp(-(X**2 + Y**2)/10)
|
| 31 |
-
return np.fft.fftshift(np.fft.fft2(dummy_image))
|
| 32 |
-
|
| 33 |
-
kspace_data = load_kspace_data()
|
| 34 |
-
|
| 35 |
-
def kspace_to_image(kspace_freq):
|
| 36 |
-
# แปลง K-space กลับเป็นภาพ MRI ขนาด 224x224
|
| 37 |
-
img = np.fft.ifft2(np.fft.ifftshift(kspace_freq))
|
| 38 |
-
img_mag = np.abs(img)
|
| 39 |
-
# Normalize ให้ค่าอยู่ระหว่าง 0-1
|
| 40 |
-
img_norm = (img_mag - img_mag.min()) / (img_mag.max() - img_mag.min())
|
| 41 |
-
return img_norm
|
| 42 |
-
|
| 43 |
-
def generate_kspace_axis_image():
|
| 44 |
-
# จำลองรูปแกน K-Space ตามที่แนบมา
|
| 45 |
-
fig, ax = plt.subplots(figsize=(5, 5))
|
| 46 |
-
ax.axhline(0, color='black', linewidth=2)
|
| 47 |
-
ax.axvline(0, color='black', linewidth=2)
|
| 48 |
-
|
| 49 |
-
ax.text(0.95, 0.05, 'kx\n(Frequency)', transform=ax.transAxes, ha='right', va='bottom', fontsize=12, fontweight='bold', color='red')
|
| 50 |
-
ax.text(0.05, 0.95, 'ky\n(Phase)', transform=ax.transAxes, ha='left', va='top', fontsize=12, fontweight='bold', color='red')
|
| 51 |
-
|
| 52 |
-
ax.grid(True, linestyle='--', alpha=0.5)
|
| 53 |
-
ax.set_xlim(-1, 1)
|
| 54 |
-
ax.set_ylim(-1, 1)
|
| 55 |
-
ax.set_xticks([])
|
| 56 |
-
ax.set_yticks([])
|
| 57 |
-
ax.set_title("K-Space", fontweight='bold')
|
| 58 |
-
|
| 59 |
-
# วาดคลื่นจำลองในกราฟให้คล้ายกับรูปที่ร่างไว้
|
| 60 |
-
x = np.linspace(-1, 1, 100)
|
| 61 |
-
y = 0.2 * np.sin(10 * x)
|
| 62 |
-
ax.plot(x, y, color='red', alpha=0.5)
|
| 63 |
-
ax.plot(y, x, color='blue', alpha=0.5)
|
| 64 |
-
|
| 65 |
-
return fig
|
| 66 |
|
| 67 |
-
|
| 68 |
-
x = np.arange(size)
|
| 69 |
-
y = np.arange(size)
|
| 70 |
-
X, Y = np.meshgrid(x, y)
|
| 71 |
-
|
| 72 |
-
X = X - size // 2
|
| 73 |
-
Y = Y - size // 2
|
| 74 |
-
|
| 75 |
-
# สร้างคลื่น 2D Sinusoidal
|
| 76 |
-
wave = np.cos(2 * np.pi * (kx * X + ky * Y) / size)
|
| 77 |
-
return wave
|
| 78 |
|
| 79 |
-
def
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
ax.plot(kx, ky, 'ro', markersize=8)
|
| 84 |
-
# วาดเส้นแสดงทิศทาง
|
| 85 |
-
ax.annotate('', xy=(kx, ky), xytext=(0, 0),
|
| 86 |
-
arrowprops=dict(arrowstyle='->', color='yellow', lw=2))
|
| 87 |
-
|
| 88 |
-
ax.axhline(0, color='white', linewidth=0.5, linestyle='--')
|
| 89 |
-
ax.axvline(0, color='white', linewidth=0.5, linestyle='--')
|
| 90 |
-
|
| 91 |
-
ax.set_xlim(-size//2, size//2)
|
| 92 |
-
ax.set_ylim(-size//2, size//2)
|
| 93 |
-
ax.set_title(f"พิกัด K-Space (kx={kx}, ky={ky})")
|
| 94 |
-
ax.axis('off')
|
| 95 |
-
return fig
|
| 96 |
|
| 97 |
-
# --- ส่วน
|
| 98 |
|
| 99 |
-
st.
|
| 100 |
|
| 101 |
st.markdown("""
|
| 102 |
**K-space คือ**
|
|
@@ -105,11 +68,29 @@ st.markdown("""
|
|
| 105 |
|
| 106 |
st.header("องค์ประกอบของ K-Space")
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
st.markdown("""
|
| 114 |
ข้อมูลใน k-space มักจะถูกนำมาแสดงผลในรูปแบบตารางสี่เหลี่ยม (Grid) โดยมีแกนหลักคือ kx (แนวนอน - Frequency) และ ky (แนวตั้ง - Phase) แต่จุดสำคัญคือ แกน kx และ ky เหล่านี้ ไม่ได้บอกตำแหน่งพิกัด ในภาพ แต่มันคือแกนที่บอกถึง ลักษณะของ "ความถี่เชิงพื้นที่ (Spatial Frequencies)" ซึ่งเป็นคลื่นความถี่ Sinusoidal wave ด้วยเหตุนี้ จุดแต่ละจุดบนพิกัด (kx, ky) ใน k-space จึง ไม่ได้จับคู่แบบ 1 ต่อ 1 กับพิกเซล (x, y) บนภาพ MRI (ไม่ได้แปลว่าจุดมุมซ้ายบนใน k-space จะสร้างภาพมุมซ้ายบนของ ภาพอวัยวะ)
|
| 115 |
""")
|
|
@@ -117,37 +98,45 @@ with col2:
|
|
| 117 |
st.divider()
|
| 118 |
|
| 119 |
st.header("1 จุดบน k-space")
|
|
|
|
| 120 |
|
| 121 |
-
# --- Interactive Part
|
| 122 |
-
st.
|
| 123 |
-
|
| 124 |
-
if 'kx_val' not in st.session_state:
|
| 125 |
-
st.session_state.kx_val = 15
|
| 126 |
-
if 'ky_val' not in st.session_state:
|
| 127 |
-
st.session_state.ky_val = 20
|
| 128 |
-
|
| 129 |
-
def reset_point():
|
| 130 |
-
st.session_state.kx_val = 0
|
| 131 |
-
st.session_state.ky_val = 0
|
| 132 |
-
|
| 133 |
-
col_w1, col_w2, col_w3 = st.columns([1, 1, 1])
|
| 134 |
-
with col_w1:
|
| 135 |
-
st.slider("ตำแหน่งแกน kx (แนวนอน)", -112, 111, key='kx_val')
|
| 136 |
-
st.slider("ตำแหน่งแกน ky (แนวตั้ง)", -112, 111, key='ky_val')
|
| 137 |
-
st.button("Reset จุด", on_click=reset_point)
|
| 138 |
-
|
| 139 |
-
with col_w2:
|
| 140 |
-
fig_pt = draw_kspace_point(st.session_state.kx_val, st.session_state.ky_val)
|
| 141 |
-
st.pyplot(fig_pt)
|
| 142 |
-
|
| 143 |
-
with col_w3:
|
| 144 |
-
wave_img = generate_2d_wave(st.session_state.kx_val, st.session_state.ky_val)
|
| 145 |
-
fig_wave, ax_wave = plt.subplots(figsize=(5, 5))
|
| 146 |
-
ax_wave.imshow(wave_img, cmap='gray')
|
| 147 |
-
ax_wave.set_title("แผ่นลวดลายคลื่น (2D Sinusoidal Wave)")
|
| 148 |
-
ax_wave.axis('off')
|
| 149 |
-
st.pyplot(fig_wave)
|
| 150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
st.markdown("""
|
| 153 |
**k-space 1 จุด = ข้อมูลของภาพทั้งภาพ และ ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด**
|
|
@@ -187,83 +176,54 @@ st.markdown("""
|
|
| 187 |
- **ตัวอย่างในภาพ MRI:** ขอบของอวัยวะ (Edges), รอยต่อระหว่างกระดูกกับไขสันหลัง, หรือรายละเอียดเส้นเลือดเส้นเล็กๆ
|
| 188 |
- **ตำแหน่งใน k-space:** ข้อมูลเหล่านี้จะกระจายตัวอยู่บริเวณ "ขอบนอก"
|
| 189 |
- **หน้าที่หลัก:** สร้าง "ความคมชัด (Resolution) และรายละเอียดเล็กๆ" ทำให้ภาพไม่เบลอ
|
| 190 |
-
|
| 191 |
-
ซึ่งจะให้ผู้เรียนได้ลองปรับหน้าตาของภาพ K-Space แล้วเปรียบเทียบความแตกต่างด้วยการปรับ High-pass filter และ Low-pass filter เพื่อดูลักษณะและความสำคัญของข้อมูลบริเวณกลางและขอบนอกของ K-space
|
| 192 |
-
เมื่อเราจำแนกข้อมูลใน k-space ออกเป็นความถี่ต่ำ (ตรงกลาง) และความถี่สูง (ขอบนอก) ได้แล้ว เราสามารถเลือก "หยิบ" หรือ "ทิ้ง" ข้อมูลบางส่วนเพื่อดูผลลัพธ์ได้ เรียกว่าการใช้ตัวกรอง (Filter)
|
| 193 |
""")
|
| 194 |
|
| 195 |
st.divider()
|
| 196 |
|
| 197 |
-
# ---
|
| 198 |
-
st.subheader("
|
| 199 |
|
| 200 |
-
|
| 201 |
-
st.session_state.filter_type = 'Low-pass Filter'
|
| 202 |
-
if 'filter_radius' not in st.session_state:
|
| 203 |
-
st.session_state.filter_radius = 112
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
st.session_state.filter_radius = 112
|
| 208 |
-
|
| 209 |
-
col_f1, col_f2 = st.columns([1, 1.5])
|
| 210 |
-
with col_f1:
|
| 211 |
-
filter_choice = st.radio("เลือกรูปแบบ Filter", ['Low-pass Filter', 'High-pass Filter'], key='filter_type')
|
| 212 |
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
st.
|
| 217 |
-
|
| 218 |
-
st.button("Reset ค่า Filter", on_click=reset_filter)
|
| 219 |
-
|
| 220 |
-
with col_f2:
|
| 221 |
-
if filter_choice == 'Low-pass Filter':
|
| 222 |
-
with st.expander("รายละเอียด Low-pass Filter", expanded=True):
|
| 223 |
-
st.markdown("""
|
| 224 |
-
**Low-pass Filter (ตัวกรองปล่อยความถี่ต่ำผ่าน):**
|
| 225 |
-
- **ทำงานอย่างไร:** "อนุญาตให้เฉพาะข้อมูลตรงกลาง (ความถี่ต่ำ) ผ่านไปสร้างภาพได้ ส่วนข้อมูลขอบนอก (ความถี่สูง) ให้ทิ้งไป"
|
| 226 |
-
- **ผลลัพธ์ที่ได้:** เราจะได้ภาพที่มี "คอนทราสต์" ดูออกว่าเป็นอวัยวะอะไร แต่ภาพจะ "เบลอ" (Blurry) เพราะข้อมูลเส้นขอบถูกทิ้งไปแล้ว
|
| 227 |
-
""")
|
| 228 |
else:
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
#
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
with col_img2:
|
| 264 |
-
mri_img = kspace_to_image(filtered_kspace)
|
| 265 |
-
fig_m, ax_m = plt.subplots(figsize=(5, 5))
|
| 266 |
-
ax_m.imshow(mri_img, cmap='gray')
|
| 267 |
-
ax_m.set_title("ภาพ MRI ผลลัพธ์ (ขนาด 224x224)", fontweight='bold')
|
| 268 |
-
ax_m.axis('off')
|
| 269 |
-
st.pyplot(fig_m)
|
|
|
|
| 2 |
import numpy as np
|
| 3 |
import scipy.io
|
| 4 |
import matplotlib.pyplot as plt
|
| 5 |
+
import plotly.express as px
|
| 6 |
+
import plotly.graph_objects as go
|
| 7 |
import zipfile
|
| 8 |
import os
|
| 9 |
|
| 10 |
+
# --- 1. ตั้งค่า CSS เพื่อปรับขนาดตัวอักษรและจัดกลาง ---
|
| 11 |
st.set_page_config(layout="wide", page_title="K-Space to MRI")
|
| 12 |
|
| 13 |
+
st.markdown("""
|
| 14 |
+
<style>
|
| 15 |
+
/* ปรับขนาดตัวอักษรหลัก */
|
| 16 |
+
html, body, [class*="st-"] {
|
| 17 |
+
font-size: 18px;
|
| 18 |
+
}
|
| 19 |
+
/* หัวข้อหลักให้อยู่กลาง */
|
| 20 |
+
.main-title {
|
| 21 |
+
text-align: center;
|
| 22 |
+
font-size: 45px !important;
|
| 23 |
+
font-weight: bold;
|
| 24 |
+
color: #1E88E5;
|
| 25 |
+
margin-bottom: 30px;
|
| 26 |
+
}
|
| 27 |
+
/* หัวข้อรอง */
|
| 28 |
+
h1, h2, h3 {
|
| 29 |
+
color: #0D47A1;
|
| 30 |
+
}
|
| 31 |
+
</style>
|
| 32 |
+
""", unsafe_allow_html=True)
|
| 33 |
+
|
| 34 |
+
# --- 2. ฟังก์ชันโหลดข้อมูลและการคำนวณ ---
|
| 35 |
@st.cache_data
|
| 36 |
def load_kspace_data():
|
| 37 |
try:
|
|
|
|
| 38 |
if os.path.exists('kspace.mat'):
|
| 39 |
mat_data = scipy.io.loadmat('kspace.mat')
|
| 40 |
+
elif os.path.exists('kspace.zip'):
|
| 41 |
with zipfile.ZipFile("kspace.zip", 'r') as zip_ref:
|
| 42 |
zip_ref.extractall("temp_kspace")
|
| 43 |
mat_data = scipy.io.loadmat("temp_kspace/kspace.mat")
|
| 44 |
+
else:
|
| 45 |
+
# สร้างข้อมูลจำลองถ้าไม่เจอไฟล์
|
| 46 |
+
return np.zeros((224, 224))
|
| 47 |
+
|
| 48 |
+
k = mat_data['kspace']
|
| 49 |
+
return k
|
| 50 |
+
except:
|
| 51 |
+
return np.zeros((224, 224))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
+
kspace_raw = load_kspace_data()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
+
def get_mri_image(k_data):
|
| 56 |
+
# แก้ Artifact สมอง 4 มุม: ต้อง shift ก่อน IFFT และ shift ผลลัพธ์หลัง IFFT อีกรอบ
|
| 57 |
+
img = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(k_data)))
|
| 58 |
+
return np.abs(img)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
# --- 3. ส่วนหน้าเว็บ ---
|
| 61 |
|
| 62 |
+
st.markdown('<p class="main-title">K-space to MRI image</p>', unsafe_allow_html=True)
|
| 63 |
|
| 64 |
st.markdown("""
|
| 65 |
**K-space คือ**
|
|
|
|
| 68 |
|
| 69 |
st.header("องค์ประกอบของ K-Space")
|
| 70 |
|
| 71 |
+
# ส่วนที่ทำภาพเลียนแบบ "รูปเขียว" (K-space axis)
|
| 72 |
+
col_ax1, col_ax2 = st.columns([1, 1])
|
| 73 |
+
with col_ax1:
|
| 74 |
+
fig_axis = go.Figure()
|
| 75 |
+
# วาด Grid และแกน
|
| 76 |
+
for i in range(-5, 6):
|
| 77 |
+
fig_axis.add_trace(go.Scatter(x=[-5, 5], y=[i, i], mode='lines', line=dict(color='lightgreen', width=1), showlegend=False))
|
| 78 |
+
fig_axis.add_trace(go.Scatter(x=[i, i], y=[-5, 5], mode='lines', line=dict(color='lightgreen', width=1), showlegend=False))
|
| 79 |
+
|
| 80 |
+
# ลูกศรแกน
|
| 81 |
+
fig_axis.add_annotation(x=5.5, y=0, text="kx (Frequency)", showarrow=True, arrowhead=2, ax=-40, ay=0, font=dict(color="red", size=14))
|
| 82 |
+
fig_axis.add_annotation(x=0, y=5.5, text="ky (Phase)", showarrow=True, arrowhead=2, ax=0, ay=40, font=dict(color="red", size=14))
|
| 83 |
+
|
| 84 |
+
fig_axis.update_layout(
|
| 85 |
+
title="K-space axis",
|
| 86 |
+
xaxis=dict(visible=False, range=[-6, 6]),
|
| 87 |
+
yaxis=dict(visible=False, range=[-6, 6]),
|
| 88 |
+
width=400, height=400,
|
| 89 |
+
plot_bgcolor='white'
|
| 90 |
+
)
|
| 91 |
+
st.plotly_chart(fig_axis)
|
| 92 |
+
|
| 93 |
+
with col_ax2:
|
| 94 |
st.markdown("""
|
| 95 |
ข้อมูลใน k-space มักจะถูกนำมาแสดงผลในรูปแบบตารางสี่เหลี่ยม (Grid) โดยมีแกนหลักคือ kx (แนวนอน - Frequency) และ ky (แนวตั้ง - Phase) แต่จุดสำคัญคือ แกน kx และ ky เหล่านี้ ไม่ได้บอกตำแหน่งพิกัด ในภาพ แต่มันคือแกนที่บอกถึง ลักษณะของ "ความถี่เชิงพื้นที่ (Spatial Frequencies)" ซึ่งเป็นคลื่นความถี่ Sinusoidal wave ด้วยเหตุนี้ จุดแต่ละจุดบนพิกัด (kx, ky) ใน k-space จึง ไม่ได้จับคู่แบบ 1 ต่อ 1 กับพิกเซล (x, y) บนภาพ MRI (ไม่ได้แปลว่าจุดมุมซ้ายบนใน k-space จะสร้างภาพมุมซ้ายบนของ ภาพอวัยวะ)
|
| 96 |
""")
|
|
|
|
| 98 |
st.divider()
|
| 99 |
|
| 100 |
st.header("1 จุดบน k-space")
|
| 101 |
+
st.info("💡 วิธีเล่น: กดคลิกที่จุดใดก็ได้บนภาพ K-Space ด้านซ้าย เพื่อดูคลื่นความถี่ที่เกิดขึ้นในจุดนั้น")
|
| 102 |
|
| 103 |
+
# --- Interactive Part: คลิกบนภาพ K-Space ---
|
| 104 |
+
col_int1, col_int2 = st.columns([1, 1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
+
with col_int1:
|
| 107 |
+
# แสดงภาพ K-Space แบบ Log scale เพื่อให้เห็นข้อมูลชัด��จน
|
| 108 |
+
k_display = np.log(np.abs(kspace_raw) + 1)
|
| 109 |
+
|
| 110 |
+
# ใช้ Plotly เพื่อให้คลิกได้
|
| 111 |
+
fig_k = px.imshow(k_display, color_continuous_scale='gray', labels=dict(x="kx", y="ky"))
|
| 112 |
+
fig_k.update_layout(coloraxis_showscale=False, margin=dict(l=20, r=20, t=30, b=20), width=450, height=450)
|
| 113 |
+
|
| 114 |
+
# จับเหตุการณ์การคลิก (ใน Streamlit จะใช้การเลือกจุดจาก Plotly)
|
| 115 |
+
selected_point = st.plotly_chart(fig_k, on_select="rerun")
|
| 116 |
+
|
| 117 |
+
# กำหนดจุดเริ่มต้นถ้ายังไม่กด
|
| 118 |
+
kx_idx, ky_idx = 127, 127 # กลางภาพ
|
| 119 |
+
if selected_point and "points" in selected_point and len(selected_point["points"]) > 0:
|
| 120 |
+
kx_idx = selected_point["points"][0]["x"]
|
| 121 |
+
ky_idx = selected_point["points"][0]["y"]
|
| 122 |
+
|
| 123 |
+
with col_int2:
|
| 124 |
+
# คำนวณคลื่น 2D จากจุดที่เลือก
|
| 125 |
+
size = 224
|
| 126 |
+
x = np.linspace(-size//2, size//2, size)
|
| 127 |
+
y = np.linspace(-size//2, size//2, size)
|
| 128 |
+
X, Y = np.meshgrid(x, y)
|
| 129 |
+
|
| 130 |
+
# ความถี่ตามตำแหน่ง kx, ky (relative to center)
|
| 131 |
+
freq_x = (kx_idx - size//2) / size
|
| 132 |
+
freq_y = (ky_idx - size//2) / size
|
| 133 |
+
wave = np.cos(2 * np.pi * (freq_x * X + freq_y * Y))
|
| 134 |
+
|
| 135 |
+
fig_wave = px.imshow(wave, color_continuous_scale='gray', title=f"คลื่นที่พิกัด kx:{kx_idx-112}, ky:{112-ky_idx}")
|
| 136 |
+
fig_wave.update_layout(coloraxis_showscale=False, width=400, height=400)
|
| 137 |
+
st.plotly_chart(fig_wave)
|
| 138 |
+
if st.button("Reset จุด"):
|
| 139 |
+
st.rerun()
|
| 140 |
|
| 141 |
st.markdown("""
|
| 142 |
**k-space 1 จุด = ข้อมูลของภาพทั้งภาพ และ ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด**
|
|
|
|
| 176 |
- **ตัวอย่างในภาพ MRI:** ขอบของอวัยวะ (Edges), รอยต่อระหว่างกระดูกกับไขสันหลัง, หรือรายละเอียดเส้นเลือดเส้นเล็กๆ
|
| 177 |
- **ตำแหน่งใน k-space:** ข้อมูลเหล่านี้จะกระจายตัวอยู่บริเวณ "ขอบนอก"
|
| 178 |
- **หน้าที่หลัก:** สร้าง "ความคมชัด (Resolution) และรายละเอียดเล็กๆ" ทำให้ภาพไม่เบลอ
|
|
|
|
|
|
|
|
|
|
| 179 |
""")
|
| 180 |
|
| 181 |
st.divider()
|
| 182 |
|
| 183 |
+
# --- ส่วน Filter ---
|
| 184 |
+
st.subheader("Interactive Filters: ปรับแต่งภาพ MRI จาก K-Space")
|
| 185 |
|
| 186 |
+
col_filter_ctrl, col_filter_res = st.columns([1, 2])
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
+
with col_filter_ctrl:
|
| 189 |
+
mode = st.radio("เลือกตัวกรอง", ["Low-pass Filter", "High-pass Filter"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
|
| 191 |
+
# ปรับปรุง Slide bar ตาม Logic ที่ต้องการ
|
| 192 |
+
if mode == "Low-pass Filter":
|
| 193 |
+
radius = st.slider("รัศมีข้อมูลความถี่ต่ำ (กลางภาพ)", 1, 112, 112)
|
| 194 |
+
st.write("ยิ่งลดค่า ภาพจะยิ่งเบลอแต่เห็นโครงร่างหลัก")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
else:
|
| 196 |
+
radius = st.slider("รัศมีการตัดข้อมูลตรงกลางออก", 0, 112, 0)
|
| 197 |
+
st.write("ยิ่งเพิ่มค่า คอนทราสต์จะหายไปเหลือแต่เส้นขอบ")
|
| 198 |
+
|
| 199 |
+
# สร้าง Mask
|
| 200 |
+
Y_m, X_m = np.ogrid[:224, :224]
|
| 201 |
+
dist = np.sqrt((X_m - 112)**2 + (Y_m - 112)**2)
|
| 202 |
+
mask = np.ones((224, 224))
|
| 203 |
+
|
| 204 |
+
if mode == "Low-pass Filter":
|
| 205 |
+
mask[dist > radius] = 0
|
| 206 |
+
else:
|
| 207 |
+
mask[dist < radius] = 0
|
| 208 |
+
|
| 209 |
+
filtered_k = kspace_raw * mask
|
| 210 |
+
mri_result = get_mri_image(filtered_k)
|
| 211 |
+
|
| 212 |
+
with col_filter_res:
|
| 213 |
+
c1, c2 = st.columns(2)
|
| 214 |
+
with c1:
|
| 215 |
+
st.image(np.log(np.abs(filtered_k)+1), caption="K-Space Filtered", use_container_width=True)
|
| 216 |
+
with c2:
|
| 217 |
+
st.image(mri_result/mri_result.max(), caption="MRI Result (224x224)", use_container_width=True)
|
| 218 |
+
|
| 219 |
+
# Toggle ซ่อนเนื้อหา
|
| 220 |
+
with st.expander("อ่านคำอธิบายเพิ่มเติมเกี่ยวกับ Filter"):
|
| 221 |
+
st.markdown("""
|
| 222 |
+
**Low-pass Filter (ตัวกรองปล่อยความถี่ต่ำผ่าน):**
|
| 223 |
+
- **ทำงานอย่างไร:** "อนุญาตให้เฉพาะข้อมูลตรงกลาง (ความถี่ต่ำ) ผ่านไปสร้างภาพได้ ส่วนข้อมูลขอบนอก (ความถี่สูง) ให้ทิ้งไป"
|
| 224 |
+
- **ผลลัพธ์ที่ได้:** เราจะได้ภาพที่มี "คอนทราสต์" ดูออกว่าเป็นอวัยวะอะไร แต่ภาพจะ "เบลอ" (Blurry) เพราะข้อมูลเส้นขอบถูกทิ้งไปแล้ว
|
| 225 |
+
|
| 226 |
+
**High-pass Filter (ตัวกรองปล่อยความถี่สูงผ่าน):**
|
| 227 |
+
- **ทำงานอย่างไร:** อนุญาตให้เฉพาะข้อมูลขอบนอก (ความถี่สูง) ผ่านไปได้ ส่วนข้อมูลตรงกลางทิ้ง
|
| 228 |
+
- **ผลลัพธ์ที่ได้:** ภาพจะสูญเสียคอนทราสต์ไปจนเกือบมืดสนิท แต่จะปรากฏ "เส้นขอบร่าง" (Outline) ของอวัยวะขึ้นมาอย่างคมชัด
|
| 229 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|