Nicha1234 commited on
Commit
70b2410
·
verified ·
1 Parent(s): 5e750d9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +147 -143
app.py CHANGED
@@ -1,9 +1,14 @@
1
  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
  # --- 1. ตั้งค่า CSS เพื่อปรับขนาดตัวอักษรและจัดกลาง ---
9
  st.set_page_config(layout="wide", page_title="K-Space to MRI")
@@ -20,8 +25,15 @@ st.markdown("""
20
  color: #1E88E5;
21
  margin-bottom: 30px;
22
  }
23
- h1, h2, h3 {
24
  color: #0D47A1;
 
 
 
 
 
 
 
25
  }
26
  </style>
27
  """, unsafe_allow_html=True)
@@ -44,25 +56,100 @@ def load_kspace_data():
44
 
45
  kspace_raw = load_kspace_data()
46
 
47
- def get_mri_image(k_data):
48
- img = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(k_data)))
49
- return np.abs(img)
50
-
51
  def format_kspace_display(k_data):
52
  k_mag = np.abs(k_data)
53
  k_display = np.log(1 + k_mag)
54
  k_min = np.min(k_display)
55
  k_max = np.max(k_display)
56
  if k_max == k_min:
57
- return np.zeros_like(k_display, dtype=np.uint8)
58
- k_display = (k_display - k_min) / (k_max - k_min) * 255.0
59
- k_display = np.power(k_display / 255.0, 0.6) * 255.0
60
- return k_display.astype(np.uint8) # บังคับเป็น uint8 แก้ปัญหาภาพหาย
61
 
62
- # เตรียมภาพ K-space พื้นานว้แสดงผล
63
  kspace_bg_image = format_kspace_display(kspace_raw)
64
 
65
- # --- 3. ส่หน้เว็บ ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
  st.markdown('<p class="main-title">K-space to MRI image</p>', unsafe_allow_html=True)
68
 
@@ -71,93 +158,22 @@ st.markdown("""
71
  เมื่อเรานำผู้ป่วยเข้าเครื่อง MRI และส่งคลื่น RF เข้าไปกระตุ้น เกิดเป็นสัญญาณ MR Signal ที่ได้มานั้นจะยังไม่ได้ออกมาเป็นภาพอวัยวะ แต่จะถูกนำไปเก็บรวบรวมไว้ในพื้นที่ที่เรียกว่า "K-space" ซึ่งเป็นพื้นที่ที่จะเก็บข้อมูลดิบแบบสองมิติ ก่อนจะนำไปผ่านกระบวนการทางคณิตศาสตร์ที่เรียกว่า Fourier Transform ให้ได้มาซึ่งภาพ MRI ซึ่งข้อมูลใน K-Sapce ถูกเก็บในรูปแบบ ความถี่เชิงพื้นที่ (Spatial Frequency) พิกัดในตารางของ K-space ถูกสร้างขึ้นจากการทำงานของสนามแม่เหล็กเกรเดียนท์ 2 แกน ได้แก่ Gx (ทำหน้าที่เข้ารหัสในแนวความถี่ Frequency encoding) และ Gy (ทำหน้าที่เข้ารหัสในแนวเฟส Phase encoding) ซึ่งเกรเดียนท์ทั้งสองตัวนี้กำหนดว่า สัญญาณจากโปรตอนที่มีความถี่และเฟสจำเพาะเจาะจงที่ต่างกันนั้น จะต้องถูกนำไปจัดเก็บไว้ตรงจุดไหนในพิกัดของ K-space
72
  """)
73
 
74
- st.header("องค์ประกอบของ K-Space")
75
-
76
- col_ax1, col_ax2 = st.columns([1, 1])
77
- with col_ax1:
78
- # สร้างแกนสีเขียวด้วย Matplotlib การันตีว่าติด 100%
79
- fig_axis, ax_axis = plt.subplots(figsize=(5, 5))
80
- for i in range(-5, 6):
81
- ax_axis.plot([-5, 5], [i, i], color='#8BC34A', lw=1)
82
- ax_axis.plot([i, i], [-5, 5], color='#8BC34A', lw=1)
83
- ax_axis.plot([-5.5, 5.5], [0, 0], color='#1565C0', lw=3)
84
- ax_axis.plot([0, 0], [-5.5, 5.5], color='#1565C0', lw=3)
85
-
86
- ax_axis.annotate('kx\n(Frequency)', xy=(5, 0), xytext=(3.5, 0.5), color='red', weight='bold', fontsize=12, arrowprops=dict(arrowstyle="->", color='red', lw=2))
87
- ax_axis.annotate('ky\n(Phase)', xy=(0, 5), xytext=(0.5, 3.5), color='red', weight='bold', fontsize=12, arrowprops=dict(arrowstyle="->", color='red', lw=2))
88
-
89
- ax_axis.set_xlim(-6, 6)
90
- ax_axis.set_ylim(-6, 6)
91
- ax_axis.axis('off')
92
- st.pyplot(fig_axis)
93
-
94
- with col_ax2:
95
- st.markdown("""
96
  ข้อมูลใน k-space มักจะถูกนำมาแสดงผลในรูปแบบตารางสี่เหลี่ยม (Grid) โดยมีแกนหลักคือ kx (แนวนอน - Frequency) และ ky (แนวตั้ง - Phase) แต่จุดสำคัญคือ แกน kx และ ky เหล่านี้ ไม่ได้บอกตำแหน่งพิกัด ในภาพ แต่มันคือแกนที่บอกถึง ลักษณะของ "ความถี่เชิงพื้นที่ (Spatial Frequencies)" ซึ่งเป็นคลื่นความถี่ Sinusoidal wave ด้วยเหตุนี้ จุดแต่ละจุดบนพิกัด (kx, ky) ใน k-space จึง ไม่ได้จับคู่แบบ 1 ต่อ 1 กับพิกเซล (x, y) บนภาพ MRI (ไม่ได้แปลว่าจุดมุมซ้ายบนใน k-space จะสร้างภาพมุมซ้ายบนของ ภาพอวัยวะ)
97
- """)
98
-
99
- st.divider()
100
-
101
- st.header("1 จุดบน k-space")
102
-
103
- # --- Interactive Part: กลับมาใช้ Slider ควบคุมบภาพจิง ---
104
- if 'kx_val' not in st.session_state:
105
- st.session_state.kx_val = 15
106
- if 'ky_val' not in st.session_state:
107
- st.session_state.ky_val = 20
108
-
109
- def reset_point():
110
- st.session_state.kx_val = 0
111
- st.session_state.ky_val = 0
112
-
113
- col_w1, col_w2, col_w3 = st.columns([1, 1, 1])
114
- with col_w1:
115
- st.markdown("**ปรับตำแหน่งพิกัด:**")
116
- st.slider("ตำแหน่งแกน kx (แนวนอน)", -112, 111, key='kx_val')
117
- st.slider("ตำแหน่งแกน ky (แนวตั้ง)", -112, 111, key='ky_val')
118
- st.button("Reset จุด", on_click=reset_point)
119
-
120
- with col_w2:
121
- fig_pt, ax_pt = plt.subplots(figsize=(5, 5))
122
- size = 224
123
- # ใช้ภาพ K-space จริงเป็น Background
124
- ax_pt.imshow(kspace_bg_image, cmap='gray', extent=[-size//2, size//2, -size//2, size//2])
125
-
126
- kx = st.session_state.kx_val
127
- ky = st.session_state.ky_val
128
-
129
- # วาดจุดแดงและลูกศรสีเหลือง
130
- ax_pt.plot(kx, ky, 'ro', markersize=8)
131
- ax_pt.annotate('', xy=(kx, ky), xytext=(0, 0),
132
- arrowprops=dict(arrowstyle='->', color='yellow', lw=2))
133
-
134
- ax_pt.axhline(0, color='white', linewidth=0.5, linestyle='--')
135
- ax_pt.axvline(0, color='white', linewidth=0.5, linestyle='--')
136
-
137
- ax_pt.set_xlim(-size//2, size//2)
138
- ax_pt.set_ylim(-size//2, size//2)
139
- ax_pt.set_title(f"พิกัด K-Space (kx={kx}, ky={ky})")
140
- ax_pt.axis('off')
141
- st.pyplot(fig_pt)
142
-
143
- with col_w3:
144
- x = np.linspace(-size//2, size//2, size)
145
- y = np.linspace(-size//2, size//2, size)
146
- X, Y = np.meshgrid(x, y)
147
-
148
- freq_x = kx / size
149
- freq_y = ky / size
150
- wave = np.cos(2 * np.pi * (freq_x * X + freq_y * Y))
151
-
152
- fig_wave, ax_wave = plt.subplots(figsize=(5, 5))
153
- ax_wave.imshow(wave, cmap='gray')
154
- ax_wave.set_title("แผ่นลวดลายคลื่น (2D Sinusoidal Wave)")
155
- ax_wave.axis('off')
156
- st.pyplot(fig_wave)
157
 
 
158
 
159
  st.markdown("""
160
  **k-space 1 จุด = ข้อมูลของภาพทั้งภาพ และ ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด**
 
161
  **1 จุดใน k-space = แผ่นลวดลายคลื่น 1 แผ่น (2D Sinusoidal Wave)**
162
 
163
  1. **ตำแหน่งของจุด (พิกัด kx, ky) บอก "ความถี่" และ "ทิศทาง"**
@@ -169,17 +185,30 @@ st.markdown("""
169
  - **จุดมืดหรือจาง:** แปลว่าภาพ MRI ภาพนี้ แทบจะไม่มีลวดลายชนิดนี้ประกอบอยู่เลย
170
  """)
171
 
172
- st.divider()
 
 
 
 
 
 
 
173
 
174
- st.header("Inverse Fourier Transform")
 
 
 
 
 
 
175
  st.markdown("""
176
  เมื่อเก็บข้อมูลจนเต็มพื้นที่ k-space เราจะใช้กระบวนการทางคณิตศาสตร์ 2D Inverse Fourier Transform (2D-iFT) ในการเปลี่ยนข้อมูลความถี่กลับไปเป็นข้อมูลในเชิงพื้นที่ (Spatial Domain) ภาพ MRI เกิดจากการนำ "คลื่นความถี่ (Sinusoidal spatial waves)" จากทุกจุดใน k-space มาซ้อนทับกัน คลื่นที่มีเฟสตรงกันจะรวมตัวกันแบบเสริมฤทธิ์ (Constructive interference) สร้างเป็นพิกัดที่สว่าง และคลื่นที่มีเฟสตรงข้ามจะหักล้างกัน (Destructive interference) กลายเป็นพื้นที่สีดำ
177
  โดยต้องอาศัยข้อมูลจากหลายจุดมาซ้อนทับกัน และเกิดการแทรกสอดตามคุณสมบัติของคลื่น
178
- o บริเวณไหนที่เป็นเนื้อเยื่อจริง คลื่นจะเสริมกันทำให้เกิด จุดสว่าง
179
- o บริเวณไหนที่เป็นช่องว่าง คลื่นจะหักล��างกันทำให้เกิด จุดมืด
180
  """)
181
 
182
- st.header("ความถี่เชิงพื้นที่ (Spatial Frequency) คือ")
183
  st.markdown("""
184
  ในทางสัญญาณภาพ (Spatial Frequency) ความถี่ไม่ได้หมายถึงความเร็วของเวลา แต่หมายถึง "อัตราการเปลี่ยนแปลงความเข้มของแสงในพื้นที่หนึ่งๆ"
185
 
@@ -191,60 +220,35 @@ st.markdown("""
191
 
192
  2. **ความถี่เชิงพื้นที่สูง (High Spatial Frequency)**
193
  - **คืออะไร:** พื้นที่ที่ความสว่าง เปลี่ยนแบบฉับพลันและรวดเร็ว ภายในระยะทางสั้นๆ เช่น จากขาว ตัดเป็นดำสนิททันที (เหมือนลายทางแคบๆ ที่สลับสีถี่ๆ)
194
- - **ตัวอย่างในภาพ MRI:** ขอบของอวัยวะ (Edges), รอยต่อระว่างกะดูกกับไขสันหลัง, หรือรายละเอียดเส้นเลือดเส้นเล็กๆ
195
  - **ตำแหน่งใน k-space:** ข้อมูลเหล่านี้จะกระจายตัวอยู่บริเวณ "ขอบนอก"
196
  - **หน้าที่หลัก:** สร้าง "ความคมชัด (Resolution) และรายละเอียดเล็กๆ" ทำให้ภาพไม่เบลอ
197
 
198
  ซึ่งจะให้ผู้เรียนได้ลองปรับหน้าตาของภาพ K-Space แล้วเปรียบเทียบความแตกต่างด้วยการปรับ High-pass filter และ Low-pass filter เพื่อดูลักษณะและความสำคัญของข้อมูลบริเวณกลางและขอบนอกของ K-space
199
  เมื่อเราจำแนกข้อมูลใน k-space ออกเป็นความถี่ต่ำ (ตรงกลาง) และความถี่สูง (ขอบนอก) ได้แล้ว เราสามารถเลือก "หยิบ" หรือ "ทิ้ง" ข้อมูลบางส่วนเพื่อดูผลลัพธ์ได้ เรียกว่าการใช้ตัวกรอง (Filter)
200
- """)
201
 
202
- st.divider()
 
 
 
 
 
 
203
 
204
- # --- ส่วน Filter ---
205
- st.subheader("Interactive Filters")
206
 
207
- col_filter_ctrl, col_filter_res = st.columns([1, 2])
208
-
209
- with col_filter_ctrl:
210
- mode = st.radio("เลือกตัวกรอง", ["Low-pass Filter", "High-pass Filter"])
211
-
212
- if mode == "Low-pass Filter":
213
- radius = st.slider("ปรับระดับความถี่ที่ยอมให้ผ่าน", 1, 112, 112)
214
- else:
215
- radius = st.slider("ปรับระดับการตัดข้อมูลส่วนกลาง", 0, 112, 0)
216
-
217
- Y_m, X_m = np.ogrid[:224, :224]
218
- dist = np.sqrt((X_m - 112)**2 + (Y_m - 112)**2)
219
- mask = np.ones((224, 224))
220
 
221
  if mode == "Low-pass Filter":
222
- mask[dist > radius] = 0
223
  else:
224
- mask[dist < radius] = 0
225
-
226
- filtered_k = kspace_raw * mask
227
- mri_result = get_mri_image(filtered_k)
228
 
229
- with col_filter_res:
230
- c1, c2 = st.columns(2)
231
- with c1:
232
- st.image(format_kspace_display(filtered_k), caption="K-Space Map", use_container_width=True)
233
- with c2:
234
- mri_disp = mri_result / (mri_result.max() + 1e-8) # ป้องกันการหารด้วยศูนย์
235
- st.image(mri_disp, caption="MRI Result", use_container_width=True, clamp=True)
236
 
237
- # เอา Toggle ออก กางเนื้อหาเต็มๆ
238
- st.markdown("---")
239
- if mode == "Low-pass Filter":
240
- st.markdown("""
241
- **Low-pass Filter (ตัวกรองปล่อยควมถี่ต่ำ่าน):**
242
- - **ทำงานอย่างไร:** "อนุญาตให้เฉพาะข้อมูลตรงกลาง (ความถี่ต่ำ) ผ่านไปสร้างภาพได้ ส่วนข้อมูลขอบนอก (ความถี่สูง) ให้ทิ้งไป"
243
- - **ผลลัพธ์ที่ได้:** เราจะได้ภาพที่มี "คอนทราสต์" ดูออกว่าเป็นอวัยวะอะไร แต่ภาพจะ "เบลอ" (Blurry) เพราะข้อมูลเส้นขอบถูกทิ้งไปแล้ว
244
- """)
245
- else:
246
- st.markdown("""
247
- **High-pass Filter (ตัวกรองปล่อยความถี่สูงผ่าน):**
248
- - **ทำงานอย่างไร:** อนุญาตให้เฉพาะข้อมูลขอบนอก (ความถี่สูง) ผ่านไปได้ ส่วนข้อมูลตรงกลางทิ้ง
249
- - **ผลลัพธ์ที่ได้:** ภาพจะสูญเสียคอนทราสต์ไปจนเกือบมืดสนิท แต่จะปรากฏ "เส้นขอบร่าง" (Outline) ของอวัยวะขึ้นมาอย่างคมชัด
250
- """)
 
1
  import streamlit as st
2
  import numpy as np
3
  import scipy.io
 
4
  import zipfile
5
  import os
6
+ import io
7
+
8
+ # สำคัญมาก! บังคับให้ Matplotlib ทำงานแบบ Background (Agg) เพื่อแก้ปัญหาภาพสั่นและลดบั๊กใน Streamlit
9
+ import matplotlib
10
+ matplotlib.use('Agg')
11
+ import matplotlib.pyplot as plt
12
 
13
  # --- 1. ตั้งค่า CSS เพื่อปรับขนาดตัวอักษรและจัดกลาง ---
14
  st.set_page_config(layout="wide", page_title="K-Space to MRI")
 
25
  color: #1E88E5;
26
  margin-bottom: 30px;
27
  }
28
+ h2 {
29
  color: #0D47A1;
30
+ border-bottom: 2px solid #1E88E5;
31
+ padding-bottom: 10px;
32
+ margin-top: 40px;
33
+ }
34
+ h3 {
35
+ color: #1565C0;
36
+ margin-top: 20px;
37
  }
38
  </style>
39
  """, unsafe_allow_html=True)
 
56
 
57
  kspace_raw = load_kspace_data()
58
 
 
 
 
 
59
  def format_kspace_display(k_data):
60
  k_mag = np.abs(k_data)
61
  k_display = np.log(1 + k_mag)
62
  k_min = np.min(k_display)
63
  k_max = np.max(k_display)
64
  if k_max == k_min:
65
+ return np.zeros_like(k_display)
66
+ k_display = (k_display - k_min) / (k_max - k_min)
67
+ k_display = np.power(k_display, 0.6)
68
+ return k_display
69
 
70
+ # เตรียมภาพพื้นหลังตั้งต้นเพื่อลดเวลคำนวณซ
71
  kspace_bg_image = format_kspace_display(kspace_raw)
72
 
73
+ # --- ฟังก์ชันวาดกราฟเป็น Image Buffer (แก้ปัญหาภาพ่นและภษาไทย) ---
74
+ def draw_kspace_point(kx, ky, bg_image):
75
+ fig, ax = plt.subplots(figsize=(5, 5))
76
+ ax.imshow(bg_image, cmap='gray', extent=[-112, 112, -112, 112])
77
+ ax.plot(kx, ky, 'ro', markersize=8)
78
+ # วาดเส้นลูกศรชี้ทิศทาง
79
+ ax.annotate('', xy=(kx, ky), xytext=(0, 0),
80
+ arrowprops=dict(arrowstyle='->', color='yellow', lw=2))
81
+ ax.axhline(0, color='white', linewidth=0.5, linestyle='--')
82
+ ax.axvline(0, color='white', linewidth=0.5, linestyle='--')
83
+ ax.set_xlim(-112, 112)
84
+ ax.set_ylim(-112, 112)
85
+ ax.axis('off')
86
+
87
+ plt.tight_layout()
88
+ buf = io.BytesIO()
89
+ plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, dpi=100)
90
+ plt.close(fig)
91
+ return buf.getvalue()
92
+
93
+ def draw_wave(kx, ky):
94
+ fig, ax = plt.subplots(figsize=(5, 5))
95
+ x = np.linspace(-112, 112, 224)
96
+ # สลับ Y ให้สอดคล้องกับพิกัดภาพเพื่อทิศทางลูกศรที่ถูกต้องเป๊ะๆ
97
+ y = np.linspace(112, -112, 224)
98
+ X, Y = np.meshgrid(x, y)
99
+ freq_x = kx / 224.0
100
+ freq_y = ky / 224.0
101
+ wave = np.cos(2 * np.pi * (freq_x * X + freq_y * Y))
102
+
103
+ ax.imshow(wave, cmap='gray', extent=[-112, 112, -112, 112])
104
+ ax.axis('off')
105
+
106
+ plt.tight_layout()
107
+ buf = io.BytesIO()
108
+ plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, dpi=100)
109
+ plt.close(fig)
110
+ return buf.getvalue()
111
+
112
+ def apply_filter(k_data, mode, radius):
113
+ Y, X = np.ogrid[:224, :224]
114
+ dist = np.sqrt((X - 112)**2 + (Y - 112)**2)
115
+ mask = np.ones((224, 224))
116
+ if mode == "Low-pass Filter":
117
+ mask[dist > radius] = 0
118
+ else:
119
+ mask[dist < radius] = 0
120
+
121
+ filtered_k = k_data * mask
122
+ img = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(filtered_k)))
123
+ img_mag = np.abs(img)
124
+ return filtered_k, img_mag
125
+
126
+ def draw_filtered_kspace(filtered_k):
127
+ fig, ax = plt.subplots(figsize=(5, 5))
128
+ k_disp = format_kspace_display(filtered_k)
129
+ ax.imshow(k_disp, cmap='gray')
130
+ ax.axis('off')
131
+
132
+ plt.tight_layout()
133
+ buf = io.BytesIO()
134
+ plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, dpi=100)
135
+ plt.close(fig)
136
+ return buf.getvalue()
137
+
138
+ def draw_mri(mri_result):
139
+ fig, ax = plt.subplots(figsize=(5, 5))
140
+ m_max = np.max(mri_result)
141
+ m_disp = mri_result / (m_max + 1e-8)
142
+ ax.imshow(m_disp, cmap='gray')
143
+ ax.axis('off')
144
+
145
+ plt.tight_layout()
146
+ buf = io.BytesIO()
147
+ plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, dpi=100)
148
+ plt.close(fig)
149
+ return buf.getvalue()
150
+
151
+
152
+ # --- 3. ส่วนเนื้อหาเว็บ (เรียงเนื้อหามาก่อน Interactive เสมอ) ---
153
 
154
  st.markdown('<p class="main-title">K-space to MRI image</p>', unsafe_allow_html=True)
155
 
 
158
  เมื่อเรานำผู้ป่วยเข้าเครื่อง MRI และส่งคลื่น RF เข้าไปกระตุ้น เกิดเป็นสัญญาณ MR Signal ที่ได้มานั้นจะยังไม่ได้ออกมาเป็นภาพอวัยวะ แต่จะถูกนำไปเก็บรวบรวมไว้ในพื้นที่ที่เรียกว่า "K-space" ซึ่งเป็นพื้นที่ที่จะเก็บข้อมูลดิบแบบสองมิติ ก่อนจะนำไปผ่านกระบวนการทางคณิตศาสตร์ที่เรียกว่า Fourier Transform ให้ได้มาซึ่งภาพ MRI ซึ่งข้อมูลใน K-Sapce ถูกเก็บในรูปแบบ ความถี่เชิงพื้นที่ (Spatial Frequency) พิกัดในตารางของ K-space ถูกสร้างขึ้นจากการทำงานของสนามแม่เหล็กเกรเดียนท์ 2 แกน ได้แก่ Gx (ทำหน้าที่เข้ารหัสในแนวความถี่ Frequency encoding) และ Gy (ทำหน้าที่เข้ารหัสในแนวเฟส Phase encoding) ซึ่งเกรเดียนท์ทั้งสองตัวนี้กำหนดว่า สัญญาณจากโปรตอนที่มีความถี่และเฟสจำเพาะเจาะจงที่ต่างกันนั้น จะต้องถูกนำไปจัดเก็บไว้ตรงจุดไหนในพิกัดของ K-space
159
  """)
160
 
161
+ st.markdown("## 🧩 องค์ประกอบของ K-Space")
162
+ st.markdown("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  ข้อมูลใน k-space มักจะถูกนำมาแสดงผลในรูปแบบตารางสี่เหลี่ยม (Grid) โดยมีแกนหลักคือ kx (แนวนอน - Frequency) และ ky (แนวตั้ง - Phase) แต่จุดสำคัญคือ แกน kx และ ky เหล่านี้ ไม่ได้บอกตำแหน่งพิกัด ในภาพ แต่มันคือแกนที่บอกถึง ลักษณะของ "ความถี่เชิงพื้นที่ (Spatial Frequencies)" ซึ่งเป็นคลื่นความถี่ Sinusoidal wave ด้วยเหตุนี้ จุดแต่ละจุดบนพิกัด (kx, ky) ใน k-space จึง ไม่ได้จับคู่แบบ 1 ต่อ 1 กับพิกเซล (x, y) บนภาพ MRI (ไม่ได้แปลว่าจุดมุมซ้ายบนใน k-space จะสร้างภาพมุมซ้ายบนของ ภาพอวัยวะ)
164
+ """)
165
+
166
+ # แทรกรูปภาพไฟล์ที่คุณให้มาโดยตรง
167
+ if os.path.exists("Screenshot 2026-05-07 205051.jpg"):
168
+ st.image("Screenshot 2026-05-07 205051.jpg", width=500)
169
+ else:
170
+ st.info("กรุณาอัปโหลดรูปภาพ Screenshot 2026-05-07 205051.jpg เข้ามาในระบบ")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
+ st.markdown("## 📍 1 จุดบน k-space")
173
 
174
  st.markdown("""
175
  **k-space 1 จุด = ข้อมูลของภาพทั้งภาพ และ ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด**
176
+
177
  **1 จุดใน k-space = แผ่นลวดลายคลื่น 1 แผ่น (2D Sinusoidal Wave)**
178
 
179
  1. **ตำแหน่งของจุด (พิกัด kx, ky) บอก "ความถี่" และ "ทิศทาง"**
 
185
  - **จุดมืดหรือจาง:** แปลว่าภาพ MRI ภาพนี้ แทบจะไม่มีลวดลายชนิดนี้ประกอบอยู่เลย
186
  """)
187
 
188
+ # --- ส่วน Interactive พิกัด K-space ---
189
+ st.markdown("### 🎛️ ลองปรับตำแหน่งของจุด K-Space เพื่อดูคลื่นความถี่")
190
+
191
+ col_slide1, col_slide2 = st.columns(2)
192
+ with col_slide1:
193
+ kx_val = st.slider("พิกัด kx (แนวนอน)", -112, 111, 15)
194
+ with col_slide2:
195
+ ky_val = st.slider("พิกัด ky (แนวตั้ง)", -112, 111, 20)
196
 
197
+ col_img1, col_img2 = st.columns(2)
198
+ with col_img1:
199
+ st.image(draw_kspace_point(kx_val, ky_val, kspace_bg_image), caption="พิกัด K-Space (มีเส้นบอกทิศทาง)", use_container_width=True)
200
+ with col_img2:
201
+ st.image(draw_wave(kx_val, ky_val), caption="แผ่นลวดลายคลื่น (2D Sinusoidal Wave)", use_container_width=True)
202
+
203
+ st.markdown("## 🔄 Inverse Fourier Transform")
204
  st.markdown("""
205
  เมื่อเก็บข้อมูลจนเต็มพื้นที่ k-space เราจะใช้กระบวนการทางคณิตศาสตร์ 2D Inverse Fourier Transform (2D-iFT) ในการเปลี่ยนข้อมูลความถี่กลับไปเป็นข้อมูลในเชิงพื้นที่ (Spatial Domain) ภาพ MRI เกิดจากการนำ "คลื่นความถี่ (Sinusoidal spatial waves)" จากทุกจุดใน k-space มาซ้อนทับกัน คลื่นที่มีเฟสตรงกันจะรวมตัวกันแบบเสริมฤทธิ์ (Constructive interference) สร้างเป็นพิกัดที่สว่าง และคลื่นที่มีเฟสตรงข้ามจะหักล้างกัน (Destructive interference) กลายเป็นพื้นที่สีดำ
206
  โดยต้องอาศัยข้อมูลจากหลายจุดมาซ้อนทับกัน และเกิดการแทรกสอดตามคุณสมบัติของคลื่น
207
+ - บริเวณไหนที่เป็นเนื้อเยื่อจริง คลื่นจะเสริมกันทำให้เกิด จุดสว่าง
208
+ - บริเวณไหนที่เป็นช่องว่าง คลื่นจะหักลางกันทำให้เกิด จุดมืด
209
  """)
210
 
211
+ st.markdown("## 📊 ความถี่เชิงพื้นที่ (Spatial Frequency) คือ")
212
  st.markdown("""
213
  ในทางสัญญาณภาพ (Spatial Frequency) ความถี่ไม่ได้หมายถึงความเร็วของเวลา แต่หมายถึง "อัตราการเปลี่ยนแปลงความเข้มของแสงในพื้นที่หนึ่งๆ"
214
 
 
220
 
221
  2. **ความถี่เชิงพื้นที่สูง (High Spatial Frequency)**
222
  - **คืออะไร:** พื้นที่ที่ความสว่าง เปลี่ยนแบบฉับพลันและรวดเร็ว ภายในระยะทางสั้นๆ เช่น จากขาว ตัดเป็นดำสนิททันที (เหมือนลายทางแคบๆ ที่สลับสีถี่ๆ)
223
+ - **ตัวอย่างในภาพ MRI:** ขอบของอวัยวะ (Edges), หรือรายละเอียดเส้นเลือดเส้นเล็กๆ
224
  - **ตำแหน่งใน k-space:** ข้อมูลเหล่านี้จะกระจายตัวอยู่บริเวณ "ขอบนอก"
225
  - **หน้าที่หลัก:** สร้าง "ความคมชัด (Resolution) และรายละเอียดเล็กๆ" ทำให้ภาพไม่เบลอ
226
 
227
  ซึ่งจะให้ผู้เรียนได้ลองปรับหน้าตาของภาพ K-Space แล้วเปรียบเทียบความแตกต่างด้วยการปรับ High-pass filter และ Low-pass filter เพื่อดูลักษณะและความสำคัญของข้อมูลบริเวณกลางและขอบนอกของ K-space
228
  เมื่อเราจำแนกข้อมูลใน k-space ออกเป็นความถี่ต่ำ (ตรงกลาง) และความถี่สูง (ขอบนอก) ได้แล้ว เราสามารถเลือก "หยิบ" หรือ "ทิ้ง" ข้อมูลบางส่วนเพื่อดูผลลัพธ์ได้ เรียกว่าการใช้ตัวกรอง (Filter)
 
229
 
230
+ - **Low-pass Filter (ตัวกรองปล่อยความถี่ต่ำผ่าน):**
231
+ - **ทำงานอย่างไร:** "อนุญาตให้เฉพาะข้อมูลตรงกลาง (ความถี่ต่ำ) ผ่านไปสร้างภาพได้ ส่วนข้อมูลขอบนอก (ความถี่สูง) ให้ทิ้งไป"
232
+ - **ผลลัพธ์ที่ได้:** เราจะได้ภาพที่มี "คอนทราสต์" ดูออกว่าเป็นอวัยวะอะไร แต่ภาพจะ "เบลอ" (Blurry) เพราะข้อมูลเส้นขอบถูกทิ้งไปแล้ว
233
+ - **High-pass Filter (ตัวกรองปล่อยความถี่สูงผ่าน):**
234
+ - **ทำงานอย่างไร:** อนุญาตให้เฉพาะข้อมูลขอบนอก (ความถี่สูง) ผ่านไปได้ ส่วนข้อมูลตรงกลางทิ้ง
235
+ - **ผลลัพธ์ที่ได้:** ภาพจะสูญเสียคอนทราสต์ไปจนเกือบมืดสนิท แต่จะปรากฏ "เส้นขอบร่าง" (Outline) ของอวัยวะขึ้นมาอย่างคมชัด
236
+ """)
237
 
238
+ # --- ส่วน Interactive ตัวกรอง K-space ---
239
+ st.markdown("### 🎛️ ลองปรับตัวกรอง (Interactive Filter)")
240
 
241
+ mode = st.radio("เลือกตัวกรอง:", ["Low-pass Filter", "High-pass Filter"], horizontal=True)
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
  if mode == "Low-pass Filter":
244
+ radius = st.slider("ปรับระดับความถี่ที่ยอมให้ผ่าน (รัศมีจากตรงกลาง)", 1, 112, 112)
245
  else:
246
+ radius = st.slider("ปรับระดับการตัดข้อมูลส่วนกลาง (รัศมีจากตรงกลาง)", 0, 112, 0)
 
 
 
247
 
248
+ filtered_k, mri_result = apply_filter(kspace_raw, mode, radius)
 
 
 
 
 
 
249
 
250
+ col_f1, col_f2 = st.columns(2)
251
+ with col_f1:
252
+ st.image(draw_filtered_kspace(filtered_k), caption="ภาพ K-Space ที่ถูกตัวกรอง", use_container_width=True)
253
+ with col_f2:
254
+ st.image(draw_mri(mri_result), caption="ภพ MRI ลลัพธ์", use_container_width=True)