Update app.py
Browse files
app.py
CHANGED
|
@@ -45,7 +45,6 @@ st.markdown("""
|
|
| 45 |
background-color: #1E88E5;
|
| 46 |
color: white;
|
| 47 |
}
|
| 48 |
-
/* ปรับแต่งส่วนอ้างอิง */
|
| 49 |
.reference-text {
|
| 50 |
font-size: 14px;
|
| 51 |
color: #666;
|
|
@@ -73,16 +72,16 @@ def load_kspace_data():
|
|
| 73 |
|
| 74 |
kspace_raw = load_kspace_data()
|
| 75 |
|
|
|
|
| 76 |
def format_kspace_display(k_data):
|
| 77 |
k_mag = np.abs(k_data)
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
return k_display
|
| 86 |
|
| 87 |
kspace_bg_image = format_kspace_display(kspace_raw)
|
| 88 |
|
|
@@ -93,9 +92,65 @@ def get_image_from_plot(fig):
|
|
| 93 |
buf.seek(0)
|
| 94 |
return Image.open(buf)
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
def draw_kspace_point(kx, ky, bg_image):
|
| 97 |
fig, ax = plt.subplots(figsize=(4, 4))
|
| 98 |
-
|
|
|
|
| 99 |
ax.plot(kx, ky, 'ro', markersize=6)
|
| 100 |
ax.annotate('', xy=(kx, ky), xytext=(0, 0), arrowprops=dict(arrowstyle='->', color='yellow', lw=2))
|
| 101 |
ax.axhline(0, color='white', linewidth=0.5, linestyle='--')
|
|
@@ -131,7 +186,7 @@ def apply_filter(k_data, mode, radius):
|
|
| 131 |
|
| 132 |
def draw_filtered_kspace(filtered_k):
|
| 133 |
fig, ax = plt.subplots(figsize=(4, 4))
|
| 134 |
-
ax.imshow(format_kspace_display(filtered_k), cmap='gray')
|
| 135 |
ax.axis('off')
|
| 136 |
return get_image_from_plot(fig)
|
| 137 |
|
|
@@ -190,52 +245,32 @@ def draw_pulse_sequence(current_step, total_steps):
|
|
| 190 |
plt.tight_layout()
|
| 191 |
return get_image_from_plot(fig)
|
| 192 |
|
| 193 |
-
|
|
|
|
| 194 |
fig, ax = plt.subplots(figsize=(5, 6))
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
y =
|
| 206 |
-
if i == current_step:
|
| 207 |
-
ax.annotate('', xy=(100, y), xytext=(-100, y), arrowprops=dict(arrowstyle='->', color='red', lw=3))
|
| 208 |
-
else:
|
| 209 |
-
ax.annotate('', xy=(100, y), xytext=(-100, y), arrowprops=dict(arrowstyle='->', color='white', lw=1.5))
|
| 210 |
-
|
| 211 |
-
for i in range(current_step):
|
| 212 |
-
ax.plot([100, -100], [y_vals[i], y_vals[i+1]], color='gray', ls=':', lw=1)
|
| 213 |
-
|
| 214 |
-
ax.set_xlim(-112, 112)
|
| 215 |
-
ax.set_ylim(-112, 112)
|
| 216 |
-
|
| 217 |
-
else:
|
| 218 |
-
display_img = np.zeros((224, 224))
|
| 219 |
-
step_size = 224 / total_steps
|
| 220 |
-
|
| 221 |
-
if current_step >= total_steps - 1:
|
| 222 |
-
display_img = real_bg
|
| 223 |
else:
|
| 224 |
-
|
| 225 |
-
display_img[:current_line_idx + int(step_size), :] = real_bg[:current_line_idx + int(step_size), :]
|
| 226 |
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
current_y_center = 112 - int(current_step * step_size + step_size / 2)
|
| 230 |
-
ax.annotate('', xy=(112, current_y_center), xytext=(-112, current_y_center), arrowprops=dict(arrowstyle='->', color='red', lw=2))
|
| 231 |
-
|
| 232 |
-
ax.set_xlim(-112, 112)
|
| 233 |
-
ax.set_ylim(-112, 112)
|
| 234 |
-
|
| 235 |
-
ax.set_title("K-Space Trajectory", fontweight='bold', color='white' if mode_type=="ข้อมูลจำลอง (Simulated)" else 'black')
|
| 236 |
-
if mode_type == "ข้อมูลจำลอง (Simulated)":
|
| 237 |
-
fig.patch.set_facecolor('black')
|
| 238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
ax.axis('off')
|
| 240 |
return get_image_from_plot(fig)
|
| 241 |
|
|
@@ -260,19 +295,16 @@ with col_main:
|
|
| 260 |
ข้อมูลใน k-space มักจะถูกนำมาแสดงผลในรูปแบบตารางสี่เหลี่ยม (Grid) โดยมีแกนหลักคือ **kx (แนวนอน - Frequency)** และ **ky (แนวตั้ง - Phase)** จุดสำคัญคือ **แกน kx และ ky เหล่านี้ ไม่ได้บอกตำแหน่งพิกัดในภาพอวัยวะ** แต่มันคือแกนที่บอกถึงลักษณะของ **"ความถี่เชิงพื้นที่"** ที่เป็นคลื่น (Sinusoidal wave) ด้วยเหตุนี้ **จุดแต่ละจุดบน K-space จึงไม่ได้จับคู่แบบ 1 ต่อ 1 กับพิกเซลบนภาพ MRI** (เช่น จุดมุมซ้ายบนของ K-space ไม่ได้สร้างภาพมุมซ้ายบนของอวัยวะ)
|
| 261 |
""")
|
| 262 |
|
| 263 |
-
_, col_img_center, _ = st.columns([1.5,
|
| 264 |
with col_img_center:
|
| 265 |
-
#
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
)
|
| 274 |
-
else:
|
| 275 |
-
st.info("กรุณาอัปโหลดรูปภาพ kspaceaxis.png เข้าระบบ")
|
| 276 |
|
| 277 |
# ---------------------------------------------------------
|
| 278 |
# NEW: K-Space Trajectories
|
|
@@ -306,10 +338,8 @@ with col_main:
|
|
| 306 |
def reset_anim():
|
| 307 |
st.session_state.fill_step = 0
|
| 308 |
|
| 309 |
-
_,
|
| 310 |
-
with
|
| 311 |
-
data_mode = st.radio("เลือกรูปแบบ K-Space:", ["ข้อมูลจำลอง (Simulated)", "ข้อมูลจริง (kspace.mat)"])
|
| 312 |
-
with col_ctrl2:
|
| 313 |
st.write("ควบคุมการเติมบรรทัด (Gy):")
|
| 314 |
c1, c2, c3, c4 = st.columns(4)
|
| 315 |
c1.button("⏮ Reset", on_click=reset_anim)
|
|
@@ -325,21 +355,31 @@ with col_main:
|
|
| 325 |
with col_anim1:
|
| 326 |
st.image(draw_pulse_sequence(st.session_state.fill_step, total_anim_steps), use_container_width=True)
|
| 327 |
with col_anim2:
|
| 328 |
-
st.image(draw_kspace_filling(st.session_state.fill_step, total_anim_steps
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
|
| 330 |
st.markdown("---")
|
| 331 |
st.markdown("## 📍 1 จุดบน k-space")
|
| 332 |
|
|
|
|
| 333 |
st.markdown("""
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
|
|
|
|
|
|
| 337 |
|
|
|
|
| 338 |
1. **ตำแหน่งของจุด (พิกัด kx, ky) บอก "ความถี่" และ "ทิศทาง"**
|
| 339 |
-
- **ระยะห่างจากศูนย์กลาง (ความถี่):** ยิ่งจุดนี้อยู่ไกลจากจุดศูนย์กลาง k-space มากเท่าไหร่ แผ่นลวดลายคลื่นก็จะยิ่ง **"ถี่"** (High frequency)
|
| 340 |
- **มุมของจุด (ทิศทาง):** ตำแหน่งของจุดเมื่อเทียบกับจุดศูนย์กลาง จะเป็นตัวบอกว่าแผ่นลวดลายคลื่นนี้จะ **"เอียง"** ไปในทิศทางไหน
|
| 341 |
-
|
| 342 |
-
|
|
|
|
| 343 |
- **จุดสว่างมาก:** ภาพ MRI มีแผ่นลวดลายชนิดนี้เป็นส่วนประกอบอยู่ **เยอะมาก (มีความสำคัญต่อภาพสูง)**
|
| 344 |
- **จุดมืดหรือจาง:** ภาพ MRI แทบจะไม่มีลวดลายชนิดนี้ประกอบอยู่เลย
|
| 345 |
""")
|
|
@@ -358,6 +398,15 @@ with col_main:
|
|
| 358 |
with col_img2:
|
| 359 |
st.image(draw_wave(kx_val, ky_val), caption="แผ่นลวดลายคลื่น (2D Sinusoidal Wave)", use_container_width=True)
|
| 360 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
st.markdown("## 🔄 Inverse Fourier Transform")
|
| 362 |
st.markdown("""
|
| 363 |
เมื่อเก็บข้อมูลจนเต็มพื้นที่ k-space เราจะใช้กระบวนการทางคณิตศาสตร์ **2D Inverse Fourier Transform (2D-iFT)** เปลี่ยนข้อมูลความถี่กลับไปเป็นข้อมูลภาพ (Spatial Domain)
|
|
@@ -401,6 +450,6 @@ with col_main:
|
|
| 401 |
|
| 402 |
_, col_fimg1, col_fimg2, _ = st.columns([0.5, 2.5, 2.5, 0.5])
|
| 403 |
with col_fimg1:
|
| 404 |
-
st.image(draw_filtered_kspace(
|
| 405 |
with col_fimg2:
|
| 406 |
st.image(draw_mri(mri_result), caption="ภาพ MRI ผลลัพธ์", use_container_width=True)
|
|
|
|
| 45 |
background-color: #1E88E5;
|
| 46 |
color: white;
|
| 47 |
}
|
|
|
|
| 48 |
.reference-text {
|
| 49 |
font-size: 14px;
|
| 50 |
color: #666;
|
|
|
|
| 72 |
|
| 73 |
kspace_raw = load_kspace_data()
|
| 74 |
|
| 75 |
+
# ปรับแก้ความสว่างด้วย Log Transformation แบบ 100% สว่างคมชัด
|
| 76 |
def format_kspace_display(k_data):
|
| 77 |
k_mag = np.abs(k_data)
|
| 78 |
+
if np.max(k_mag) == 0:
|
| 79 |
+
return np.zeros_like(k_mag, dtype=np.uint8)
|
| 80 |
+
|
| 81 |
+
# สูตร Log Transformation เพื่อดึงความสว่าง
|
| 82 |
+
c = 255.0 / np.log(1 + np.max(k_mag))
|
| 83 |
+
log_img = c * np.log(1 + k_mag)
|
| 84 |
+
return log_img.astype(np.uint8)
|
|
|
|
| 85 |
|
| 86 |
kspace_bg_image = format_kspace_display(kspace_raw)
|
| 87 |
|
|
|
|
| 92 |
buf.seek(0)
|
| 93 |
return Image.open(buf)
|
| 94 |
|
| 95 |
+
# สร้างภาพแผนผังองค์ประกอบ K-Space ด้วยโค้ด Matplotlib (ไม่ต้องใช้รูปภาพภายนอก)
|
| 96 |
+
def draw_kspace_diagram():
|
| 97 |
+
fig, ax = plt.subplots(figsize=(10, 6))
|
| 98 |
+
|
| 99 |
+
c_center = '#E1B138' # สีเหลืองตรงกลาง
|
| 100 |
+
c_tb = '#4B8BDE' # สีฟ้าบนล่าง
|
| 101 |
+
c_lr = '#5EAA4D' # สีเขียวซ้ายขวา
|
| 102 |
+
|
| 103 |
+
# วาดโซนต่างๆ
|
| 104 |
+
ax.add_patch(plt.Rectangle((0.2, 0.2), 0.4, 0.6, facecolor=c_center, edgecolor='black', zorder=2))
|
| 105 |
+
ax.add_patch(plt.Rectangle((0.1, 0.2), 0.1, 0.6, facecolor=c_lr, edgecolor='black', zorder=2))
|
| 106 |
+
ax.add_patch(plt.Rectangle((0.6, 0.2), 0.1, 0.6, facecolor=c_lr, edgecolor='black', zorder=2))
|
| 107 |
+
ax.add_patch(plt.Rectangle((0.1, 0.8), 0.6, 0.1, facecolor=c_tb, edgecolor='black', zorder=2))
|
| 108 |
+
ax.add_patch(plt.Rectangle((0.1, 0.1), 0.6, 0.1, facecolor=c_tb, edgecolor='black', zorder=2))
|
| 109 |
+
|
| 110 |
+
# วาดเส้น Grid
|
| 111 |
+
for i in range(1, 10):
|
| 112 |
+
ax.plot([0.1, 0.7], [i*0.1, i*0.1], color='black', lw=0.5, alpha=0.5, zorder=3)
|
| 113 |
+
for i in range(1, 7):
|
| 114 |
+
ax.plot([0.1 + i*0.1, 0.1 + i*0.1], [0.1, 0.9], color='black', lw=0.5, alpha=0.5, zorder=3)
|
| 115 |
+
|
| 116 |
+
# ตัวหนังสืออธิบาย
|
| 117 |
+
ax.text(0.4, 0.95, "k-space data", ha='center', va='bottom', fontsize=16, fontweight='bold')
|
| 118 |
+
ax.text(0.4, 0.85, "HIGH $K_y$ FREQUENCY ZONE\nCAPTURES HORIZONTAL EDGES", ha='center', va='center', fontsize=9, fontweight='bold')
|
| 119 |
+
ax.text(0.4, 0.5, "LOW SPATIAL\nFREQUENCY ZONE\nCAPTURES BASIC CONTRAST", ha='center', va='center', fontsize=11, fontweight='bold')
|
| 120 |
+
|
| 121 |
+
# แกน X, Y
|
| 122 |
+
ax.annotate('', xy=(0.7, 0.05), xytext=(0.1, 0.05), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=2))
|
| 123 |
+
ax.text(0.4, 0.0, 'Frequency Encoding (horizontal axis) $K_x$', ha='center', va='top', fontsize=14)
|
| 124 |
+
ax.annotate('', xy=(0.05, 0.9), xytext=(0.05, 0.1), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=2))
|
| 125 |
+
ax.text(0.0, 0.5, 'Phase Encoding\n(vertical axis) $K_y$', ha='center', va='center', rotation=90, fontsize=14)
|
| 126 |
+
|
| 127 |
+
# วาดคลื่น 3 รูปแบบ
|
| 128 |
+
x_w = np.linspace(0, 1, 300)
|
| 129 |
+
|
| 130 |
+
# คลื่นบน (Yellow - Low freq)
|
| 131 |
+
y_w1 = np.sin(2 * np.pi * 2 * x_w) * 0.1 + 0.8
|
| 132 |
+
ax.plot(x_w*0.2 + 0.8, y_w1, color=c_center, lw=3)
|
| 133 |
+
ax.annotate('', xy=(0.78, 0.8), xytext=(0.55, 0.6), arrowprops=dict(arrowstyle='fancy', color=c_center, lw=2, connectionstyle="arc3,rad=-0.2"))
|
| 134 |
+
|
| 135 |
+
# คลื่นกลาง (Blue - High freq y)
|
| 136 |
+
y_w2 = np.sin(2 * np.pi * 12 * x_w) * 0.1 + 0.5
|
| 137 |
+
ax.plot(x_w*0.2 + 0.8, y_w2, color=c_tb, lw=3)
|
| 138 |
+
ax.annotate('', xy=(0.78, 0.55), xytext=(0.65, 0.85), arrowprops=dict(arrowstyle='fancy', color=c_tb, lw=2, connectionstyle="arc3,rad=0.2"))
|
| 139 |
+
|
| 140 |
+
# คลื่นล่าง (Green - High freq x)
|
| 141 |
+
y_w3 = np.sin(2 * np.pi * 8 * x_w) * 0.1 + 0.2
|
| 142 |
+
ax.plot(x_w*0.2 + 0.8, y_w3, color=c_lr, lw=3)
|
| 143 |
+
ax.annotate('', xy=(0.78, 0.2), xytext=(0.65, 0.3), arrowprops=dict(arrowstyle='fancy', color=c_lr, lw=2, connectionstyle="arc3,rad=0.2"))
|
| 144 |
+
|
| 145 |
+
ax.set_xlim(-0.05, 1.05)
|
| 146 |
+
ax.set_ylim(-0.05, 1.05)
|
| 147 |
+
ax.axis('off')
|
| 148 |
+
return get_image_from_plot(fig)
|
| 149 |
+
|
| 150 |
def draw_kspace_point(kx, ky, bg_image):
|
| 151 |
fig, ax = plt.subplots(figsize=(4, 4))
|
| 152 |
+
# vmin=0, vmax=255 ช่วยให้ภาพสว่างเต็มที่
|
| 153 |
+
ax.imshow(bg_image, cmap='gray', extent=[-112, 112, -112, 112], vmin=0, vmax=255)
|
| 154 |
ax.plot(kx, ky, 'ro', markersize=6)
|
| 155 |
ax.annotate('', xy=(kx, ky), xytext=(0, 0), arrowprops=dict(arrowstyle='->', color='yellow', lw=2))
|
| 156 |
ax.axhline(0, color='white', linewidth=0.5, linestyle='--')
|
|
|
|
| 186 |
|
| 187 |
def draw_filtered_kspace(filtered_k):
|
| 188 |
fig, ax = plt.subplots(figsize=(4, 4))
|
| 189 |
+
ax.imshow(format_kspace_display(filtered_k), cmap='gray', vmin=0, vmax=255)
|
| 190 |
ax.axis('off')
|
| 191 |
return get_image_from_plot(fig)
|
| 192 |
|
|
|
|
| 245 |
plt.tight_layout()
|
| 246 |
return get_image_from_plot(fig)
|
| 247 |
|
| 248 |
+
# วาด Trajectory แบบลูกศรซ้ายไปขวา (เฉพาะโหมดจำลอง)
|
| 249 |
+
def draw_kspace_filling(current_step, total_steps):
|
| 250 |
fig, ax = plt.subplots(figsize=(5, 6))
|
| 251 |
|
| 252 |
+
ax.set_facecolor('black')
|
| 253 |
+
ax.axhline(0, color='gray', lw=1, ls='--')
|
| 254 |
+
ax.axvline(0, color='gray', lw=1, ls='--')
|
| 255 |
+
|
| 256 |
+
y_vals = np.linspace(90, -90, total_steps)
|
| 257 |
+
|
| 258 |
+
for i in range(current_step + 1):
|
| 259 |
+
y = y_vals[i]
|
| 260 |
+
if i == current_step:
|
| 261 |
+
ax.annotate('', xy=(100, y), xytext=(-100, y), arrowprops=dict(arrowstyle='->', color='red', lw=3))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
else:
|
| 263 |
+
ax.annotate('', xy=(100, y), xytext=(-100, y), arrowprops=dict(arrowstyle='->', color='white', lw=1.5))
|
|
|
|
| 264 |
|
| 265 |
+
for i in range(current_step):
|
| 266 |
+
ax.plot([100, -100], [y_vals[i], y_vals[i+1]], color='gray', ls=':', lw=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
|
| 268 |
+
ax.set_xlim(-112, 112)
|
| 269 |
+
ax.set_ylim(-112, 112)
|
| 270 |
+
|
| 271 |
+
ax.set_title("K-Space Trajectory (Simulated)", fontweight='bold', color='white')
|
| 272 |
+
fig.patch.set_facecolor('black')
|
| 273 |
+
|
| 274 |
ax.axis('off')
|
| 275 |
return get_image_from_plot(fig)
|
| 276 |
|
|
|
|
| 295 |
ข้อมูลใน k-space มักจะถูกนำมาแสดงผลในรูปแบบตารางสี่เหลี่ยม (Grid) โดยมีแกนหลักคือ **kx (แนวนอน - Frequency)** และ **ky (แนวตั้ง - Phase)** จุดสำคัญคือ **แกน kx และ ky เหล่านี้ ไม่ได้บอกตำแหน่งพิกัดในภาพอวัยวะ** แต่มันคือแกนที่บอกถึงลักษณะของ **"ความถี่เชิงพื้นที่"** ที่เป็นคลื่น (Sinusoidal wave) ด้วยเหตุนี้ **จุดแต่ละจุดบน K-space จึงไม่ได้จับคู่แบบ 1 ต่อ 1 กับพิกเซลบนภาพ MRI** (เช่น จุดมุมซ้ายบนของ K-space ไม่ได้สร้างภาพมุมซ้ายบนของอวัยวะ)
|
| 296 |
""")
|
| 297 |
|
| 298 |
+
_, col_img_center, _ = st.columns([1.5, 4, 1.5])
|
| 299 |
with col_img_center:
|
| 300 |
+
# ใช้กราฟโค้ดแทนการดึงรูปภาพภายนอก
|
| 301 |
+
st.image(draw_kspace_diagram(), use_container_width=True)
|
| 302 |
+
# คำอ้างอิง
|
| 303 |
+
st.markdown(
|
| 304 |
+
"""<p class="reference-text">ภาพจำลองอ้างอิงจาก: Clover Learning. (2024). <i>MRI k-space made easy - MRI physics explained</i> [Video]. YouTube.
|
| 305 |
+
<a href="https://youtu.be/cfE6SL2Xj6o?t=68" target="_blank">https://youtu.be/cfE6SL2Xj6o?t=68</a></p>""",
|
| 306 |
+
unsafe_allow_html=True
|
| 307 |
+
)
|
|
|
|
|
|
|
|
|
|
| 308 |
|
| 309 |
# ---------------------------------------------------------
|
| 310 |
# NEW: K-Space Trajectories
|
|
|
|
| 338 |
def reset_anim():
|
| 339 |
st.session_state.fill_step = 0
|
| 340 |
|
| 341 |
+
_, col_ctrl, _ = st.columns([1, 4, 1])
|
| 342 |
+
with col_ctrl:
|
|
|
|
|
|
|
| 343 |
st.write("ควบคุมการเติมบรรทัด (Gy):")
|
| 344 |
c1, c2, c3, c4 = st.columns(4)
|
| 345 |
c1.button("⏮ Reset", on_click=reset_anim)
|
|
|
|
| 355 |
with col_anim1:
|
| 356 |
st.image(draw_pulse_sequence(st.session_state.fill_step, total_anim_steps), use_container_width=True)
|
| 357 |
with col_anim2:
|
| 358 |
+
st.image(draw_kspace_filling(st.session_state.fill_step, total_anim_steps), use_container_width=True)
|
| 359 |
+
|
| 360 |
+
st.markdown("""
|
| 361 |
+
---
|
| 362 |
+
**📌 หมายเหตุ:** หากพูดถึงจำนวนครั้ง Phase Encoding ที่มากขึ้น หรือจำนวนบรรทัดการเก็บข้อมูลมากขึ้น ภาพก็จะมีความละเอียด (Resolution) ที่มากขึ้น เช่น เพิ่มขึ้นเป็น 128 หรือ 256 บรรทัด เป็นต้น
|
| 363 |
+
""")
|
| 364 |
|
| 365 |
st.markdown("---")
|
| 366 |
st.markdown("## 📍 1 จุดบน k-space")
|
| 367 |
|
| 368 |
+
# Center Text
|
| 369 |
st.markdown("""
|
| 370 |
+
<div style="text-align: center; background-color: #f0f8ff; padding: 15px; border-radius: 10px; margin-bottom: 20px;">
|
| 371 |
+
<b>k-space 1 จุด = ข้อมูลของภาพทั้งภาพ และ ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด</b><br><br>
|
| 372 |
+
<b>1 จุดใน k-space = แผ่นลวดลายคลื่น 1 แผ่น (2D Sinusoidal Wave)</b>
|
| 373 |
+
</div>
|
| 374 |
+
""", unsafe_allow_html=True)
|
| 375 |
|
| 376 |
+
st.markdown("""
|
| 377 |
1. **ตำแหน่งของจุด (พิกัด kx, ky) บอก "ความถี่" และ "ทิศทาง"**
|
| 378 |
+
- **ระยะห่างจากศูนย์กลาง (ความถี่):** ยิ่งจุดนี้อยู่ไกลจากจุดศูนย์กลาง k-space มากเท่าไหร่ แผ่นลวดลายคลื่นก็จะยิ่ง **"ถี่"** (High frequency) ความยาวคลื่นจะสั้นลง มีความละเอียด เส้นจะห่างกันน้อยลง ทำให้ภาพมีดีเทลที่ชัดเจนขึ้น ในขณะที่ส่วนที่ใกล้จุดศูนย์กลาง คลื่นจะมีความถี่น้อย ความยาวคลื่นมาก มีลัก���ณะเป็นก้อนใหญ่ ๆ (สร้างรูปร่างโดยรวมของภาพ)
|
| 379 |
- **มุมของจุด (ทิศทาง):** ตำแหน่งของจุดเมื่อเทียบกับจุดศูนย์กลาง จะเป็นตัวบอกว่าแผ่นลวดลายคลื่นนี้จะ **"เอียง"** ไปในทิศทางไหน
|
| 380 |
+
- **จุดศูนย์กลางเป๊ะ (Origin):** จะเป็นคลื่นที่ไม่มีความถี่ ค่าความถี่ของคลื่นเป็นศูนย์ ดังนั้นตรงกลางจะไม่เห็นลักษณะรูปคลื่น
|
| 381 |
+
2. **ความสว่างของจุด (Amplitude / Magnitude) บอก "ปริมาณ/น้ำหนัก (Weight)"**
|
| 382 |
+
- ความสว่างของจุดไม่ได้แปลว่าภาพ MRI ตรงนั้นจะสว่าง แต่มันบอกถึง **"ปริมาณ/น้ำหนัก (Weight)"**
|
| 383 |
- **จุดสว่างมาก:** ภาพ MRI มีแผ่นลวดลายชนิดนี้เป็นส่วนประกอบอยู่ **เยอะมาก (มีความสำคัญต่อภาพสูง)**
|
| 384 |
- **จุดมืดหรือจาง:** ภาพ MRI แทบจะไม่มีลวดลายชนิดนี้ประกอบอยู่เลย
|
| 385 |
""")
|
|
|
|
| 398 |
with col_img2:
|
| 399 |
st.image(draw_wave(kx_val, ky_val), caption="แผ่นลวดลายคลื่น (2D Sinusoidal Wave)", use_container_width=True)
|
| 400 |
|
| 401 |
+
# ส่วน Toggle สำหรับสังเกตคลื่น
|
| 402 |
+
with st.expander("🔍 สังเกตลักษณะคลื่น (คลิกเพื่อดูคำอธิบายเพิ่มเติม)"):
|
| 403 |
+
st.markdown("""
|
| 404 |
+
**ลองปรับเลื่อนพิกัดเพื่อสังเกตความเปลี่ยนแปลง:**
|
| 405 |
+
- **ยิ่งจุดอยู่ใกล้ศูนย์กลาง:** คลื่นจะมีความถี่น้อย ความยาวคลื่นมาก มีลักษณะเป็นก้อนใหญ่ ๆ (แสดงถึงรูปร่างโดยรวม)
|
| 406 |
+
- **ยิ่งจุดอยู่ไกลศูนย์กลาง:** คลื่นจะมีความถี่สูง ความยาวคลื่นสั้นลง เส้นห่างกันน้อยลง มีความละเอียดสูง (แสดงส่วนที่เป็นดีเทลหรือเส้นขอบ)
|
| 407 |
+
- **เมื่อจุดอยู่ตรงกลางเป๊ะ (kx=0, ky=0):** จะไม่มีรูปคลื่นปรากฏให้เห็นเลย เพราะค่าความถี่ของคลื่นเป็นศูนย์
|
| 408 |
+
""")
|
| 409 |
+
|
| 410 |
st.markdown("## 🔄 Inverse Fourier Transform")
|
| 411 |
st.markdown("""
|
| 412 |
เมื่อเก็บข้อมูลจนเต็มพื้นที่ k-space เราจะใช้กระบวนการทางคณิตศาสตร์ **2D Inverse Fourier Transform (2D-iFT)** เปลี่ยนข้อมูลความถี่กลับไปเป็นข้อมูลภาพ (Spatial Domain)
|
|
|
|
| 450 |
|
| 451 |
_, col_fimg1, col_fimg2, _ = st.columns([0.5, 2.5, 2.5, 0.5])
|
| 452 |
with col_fimg1:
|
| 453 |
+
st.image(draw_filtered_kspace(filtered_k), caption="ภาพ K-Space ที่ถูกเลือก", use_container_width=True)
|
| 454 |
with col_fimg2:
|
| 455 |
st.image(draw_mri(mri_result), caption="ภาพ MRI ผลลัพธ์", use_container_width=True)
|