Nicha1234 commited on
Commit
0d9cd1c
·
verified ·
1 Parent(s): 6f0605e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -76
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
- k_display = np.log(1 + k_mag)
79
- k_min = np.min(k_display)
80
- k_max = np.max(k_display)
81
- if k_max == k_min:
82
- return np.zeros_like(k_display)
83
- k_display = (k_display - k_min) / (k_max - k_min)
84
- k_display = np.power(k_display, 0.6)
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
- ax.imshow(bg_image, cmap='gray', extent=[-112, 112, -112, 112])
 
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
- def draw_kspace_filling(current_step, total_steps, mode_type, real_bg):
 
194
  fig, ax = plt.subplots(figsize=(5, 6))
195
 
196
- if mode_type == "ข้อมูลจำลอง (Simulated)":
197
- ax.set_facecolor('black')
198
-
199
- ax.axhline(0, color='gray', lw=1, ls='--')
200
- ax.axvline(0, color='gray', lw=1, ls='--')
201
-
202
- y_vals = np.linspace(90, -90, total_steps)
203
-
204
- for i in range(current_step + 1):
205
- y = y_vals[i]
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
- current_line_idx = int(current_step * step_size)
225
- display_img[:current_line_idx + int(step_size), :] = real_bg[:current_line_idx + int(step_size), :]
226
 
227
- ax.imshow(display_img, cmap='gray', extent=[-112, 112, -112, 112], origin='upper', vmin=0, vmax=1)
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, 3, 1.5])
264
  with col_img_center:
265
- # ็คไล์รูปภาพใหม่
266
- if os.path.exists("kspaceaxis.png"):
267
- st.image("kspaceaxis.png", use_container_width=True)
268
- # เพิ่มคำอ��ิบายอ้างอิงด้านล่างภาพ
269
- st.markdown(
270
- """<p class="reference-text">ภาพดัดแปลงจาก: Clover Learning. (2024). <i>MRI k-space made easy - MRI physics explained</i> [Video]. YouTube.
271
- <a href="https://youtu.be/cfE6SL2Xj6o?t=68" target="_blank">https://youtu.be/cfE6SL2Xj6o?t=68</a></p>""",
272
- unsafe_allow_html=True
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
- _, col_ctrl1, col_ctrl2, _ = st.columns([0.5, 2.5, 2.5, 0.5])
310
- with col_ctrl1:
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, data_mode, kspace_bg_image), use_container_width=True)
 
 
 
 
 
329
 
330
  st.markdown("---")
331
  st.markdown("## 📍 1 จุดบน k-space")
332
 
 
333
  st.markdown("""
334
- **k-space 1 จุด = ข้อมูลของภาพทั้งภาพ** และ **ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด**
335
-
336
- **1 จุดใน k-space = แผ่นลวดลายคลื่น 1 แผ่น (2D Sinusoidal Wave)**
 
 
337
 
 
338
  1. **ตำแหน่งของจุด (พิกัด kx, ky) บอก "ความถี่" และ "ทิศทาง"**
339
- - **ระยะห่างจากศูนย์กลาง (ความถี่):** ยิ่งจุดนี้อยู่ไกลจากจุดศูนย์กลาง k-space มากเท่าไหร่ แผ่นลวดลายคลื่นก็จะยิ่ง **"ถี่"** (High frequency)
340
  - **มุมของจุด (ทิศทาง):** ตำแหน่งของจุดเมื่อเทียบกับจุดศูนย์กลาง จะเป็นตัวบอกว่าแผ่นลวดลายคลื่นนี้จะ **"เอียง"** ไปในทิศทางไหน
341
- 2. **ความสว่าของจุด (Amplitude / Magnitude) บอก "น้หนัก"**
342
- - ความสว่างของจุดไม่ได้แปลว่าภาพ MRI ตรงนั้นจะสว่าง แต่มันบอกถึง **"ปริมาณ (Weight)"**
 
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(np.log(filtered_k)), caption="ภาพ K-Space ที่ถูกเลือก", use_container_width=True)
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)