Nicha1234 commited on
Commit
5d796d4
·
verified ·
1 Parent(s): 7a2e3a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -95
app.py CHANGED
@@ -1,61 +1,86 @@
1
  import streamlit as st
2
  import numpy as np
3
  import scipy.io
4
- from PIL import Image, ImageDraw
5
- import matplotlib.pyplot as plt
6
  from streamlit_image_coordinates import streamlit_image_coordinates
7
 
8
- # 1. ตั้งค่าหน้าเว็บให้ดูเป็นมืออาชีพ
9
- st.set_page_config(layout="centered", page_title="K-Space Interactive Learning")
10
 
11
- # ปรับ CSS เพื่อความสวยงาม (ฟอนต์ใหญ่, หัวข้อชัดเจน)
12
  st.markdown("""
13
  <style>
14
- .main { font-size: 18px; }
15
- h1 { color: #1E88E5; font-size: 42px !important; border-bottom: 2px solid #1E88E5; padding-bottom: 10px; }
16
- h2 { color: #1565C0; font-size: 32px !important; margin-top: 30px !important; }
17
- h3 { color: #0D47A1; font-size: 26px !important; }
18
- .stText, .stMarkdown p { font-size: 18px !important; line-height: 1.6; }
19
- .stButton>button { width: 100%; border-radius: 10px; height: 3em; background-color: #f0f2f6; }
 
 
 
 
 
 
20
  </style>
21
  """, unsafe_allow_html=True)
22
 
23
- # 2. โหลดข้อมู
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  @st.cache_data
25
- def load_kspace():
26
  try:
27
  mat = scipy.io.loadmat('kspace.mat')
28
- # ตรวจสอบชื่อตัวแปร (IM หรือ kspace)
29
- key = 'IM' if 'IM' in mat else ([k for k in mat.keys() if not k.startswith('__')][0])
30
  data = mat[key]
31
- # บังคับขนาด 224x224
32
  if data.shape != (224, 224):
33
- # ถ้าเป็น 4D หรือใหญ่กว่า ให้เอาแค่ slice แรกและ crop
34
- if data.ndim > 2: data = data[..., 0]
35
- h, w = data.shape
36
  data = data[(h-224)//2:(h+224)//2, (w-224)//2:(w+224)//2]
37
  return data
38
  except:
39
- # สร้างข้อมูลจำลองถ้าไม่พบไฟล์
40
  y, x = np.ogrid[-112:112, -112:112]
41
  img = np.zeros((224, 224))
42
- img[(x/70)**2 + (y/90)**2 <= 1] = 0.5
43
- img[(x/40)**2 + (y/50)**2 <= 1] = 0.8
 
44
  return np.fft.fftshift(np.fft.fft2(img))
45
 
46
- k_data = load_kspace()
47
 
48
- # ฟังก์ชันจัดการรูปภาพ
49
- def to_pil(arr, is_mag=True):
50
- if is_mag:
51
- arr = np.log(1 + np.abs(arr))
52
  else:
53
- arr = np.abs(arr)
54
- norm = (arr - arr.min()) / (arr.max() - arr.min() + 1e-8) * 255
 
55
  return Image.fromarray(norm.astype(np.uint8)).convert("RGB")
56
 
57
  # ==========================================
58
- # อหา (Content)
59
  # ==========================================
60
 
61
  st.title("K-space to MRI image")
@@ -64,110 +89,99 @@ st.header("K-space คือ")
64
  st.write("""เมื่อเรานำผู้ป่วยเข้าเครื่อง MRI และส่งคลื่น RF เข้าไปกระตุ้น เกิดเป็นสัญญาณ MR Signal ที่ได้มานั้นจะยังไม่ได้ออกมาเป็นภาพอวัยวะ แต่จะถูกนำไปเก็บรวบรวมไว้ในพื้นที่ที่เรียกว่า "K-space" ซึ่งเป็นพื้นที่ที่จะเก็บข้อมูลดิบแบบสองมิติ ก่อนจะนำไปผ่านกระบวนการทางคฺณิตศาสตร์ที่เรียกว่า Fourier Transform ให้ได้มาซึ่งภาพ MRI ซึ่งข้อมูลใน K-Sapce ถูกเก็บในรูปแบบ ความถี่เชิงพื้นที่ (Spatial Frequency) พิกัดในตารางของ K-space ถูกสร้างขึ้นจากการทำงานของสนามแม่เหล็กเกรเดียนท์ 2 แกน ได้แก่ Gx (ทำหน้าที่เข้ารหัสในแนวความถี่ Frequency encoding) และ Gy (ทำหน้าที่เข้ารหัสในแนวเฟส Phase encoding) ซึ่งเกรเดียนท์ทั้งสองตัวนี้กำหนดว่า สัญญาณจากโปรตอนที่มีความถี่และเฟสจำเพาะเจาะจงที่ต่างกันนั้น จะต้องถูกนำไปจัดเก็บไว้ตรงจุดไหนในพิกัดของ K-space""")
65
 
66
  st.header("องค์ประกอบของ K-Space")
67
- # ส่วขอการแสดงรูปภาพองค์ประกอบ (ดึงจาไฟล์ที่คุณไว้)
68
- try:
69
- # สมมติว่าไฟล์รูปองค์ประกอบคือ Notes_260516_022203_1.jpg
70
- st.image("Notes_260516_022203_1.jpg", caption="องค์ประกอบของ K-Space", use_container_width=True)
71
- except:
72
- st.info("💡 [พื้นที่สำหรับรูปองค์ประกอบของ K-Space]")
73
 
74
  st.write("""ข้อมูลใน k-space มักจะถูกนำมาแสดงผลในรูปแบบตารางสี่เหลี่ยม (Grid) โดยมีแกนหลักคือ kx (แนวนอน - Frequency) และ ky (แนวตั้ง - Phase) แต่จุดสำคัญคือ แกน kx และ ky เหล่านี้ ไม่ได้บอกตำแหน่งพิกัด ในภาพ แต่มันคือแกนที่บอกถึงลักษณะของ "ความถี่เชิงพื้นที่ (Spatial Frequencies)" ซึ่งเป็นคลื่นความถี่ Sinusoidal wave ด้วยเหตุนี้ จุดแต่ละจุดบนพิกัด (kx, ky) ใน k-space จึง ไม่ได้จับคู่แบบ 1 ต่อ 1 กับพิกเซล (x, y) บนภาพ MRI (ไม่ได้แปลว่าจุดมุมซ้ายบนใน k-space จะสร้างภาพมุมซ้ายบนของภาพอวัยวะ)""")
75
 
76
- # ------------------------------------------
77
- # ส่วนกดจุด (Interactive 1)
78
- # ------------------------------------------
79
  st.subheader("1 จุดบน k-space")
80
 
81
- if st.button("🔄 Reset จุดเลือก"):
82
- st.session_state.pop("coords", None)
 
 
83
  st.rerun()
84
 
85
- col_k, col_w = st.columns(2)
86
 
87
- with col_k:
88
- st.markdown("**เลือกพิกัน K-Space:**")
89
- base_img = to_pil(k_data)
90
 
91
- # วาเส้นทิศทางและุดถ้ามีการคลิก
92
- coords = streamlit_image_coordinates(base_img, key="kspace_map")
93
 
94
- if coords:
95
- st.session_state["coords"] = coords
96
- px, py = coords['x'], coords['y']
97
- draw = ImageDraw.Draw(base_img)
98
- # วาดเส้นทิศทางสีแดงจากศูนย์กลาง (112, 112)
99
- draw.line((112, 112, px, py), fill="#FF0000", width=2)
100
- # วาดจุดแดง
101
- r = 4
102
- draw.ellipse((px-r, py-r, px+r, py+r), fill="#FF0000", outline="white")
103
  kx, ky = px - 112, py - 112
104
- # อัปเตรูปที่มีเส้น
105
- st.image(base_img, width=300)
 
 
 
106
  else:
107
  kx, ky = 0, 0
108
- st.image(base_img, width=300)
109
 
110
- with col_w:
111
- st.markdown(f"**Sinusoidal Wave (kx:{kx}, ky:{ky}):**")
112
  Y, X = np.mgrid[-112:112, -112:112]
 
113
  wave = np.cos(2 * np.pi * (kx * X / 224 + ky * Y / 224))
114
- wave_img = ((wave + 1) * 127.5).astype(np.uint8)
115
- st.image(Image.fromarray(wave_img), width=300)
116
 
117
- st.write("""k-space 1 จุด = ข้อมูลของภาพทั้งภาพ และ ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด
118
- 1 จุดน k-space = แผ่นลวดลายคลื่น 1 แผ่น (2D Sinusoidal Wave)
119
- **1. ตำแหน่งของจุด (พิกัด kx, ky) บอก "ความถี่" และ "ทิศทาง"** - **ระยะ่างจากศูย์กลาง (ความถี):** ยิ่งจุดนี้อยู่ไากจุดศูนย์กลาง k-space มาก่าไหร่ แผ่นลวดลายคลื่นก็จะยิ่ง "" หรือมีเส้นที่แคบมากขึ้นเานั้น (High frequency)
120
- - **มุมของจุด (ทิศทาง):** หน่งของจุดอเทียบกับจุศูนย์กลาง จะเ็นตับอกว่าแผ่นลวดลยคลื่นี้จะ "เอียง" ไปในทิศทางไหน (ตั้ง อน หรือเฉียงี่งศา)
121
- **2. ความสว่างของจุด (Amplitude / Magnitude) บอก "น้ำหนัก"** ความสว่างของจุดใน k-space ไ่ได้แปลว่าภาพ MRI ตรงนจะสว่างมัคือการบอ "ปริมา (Weight)"
122
- - **จุดสว่างมา:** แปลว่าภาพ MRI ภาพนี้ มีลวดลายชนิดนี้็นส่วนประกอบอยู่ เยอะมาก (มีความสำคัญต่อภาพสูง)
123
- - **จุดมืดหรือจาง:** แปลว่าภาพ MRI ภาพนี้ แทบจะไม่มีลวดลายชนิดนี้ประกอบอยู่เลย""")
124
 
125
  st.header("Inverse Fourier Transform")
126
  st.write("""เมื่อเก็บข้อมูลจนเต็มพื้นที่ k-space เราจะใช้กระบวนการทางคณิตศาสตร์ 2D Inverse Fourier Transform (2D-iFT) ในการเปลี่ยนข้อมูลความถี่กลับไปเป็นข้อมูลในเชิงพื้นที่ (Spatial Domain) ภาพ MRI เกิดจากการนำ "คลื่นความถี่ (Sinusoidal spatial waves)" จากทุกจุดใน k-space มาซ้อนทับกัน คลื่นที่มีเฟสตรงกันจะรวมตัวกันแบบเสริมฤทธิ์ (Constructive interference) สร้างเป็นพิกัดที่สว่าง และคลื่นที่มีเฟสตรงข้ามจะหักล้างกัน (Destructive interference) กลายเป็นพื้นที่สีดำ โดยต้องอาศัยข้อมูลจากหลายจุดมาซ้อนทับกัน และเกิดการแทรกสอดตามคุณสมบัติของคลื่น บริเวณไหนที่เป็นเนื้อเยื่อจริง คลื่นจะเสริมกันทำให้เกิด จุดสว่าง บริเวณไหนที่เป็นช่องว่าง คลื่นจะหักล้างกันทำให้เกิด จุดมืด""")
127
 
128
  st.header("ความถี่เชิงพื้นที่ (Spatial Frequency) คือ")
129
  st.write("""ในทางสัญญาณภาพ (Spatial Frequency) ความถี่ไม่ได้หมายถึงความเร็วของเวลา แต่หมายถึง "อัตราการเปลี่ยนแปลงความเข้มของแสงในพื้นที่หนึ่งๆ"
130
- **1. ความถี่เชิงพื้นที่ต่ำ (Low Spatial Frequency)** คืออะไร: พื้นที่ที่สีหรือความสว่าง "ค่อยๆ เปลี่ยน" หรือ "เหมือนเดิมเป็นบริเวณกว้าง" (เหมือนคลื่นลูกใหญ่ๆ ที่ขยับช้าๆ)
131
  ตัวอย่างในภาพ MRI: บริเวณเนื้อเยื่อก้อนใหญ่ๆ เช่น เนื้อตับ หรือเนื้อสมอง ที่มีสีเทาโทนเดียวกันกินพื้นที่กว้าง
132
  ตำแหน่งใน k-space: ข้อมูลเหล่านี้จะรวมตัวกันอยู่บริเวณ "ตรงกลาง"
133
  หน้าที่หลัก: สร้าง "รูปร่างรวมๆ และคอนทราสต์ (Contrast)" ให้เรารู้ว่านี่คือก้อนอวัยวะอะไร
134
 
135
- **2. ความถี่เชิงพื้นที่สูง (High Spatial Frequency)** คืออะไร: พื้นที่ท��่ความสว่างเปลี่ยนแบบฉับพลันและรวดเร็วภายในระยะทางสั้นๆ เช่น จากขาวตัดเป็นดำสนิททันที (เหมือนลายทางแคบๆ ที่สลับสีถี่ๆ)
136
  ตัวอย่างในภาพ MRI: ขอบของอวัยวะ (Edges), รอยต่อระหว่างกระดูกกับไขสันหลัง, หรือรายละเอียดเส้นเลือดเส้นเล็กๆ
137
  ตำแหน่งใน k-space: ข้อมูลเหล่านี้จะกระจายตัวอยู่บริเวณ "ขอบนอก"
138
  หน้าที่หลัก: สร้าง "ความคมชัด (Resolution) และรายละเอียดเล็กๆ" ทำให้ภาพไม่เบลอ""")
139
 
140
- # ------------------------------------------
141
- # ส่วนตัวกรอง (Interactive 2)
142
- # ------------------------------------------
143
- st.write("""เมื่อเราจำแนกข้อมูลใน k-space ออกเป็นความถี่ต่ำ (ตรงกลาง) และความถี่สูง (ขอบนอก) ได้แล้ว เราสามารถเลือก "หยิบ" หรือ "ทิ้ง" ข้อมูลบางส่วนเพื่อดูผลลัพธ์ได้ เรียกว่าการใช้ตัวกรอง (Filter)""")
144
 
145
- f_type = st.select_slider("เลือกประเภทตัวกรอง:", options=["Low-pass Filter", "High-pass Filter"])
146
 
147
- Y_d, X_d = np.ogrid[-112:112, -112:112]
148
- dist = np.sqrt(X_d**2 + Y_d**2)
149
 
150
  if f_type == "Low-pass Filter":
151
- st.info('**Low-pass Filter:** อนุญาให้เฉพาะข้อมูลตรงาง (ความถี่ต่ำ) ผ่านไปสร้างภาพได้')
152
- rad = st.slider("ปับรัศ (Radius):", 0, 112, 112)
153
- mask = dist <= rad
154
- res_msg = 'ผลลัพธ์: ได้ภาพที่มีคอนทราสต์ชัด แต่ภาพจะ **เบลอ (Blurry)**'
 
155
  else:
156
- st.info('**High-pass Filter:** อนุญาให้เฉพาะข้มูบนอก (ความถี่สูง) ผ่านไปได้')
157
- rad = st.slider("ับรัศ (Radius):", 0, 112, 0)
158
- mask = dist >= rad
159
- res_msg = 'ผลลัพธ์: สูญเสียคอนทราสต์ แต่ปรากฏ **เส้นขอบร่าง (Outline)** คมชัด'
 
160
 
161
- # คำนวณภาพผลลัพธ์ (Reconstruction)
162
  filtered_k = k_data * mask
163
- mri_result = np.abs(np.fft.ifft2(np.fft.ifftshift(filtered_k)))
164
-
165
  col_f1, col_f2 = st.columns(2)
 
166
  with col_f1:
167
- st.image(to_pil(filtered_k), caption="K-Space ที่ผ่านตัวกรอง", width=300)
168
  with col_f2:
169
- # แสดงภาพสมองภาพเดียว
170
- res_pil = (mri_result - mri_result.min()) / (mri_result.max() - mri_result.min() + 1e-8) * 255
171
- st.image(Image.fromarray(res_pil.astype(np.uint8)), caption="ภาพ MRI ผลลัพธ์", width=300)
172
 
173
- st.success(res_msg)
 
1
  import streamlit as st
2
  import numpy as np
3
  import scipy.io
4
+ from PIL import Image, ImageDraw, ImageFont
 
5
  from streamlit_image_coordinates import streamlit_image_coordinates
6
 
7
+ # 1. ตั้งค่าเลย์เอาต์หน้าเว็บและการออกแบบ (CSS)
8
+ st.set_page_config(layout="centered", page_title="MRI K-Space Learning Tool")
9
 
 
10
  st.markdown("""
11
  <style>
12
+ /* ปรับขนาดฟอนต์เนื้อหา */
13
+ .main .block-container { max-width: 900px; }
14
+ p, li { font-size: 19px !important; line-height: 1.7 !important; color: #333; }
15
+
16
+ /* ปรับหัวข้อ */
17
+ h1 { color: #0047AB; font-size: 45px !important; font-weight: 800 !important; border-bottom: 3px solid #0047AB; padding-bottom: 10px; margin-bottom: 30px !important; }
18
+ h2 { color: #0056b3; font-size: 32px !important; font-weight: 700 !important; margin-top: 40px !important; border-left: 8px solid #0056b3; padding-left: 15px; }
19
+ h3 { color: #007bff; font-size: 26px !important; font-weight: 600 !important; margin-top: 30px !important; }
20
+
21
+ /* ปรับปุ่มและ Slider */
22
+ .stButton>button { background-color: #f8f9fa; border: 1px solid #ddd; border-radius: 8px; font-weight: bold; }
23
+ .stSlider { padding-top: 20px; }
24
  </style>
25
  """, unsafe_allow_html=True)
26
 
27
+ # 2. ฟังก์ชันสรางรูปงค์ประกอบ K-Space ขึ้นาใหม่ (แทนรปเดิม)
28
+ def create_kspace_diagram():
29
+ img = Image.new('RGB', (600, 400), color=(255, 255, 255))
30
+ draw = ImageDraw.Draw(img)
31
+ center = (300, 200)
32
+
33
+ # วาด Grid พื้นหลัง
34
+ for i in range(0, 601, 40): draw.line([(i, 0), (i, 400)], fill=(240, 240, 240))
35
+ for i in range(0, 401, 40): draw.line([(0, i), (600, i)], fill=(240, 240, 240))
36
+
37
+ # วาดแกน
38
+ draw.line([(50, 200), (550, 200)], fill=(0, 0, 0), width=3) # แกน kx
39
+ draw.line([(300, 50), (300, 350)], fill=(0, 0, 0), width=3) # แกน ky
40
+
41
+ # วาดพื้นที่ Low Frequency (กลาง) และ High Frequency (ขอบ)
42
+ draw.ellipse([240, 140, 360, 260], outline=(0, 100, 255), width=2) # วงใน
43
+ draw.text((310, 160), "Low Frequency\n(Contrast)", fill=(0, 80, 200))
44
+
45
+ draw.text((450, 210), "kx (Frequency)", fill=(0, 0, 0))
46
+ draw.text((310, 60), "ky (Phase)", fill=(0, 0, 0))
47
+ draw.text((420, 100), "High Frequency\n(Edges/Details)", fill=(150, 0, 0))
48
+
49
+ return img
50
+
51
+ # 3. จัดการข้อมูล K-Space
52
  @st.cache_data
53
+ def load_data():
54
  try:
55
  mat = scipy.io.loadmat('kspace.mat')
56
+ key = 'kspace' if 'kspace' in mat else ([k for k in mat.keys() if not k.startswith('__')][0])
 
57
  data = mat[key]
 
58
  if data.shape != (224, 224):
59
+ h, w = data.shape[:2]
 
 
60
  data = data[(h-224)//2:(h+224)//2, (w-224)//2:(w+224)//2]
61
  return data
62
  except:
63
+ # สร้างข้อมูลสมองจำลอง (Synthetic MRI)
64
  y, x = np.ogrid[-112:112, -112:112]
65
  img = np.zeros((224, 224))
66
+ img[(x/80)**2 + (y/100)**2 <= 1] = 0.5
67
+ img[(x/50)**2 + (y/70)**2 <= 1] = 0.8
68
+ img[x**2 + (y-30)**2 <= 15**2] = 1.0
69
  return np.fft.fftshift(np.fft.fft2(img))
70
 
71
+ k_data = load_data()
72
 
73
+ def process_image(arr, is_kspace=True):
74
+ if is_kspace:
75
+ res = np.log(1 + np.abs(arr))
 
76
  else:
77
+ # ป้องกันภาพซ้อน 4: ใช้ ifftshift ก่อน ifft2
78
+ res = np.abs(np.fft.ifft2(np.fft.ifftshift(arr)))
79
+ norm = (res - res.min()) / (res.max() - res.min() + 1e-8) * 255
80
  return Image.fromarray(norm.astype(np.uint8)).convert("RGB")
81
 
82
  # ==========================================
83
+ # เริมต้น้าเว็บ (Main Content)
84
  # ==========================================
85
 
86
  st.title("K-space to MRI image")
 
89
  st.write("""เมื่อเรานำผู้ป่วยเข้าเครื่อง MRI และส่งคลื่น RF เข้าไปกระตุ้น เกิดเป็นสัญญาณ MR Signal ที่ได้มานั้นจะยังไม่ได้ออกมาเป็นภาพอวัยวะ แต่จะถูกนำไปเก็บรวบรวมไว้ในพื้นที่ที่เรียกว่า "K-space" ซึ่งเป็นพื้นที่ที่จะเก็บข้อมูลดิบแบบสองมิติ ก่อนจะนำไปผ่านกระบวนการทางคฺณิตศาสตร์ที่เรียกว่า Fourier Transform ให้ได้มาซึ่งภาพ MRI ซึ่งข้อมูลใน K-Sapce ถูกเก็บในรูปแบบ ความถี่เชิงพื้นที่ (Spatial Frequency) พิกัดในตารางของ K-space ถูกสร้างขึ้นจากการทำงานของสนามแม่เหล็กเกรเดียนท์ 2 แกน ได้แก่ Gx (ทำหน้าที่เข้ารหัสในแนวความถี่ Frequency encoding) และ Gy (ทำหน้าที่เข้ารหัสในแนวเฟส Phase encoding) ซึ่งเกรเดียนท์ทั้งสองตัวนี้กำหนดว่า สัญญาณจากโปรตอนที่มีความถี่และเฟสจำเพาะเจาะจงที่ต่างกันนั้น จะต้องถูกนำไปจัดเก็บไว้ตรงจุดไหนในพิกัดของ K-space""")
90
 
91
  st.header("องค์ประกอบของ K-Space")
92
+ st.image(create_kspace_diagram(), caption="แผผังแสดงองค์ประกอบของ K-Space (นความถี่แเฟส)", width=600)
 
 
 
 
 
93
 
94
  st.write("""ข้อมูลใน k-space มักจะถูกนำมาแสดงผลในรูปแบบตารางสี่เหลี่ยม (Grid) โดยมีแกนหลักคือ kx (แนวนอน - Frequency) และ ky (แนวตั้ง - Phase) แต่จุดสำคัญคือ แกน kx และ ky เหล่านี้ ไม่ได้บอกตำแหน่งพิกัด ในภาพ แต่มันคือแกนที่บอกถึงลักษณะของ "ความถี่เชิงพื้นที่ (Spatial Frequencies)" ซึ่งเป็นคลื่นความถี่ Sinusoidal wave ด้วยเหตุนี้ จุดแต่ละจุดบนพิกัด (kx, ky) ใน k-space จึง ไม่ได้จับคู่แบบ 1 ต่อ 1 กับพิกเซล (x, y) บนภาพ MRI (ไม่ได้แปลว่าจุดมุมซ้ายบนใน k-space จะสร้างภาพมุมซ้ายบนของภาพอวัยวะ)""")
95
 
96
+ # --- Interactive 1 ---
 
 
97
  st.subheader("1 จุดบน k-space")
98
 
99
+ # ปุ่ม Reset
100
+ if st.button("🔄 Reset พิกัด"):
101
+ st.session_state.kx = 112
102
+ st.session_state.ky = 112
103
  st.rerun()
104
 
105
+ col_pick, col_wave = st.columns([1, 1])
106
 
107
+ with col_pick:
108
+ st.markdown("**คลิกเลือกจุน K-Space:**")
109
+ k_img = process_image(k_data)
110
 
111
+ # ดักับการคลิก
112
+ pos = streamlit_image_coordinates(k_img, key="coords")
113
 
114
+ if pos:
115
+ px, py = pos['x'], pos['y']
 
 
 
 
 
 
 
116
  kx, ky = px - 112, py - 112
117
+ # วาดเส้นทิศทางและจุดแดง
118
+ draw = ImageDraw.Draw(k_img)
119
+ draw.line((112, 112, px, py), fill="red", width=2)
120
+ draw.ellipse((px-4, py-4, px+4, py+4), fill="red", outline="white")
121
+ st.image(k_img, width=300)
122
  else:
123
  kx, ky = 0, 0
124
+ st.image(k_img, width=300)
125
 
126
+ with col_wave:
127
+ st.markdown(f"**Sinusoidal Wave (kx: {kx}, ky: {ky})**")
128
  Y, X = np.mgrid[-112:112, -112:112]
129
+ # คำนวณคลื่น
130
  wave = np.cos(2 * np.pi * (kx * X / 224 + ky * Y / 224))
131
+ wave_norm = ((wave + 1) * 127.5).astype(np.uint8)
132
+ st.image(Image.fromarray(wave_norm), width=300)
133
 
134
+ st.write("""**k-space 1 จุด = ข้อมูลของภาพทั้งภาพ และ ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด** 1 จุดใน k-space = แผ่นลวดลายคลื่น 1 แผ่น (2D Sinusoidal Wave)
135
+ 1. **ตำแหน่งของจุด (พิกัด kx, ky) บอก "ความถี่" และ "ทิศทาง"** - ระยะห่างจากศูย์กลาง (ความถี่): ยิ่งจุดนี้อยู่ไกลจากจุดศูนย์กลาง k-space มากเท่าไหร่ แผ่นลวดลายคลื่นก็จะยิ่ง "ถี่" หรือมีเส้นทีแคบมากขึ้เท��านั้น (High frequency)
136
+ - มุมของจุด (ทิศทาง): ตำแหน่งของจุดเมื่เทีับจุดศูนย์กลาง จะป็นตัวบอกว่าแผ่นลวดลายคลื่นนี้จะ "เอยง" ไปในทิศทางไห (ตั้อน หรือเฉียงกี่องศา)
137
+ 2. **ควาสว่างของจุด (Amplitude / Magnitude) บอก "น้ำหนัก"** ความสวงของจุดใน k-space ไม่้แว่าพ MRI ตรงจะสว่าง ่มันือการบก "ปริมณ (Weight)"
138
+ - จุดสว่างมาก: แปลว่าภาพ MRI ภาพมี่นลวดลยชนิดนี้เป็นส่วนปะกอบอยู่ เยอะมา (มีความสำคัญต่อภาพสูง)
139
+ - จุดมืดหรือจ: แปลว่าภาพ MRI ภาพนี้ แทบจะไมมีลวดลายชนิดนี้ประกอบอยู่เ""")
 
140
 
141
  st.header("Inverse Fourier Transform")
142
  st.write("""เมื่อเก็บข้อมูลจนเต็มพื้นที่ k-space เราจะใช้กระบวนการทางคณิตศาสตร์ 2D Inverse Fourier Transform (2D-iFT) ในการเปลี่ยนข้อมูลความถี่กลับไปเป็นข้อมูลในเชิงพื้นที่ (Spatial Domain) ภาพ MRI เกิดจากการนำ "คลื่นความถี่ (Sinusoidal spatial waves)" จากทุกจุดใน k-space มาซ้อนทับกัน คลื่นที่มีเฟสตรงกันจะรวมตัวกันแบบเสริมฤทธิ์ (Constructive interference) สร้างเป็นพิกัดที่สว่าง และคลื่นที่มีเฟสตรงข้ามจะหักล้างกัน (Destructive interference) กลายเป็นพื้นที่สีดำ โดยต้องอาศัยข้อมูลจากหลายจุดมาซ้อนทับกัน และเกิดการแทรกสอดตามคุณสมบัติของคลื่น บริเวณไหนที่เป็นเนื้อเยื่อจริง คลื่นจะเสริมกันทำให้เกิด จุดสว่าง บริเวณไหนที่เป็นช่องว่าง คลื่นจะหักล้างกันทำให้เกิด จุดมืด""")
143
 
144
  st.header("ความถี่เชิงพื้นที่ (Spatial Frequency) คือ")
145
  st.write("""ในทางสัญญาณภาพ (Spatial Frequency) ความถี่ไม่ได้หมายถึงความเร็วของเวลา แต่หมายถึง "อัตราการเปลี่ยนแปลงความเข้มของแสงในพื้นที่หนึ่งๆ"
146
+ 1. **ความถี่เชิงพื้นที่ต่ำ (Low Spatial Frequency)** คืออะไร: พื้นที่ที่สีหรือความสว่าง "ค่อยๆ เปลี่ยน" หรือ "เหมือนเดิมเป็นบริเวณกว้าง" (เหมือนคลื่นลูกใหญ่ๆ ที่ขยับช้าๆ)
147
  ตัวอย่างในภาพ MRI: บริเวณเนื้อเยื่อก้อนใหญ่ๆ เช่น เนื้อตับ หรือเนื้อสมอง ที่มีสีเทาโทนเดียวกันกินพื้นที่กว้าง
148
  ตำแหน่งใน k-space: ข้อมูลเหล่านี้จะรวมตัวกันอยู่บริเวณ "ตรงกลาง"
149
  หน้าที่หลัก: สร้าง "รูปร่างรวมๆ และคอนทราสต์ (Contrast)" ให้เรารู้ว่านี่คือก้อนอวัยวะอะไร
150
 
151
+ 2. **ความถี่เชิงพื้นที่สูง (High Spatial Frequency)** คืออะไร: พื้นที่ท่ความสว่างเปลี่ยนแบบฉับพลันและรวดเร็วภายในระยะทางสั้นๆ เช่น จากขาวตัดเป็นดำสนิททันที (เหมือนลายทางแคบๆ ที่สลับสีถี่ๆ)
152
  ตัวอย่างในภาพ MRI: ขอบของอวัยวะ (Edges), รอยต่อระหว่างกระดูกกับไขสันหลัง, หรือรายละเอียดเส้นเลือดเส้นเล็กๆ
153
  ตำแหน่งใน k-space: ข้อมูลเหล่านี้จะกระจายตัวอยู่บริเวณ "ขอบนอก"
154
  หน้าที่หลัก: สร้าง "ความคมชัด (Resolution) และรายละเอียดเล็กๆ" ทำให้ภาพไม่เบลอ""")
155
 
156
+ # --- Interactive 2 ---
157
+ st.divider()
158
+ st.write("""ซึ่งจะให้ผู้เรียนได้ลองปรับหน้าตาของภาพ K-Space แล้วเปรียบเทียบความแตกต่างด้วยการปรับ High-pass filter และ Low-pass filter เพื่อดูลักษณะและความสำคัญของข้อมูลบริเวณกลางและขอบนอกของ K-space เมื่อเราจำแนกข้อมูลใน k-space ออกเป็นความถี่ต่ำ (ตรงกลาง) และความถี่สูง (ขอบนอก) ได้แล้ว เราสามารถเลือก "หยิบ" หรือ "ทิ้ง" ข้อมูลบางส่วนเพื่อดูผลลัพธ์ได้ เรียกว่าการใช้ตัวกรอง (Filter)""")
 
159
 
160
+ f_type = st.radio("เลือกชนิดตัวกรอง:", ["Low-pass Filter", "High-pass Filter"], horizontal=True)
161
 
162
+ Y_dist, X_dist = np.ogrid[-112:112, -112:112]
163
+ d = np.sqrt(X_dist**2 + Y_dist**2)
164
 
165
  if f_type == "Low-pass Filter":
166
+ st.markdown("**Low-pass Filter (ัวก่อยความถี่ต่ำผ่าน):**")
167
+ st.write('ทำงานอย่างไ: "อนุญาตให้เฉพาะข้อูลตรงกลาง (ความถี่ต่ำ) ผ่านไปสร้างภาพได้ ส่วนข้อมูลขอบนอก (ความถี่สูง) ให้ทิ้งไป"')
168
+ rad = st.slider("รัศมีวงกลม (Radius):", 0, 112, 112)
169
+ mask = d <= rad
170
+ msg = 'เราจะได้ภาพที่มี "คอนทราสต์" ดูออกว่าเป็นอวัยวะอะไร แต่ภาพจะ "เบลอ" (Blurry) เพราะข้อมูลเส้นขอบถูกทิ้งไปแล้ว'
171
  else:
172
+ st.markdown("**High-pass Filter (ัวกรงปความถี่สูงผ่าน):**")
173
+ st.write("ทำงานอย่างไ: อนุญาตให้เฉพาะข้อูลขอบนอก (ความถี่สูง) ผ่านไปได้ ส่วนข้อมูลตรงกลางทิ้ง")
174
+ rad = st.slider("รัศมีวงกลม (Radius):", 0, 112, 0)
175
+ mask = d >= rad
176
+ msg = 'ภาพจะสูญเสียคอนทราสต์ไปจนเกือบมืดสนิท แต่จะปรากฏ "เส้นขอบร่าง" (Outline) ของอวัยวะขึ้นมาอย่างคมชัด'
177
 
178
+ # คำนวณภาพผลลัพธ์
179
  filtered_k = k_data * mask
 
 
180
  col_f1, col_f2 = st.columns(2)
181
+
182
  with col_f1:
183
+ st.image(process_image(filtered_k), caption="K-Space Filtered", width=300)
184
  with col_f2:
185
+ st.image(process_image(filtered_k, is_kspace=False), caption="MRI Result (Single Slice)", width=300)
 
 
186
 
187
+ st.success(f"**ผลลัพธ์ที่ได้:** {msg}")