Update app.py
Browse files
app.py
CHANGED
|
@@ -5,8 +5,6 @@ import zipfile
|
|
| 5 |
import os
|
| 6 |
import io
|
| 7 |
from PIL import Image
|
| 8 |
-
|
| 9 |
-
# บังคับให้ Matplotlib ทำงานแบบ Background เพื่อแก้ปัญหาภาพสั่น
|
| 10 |
import matplotlib
|
| 11 |
matplotlib.use('Agg')
|
| 12 |
import matplotlib.pyplot as plt
|
|
@@ -36,7 +34,6 @@ st.markdown("""
|
|
| 36 |
color: #1565C0;
|
| 37 |
margin-top: 20px;
|
| 38 |
}
|
| 39 |
-
/* ปรับแต่งปุ่มให้ดูสวยงาม */
|
| 40 |
div.stButton > button:first-child {
|
| 41 |
background-color: #f0f2f6;
|
| 42 |
color: #0D47A1;
|
|
@@ -82,7 +79,6 @@ def format_kspace_display(k_data):
|
|
| 82 |
|
| 83 |
kspace_bg_image = format_kspace_display(kspace_raw)
|
| 84 |
|
| 85 |
-
# --- ฟังก์ชันแปลงรูปภาพ (ลดการสั่น) ---
|
| 86 |
def get_image_from_plot(fig):
|
| 87 |
buf = io.BytesIO()
|
| 88 |
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.1, dpi=100)
|
|
@@ -139,72 +135,87 @@ def draw_mri(mri_result):
|
|
| 139 |
ax.axis('off')
|
| 140 |
return get_image_from_plot(fig)
|
| 141 |
|
| 142 |
-
# --- ฟังก์ชันวาดร
|
| 143 |
-
def
|
| 144 |
-
|
| 145 |
-
|
| 146 |
|
| 147 |
-
#
|
| 148 |
-
ax
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
if i == current_step:
|
| 156 |
-
ax.plot(
|
| 157 |
elif i < current_step:
|
| 158 |
-
ax.plot(
|
| 159 |
else:
|
| 160 |
-
ax.plot(
|
| 161 |
-
|
| 162 |
-
ax.
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
ax.axis('off')
|
|
|
|
|
|
|
| 166 |
return get_image_from_plot(fig)
|
| 167 |
|
| 168 |
def draw_kspace_filling(current_step, total_steps, mode_type, real_bg):
|
| 169 |
-
|
| 170 |
-
fig, ax = plt.subplots(figsize=(4, 4))
|
| 171 |
-
|
| 172 |
-
# สร้างภาพเปล่าสีดำ
|
| 173 |
display_img = np.zeros((224, 224))
|
| 174 |
-
|
| 175 |
-
# คำนวณขอบเขตบรรทัดที่จะเติม
|
| 176 |
lines_per_step = 224 // total_steps
|
| 177 |
current_line_idx = current_step * lines_per_step
|
| 178 |
|
| 179 |
if mode_type == "ข้อมูลจำลอง (Simulated)":
|
| 180 |
-
# สร้างภาพเส้นจำลองสีเหลืองแนวนอน
|
| 181 |
Y, X = np.ogrid[:224, :224]
|
| 182 |
-
# จำลองสัญญาณให้ตรงกลางสว่าง ขอบมืด
|
| 183 |
intensity = np.exp(-((Y - 112)**2) / (2 * 50**2))
|
| 184 |
for i in range(current_line_idx + lines_per_step):
|
| 185 |
display_img[i, :] = intensity[i, 0] * 255
|
| 186 |
-
|
| 187 |
ax.imshow(display_img, cmap='inferno', extent=[-112, 112, -112, 112], vmin=0, vmax=255)
|
| 188 |
else:
|
| 189 |
-
# ใช้ข้อมูล kspace.mat จริง
|
| 190 |
if current_step >= total_steps - 1:
|
| 191 |
display_img = real_bg
|
| 192 |
else:
|
| 193 |
display_img[:current_line_idx + lines_per_step, :] = real_bg[:current_line_idx + lines_per_step, :]
|
| 194 |
-
|
| 195 |
ax.imshow(display_img, cmap='gray', extent=[-112, 112, -112, 112], vmin=0, vmax=1)
|
| 196 |
|
| 197 |
ax.set_xlim(-112, 112)
|
| 198 |
-
ax.set_ylim(112, -112) #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
ax.axhline(0, color='white', linewidth=0.5, linestyle='--')
|
| 200 |
ax.axvline(0, color='white', linewidth=0.5, linestyle='--')
|
| 201 |
ax.set_title("K-Space Filling", fontweight='bold')
|
| 202 |
ax.axis('off')
|
| 203 |
return get_image_from_plot(fig)
|
| 204 |
|
| 205 |
-
|
| 206 |
# ==========================================================
|
| 207 |
-
# --- 3. ส่วนแสดงผลเว็บ
|
| 208 |
# ==========================================================
|
| 209 |
|
| 210 |
_, col_main, _ = st.columns([1, 6, 1])
|
|
@@ -230,16 +241,21 @@ with col_main:
|
|
| 230 |
st.image("Screenshot 2026-05-07 205051.png", use_container_width=True)
|
| 231 |
elif os.path.exists("Screenshot 2026-05-07 205051.jpg"):
|
| 232 |
st.image("Screenshot 2026-05-07 205051.jpg", use_container_width=True)
|
| 233 |
-
else:
|
| 234 |
-
st.info("กรุณาอัปโหลดรูปภาพ Screenshot 2026-05-07 205051 (เป็น .png หรือ .jpg ก็ได้)")
|
| 235 |
|
| 236 |
# ---------------------------------------------------------
|
| 237 |
-
# NEW
|
| 238 |
# ---------------------------------------------------------
|
| 239 |
-
st.markdown("##
|
| 240 |
-
st.markdown("
|
| 241 |
-
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
total_anim_steps = 15
|
| 244 |
if 'fill_step' not in st.session_state:
|
| 245 |
st.session_state.fill_step = 0
|
|
@@ -259,11 +275,11 @@ with col_main:
|
|
| 259 |
st.session_state.fill_step = 0
|
| 260 |
|
| 261 |
# แถบควบค���มบน
|
| 262 |
-
_, col_ctrl1, col_ctrl2, _ = st.columns([
|
| 263 |
with col_ctrl1:
|
| 264 |
-
data_mode = st.radio("เลือกรูปแบบ
|
| 265 |
with col_ctrl2:
|
| 266 |
-
st.write("ควบคุมการเติมบรรทัด:")
|
| 267 |
c1, c2, c3, c4 = st.columns(4)
|
| 268 |
c1.button("⏮ Reset", on_click=reset_anim)
|
| 269 |
c2.button("◀ ก่อนหน้า", on_click=step_backward)
|
|
@@ -272,12 +288,14 @@ with col_main:
|
|
| 272 |
|
| 273 |
st.progress((st.session_state.fill_step + 1) / total_anim_steps)
|
| 274 |
|
| 275 |
-
#
|
| 276 |
-
|
| 277 |
-
with
|
| 278 |
-
st.
|
| 279 |
-
|
| 280 |
-
|
|
|
|
|
|
|
| 281 |
|
| 282 |
|
| 283 |
st.markdown("---")
|
|
|
|
| 5 |
import os
|
| 6 |
import io
|
| 7 |
from PIL import Image
|
|
|
|
|
|
|
| 8 |
import matplotlib
|
| 9 |
matplotlib.use('Agg')
|
| 10 |
import matplotlib.pyplot as plt
|
|
|
|
| 34 |
color: #1565C0;
|
| 35 |
margin-top: 20px;
|
| 36 |
}
|
|
|
|
| 37 |
div.stButton > button:first-child {
|
| 38 |
background-color: #f0f2f6;
|
| 39 |
color: #0D47A1;
|
|
|
|
| 79 |
|
| 80 |
kspace_bg_image = format_kspace_display(kspace_raw)
|
| 81 |
|
|
|
|
| 82 |
def get_image_from_plot(fig):
|
| 83 |
buf = io.BytesIO()
|
| 84 |
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.1, dpi=100)
|
|
|
|
| 135 |
ax.axis('off')
|
| 136 |
return get_image_from_plot(fig)
|
| 137 |
|
| 138 |
+
# --- ฟังก์ชันวาดกราฟ Pulse Sequence (RF, Gx, Gy, Echo) ---
|
| 139 |
+
def draw_pulse_sequence(current_step, total_steps):
|
| 140 |
+
fig, axes = plt.subplots(4, 1, figsize=(5, 6), gridspec_kw={'height_ratios': [1, 1, 1.5, 1]})
|
| 141 |
+
t = np.linspace(0, 10, 500)
|
| 142 |
|
| 143 |
+
# 1. RF
|
| 144 |
+
ax = axes[0]
|
| 145 |
+
rf = np.sinc(t - 2) * (t > 0) * (t < 4)
|
| 146 |
+
ax.plot(t, rf, color='black', lw=2)
|
| 147 |
+
ax.text(0, 0.5, 'RF', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.get_yaxis_transform())
|
| 148 |
+
ax.axis('off')
|
| 149 |
|
| 150 |
+
# 2. Gx (Frequency Encoding)
|
| 151 |
+
ax = axes[1]
|
| 152 |
+
gx = np.zeros_like(t)
|
| 153 |
+
gx[(t > 4.5) & (t < 5.5)] = -1 # Dephase
|
| 154 |
+
gx[(t > 6) & (t < 9)] = 1 # Readout
|
| 155 |
+
ax.plot(t, gx, color='black', lw=2)
|
| 156 |
+
ax.fill_between(t, gx, 0, alpha=0.3, color='gray')
|
| 157 |
+
ax.text(0, 0, 'Gx', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.get_yaxis_transform())
|
| 158 |
+
ax.axis('off')
|
| 159 |
|
| 160 |
+
# 3. Gy (Phase Encoding)
|
| 161 |
+
ax = axes[2]
|
| 162 |
+
gy_vals = np.linspace(1, -1, total_steps)
|
| 163 |
+
for i, gy_amp in enumerate(gy_vals):
|
| 164 |
+
gy = np.zeros_like(t)
|
| 165 |
+
gy[(t > 4.5) & (t < 5.5)] = gy_amp
|
| 166 |
if i == current_step:
|
| 167 |
+
ax.plot(t, gy, color='red', lw=3, zorder=10) # ไฮไลต์เส้นปัจจุบัน
|
| 168 |
elif i < current_step:
|
| 169 |
+
ax.plot(t, gy, color='gray', lw=1, alpha=0.5)
|
| 170 |
else:
|
| 171 |
+
ax.plot(t, gy, color='blue', lw=1, alpha=0.3)
|
| 172 |
+
ax.text(0, 0, 'Gy', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.get_yaxis_transform())
|
| 173 |
+
ax.axis('off')
|
| 174 |
+
|
| 175 |
+
# 4. Echo
|
| 176 |
+
ax = axes[3]
|
| 177 |
+
echo = np.sinc(t - 7.5) * (t > 6) * (t < 9)
|
| 178 |
+
ax.plot(t, echo, color='black', lw=2)
|
| 179 |
+
ax.text(0, 0.5, 'Echo', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.get_yaxis_transform())
|
| 180 |
ax.axis('off')
|
| 181 |
+
|
| 182 |
+
plt.tight_layout()
|
| 183 |
return get_image_from_plot(fig)
|
| 184 |
|
| 185 |
def draw_kspace_filling(current_step, total_steps, mode_type, real_bg):
|
| 186 |
+
fig, ax = plt.subplots(figsize=(5, 6))
|
|
|
|
|
|
|
|
|
|
| 187 |
display_img = np.zeros((224, 224))
|
|
|
|
|
|
|
| 188 |
lines_per_step = 224 // total_steps
|
| 189 |
current_line_idx = current_step * lines_per_step
|
| 190 |
|
| 191 |
if mode_type == "ข้อมูลจำลอง (Simulated)":
|
|
|
|
| 192 |
Y, X = np.ogrid[:224, :224]
|
|
|
|
| 193 |
intensity = np.exp(-((Y - 112)**2) / (2 * 50**2))
|
| 194 |
for i in range(current_line_idx + lines_per_step):
|
| 195 |
display_img[i, :] = intensity[i, 0] * 255
|
|
|
|
| 196 |
ax.imshow(display_img, cmap='inferno', extent=[-112, 112, -112, 112], vmin=0, vmax=255)
|
| 197 |
else:
|
|
|
|
| 198 |
if current_step >= total_steps - 1:
|
| 199 |
display_img = real_bg
|
| 200 |
else:
|
| 201 |
display_img[:current_line_idx + lines_per_step, :] = real_bg[:current_line_idx + lines_per_step, :]
|
|
|
|
| 202 |
ax.imshow(display_img, cmap='gray', extent=[-112, 112, -112, 112], vmin=0, vmax=1)
|
| 203 |
|
| 204 |
ax.set_xlim(-112, 112)
|
| 205 |
+
ax.set_ylim(112, -112) # วาดจากบนลงล่าง
|
| 206 |
+
|
| 207 |
+
# วาดลูกศรชี้ว่ากำลังเติมบรรทัดไหน
|
| 208 |
+
y_pos = 112 - (current_line_idx + lines_per_step//2)
|
| 209 |
+
ax.annotate('', xy=(0, y_pos), xytext=(-100, y_pos), arrowprops=dict(arrowstyle='->', color='red', lw=3))
|
| 210 |
+
|
| 211 |
ax.axhline(0, color='white', linewidth=0.5, linestyle='--')
|
| 212 |
ax.axvline(0, color='white', linewidth=0.5, linestyle='--')
|
| 213 |
ax.set_title("K-Space Filling", fontweight='bold')
|
| 214 |
ax.axis('off')
|
| 215 |
return get_image_from_plot(fig)
|
| 216 |
|
|
|
|
| 217 |
# ==========================================================
|
| 218 |
+
# --- 3. ส่วนแสดงผลเว็บ ---
|
| 219 |
# ==========================================================
|
| 220 |
|
| 221 |
_, col_main, _ = st.columns([1, 6, 1])
|
|
|
|
| 241 |
st.image("Screenshot 2026-05-07 205051.png", use_container_width=True)
|
| 242 |
elif os.path.exists("Screenshot 2026-05-07 205051.jpg"):
|
| 243 |
st.image("Screenshot 2026-05-07 205051.jpg", use_container_width=True)
|
|
|
|
|
|
|
| 244 |
|
| 245 |
# ---------------------------------------------------------
|
| 246 |
+
# NEW: K-Space Trajectories (การบันทึกข้อมูล)
|
| 247 |
# ---------------------------------------------------------
|
| 248 |
+
st.markdown("## 🛤️ K-Space Trajectories")
|
| 249 |
+
st.markdown("""
|
| 250 |
+
**การบันทึกข้อมูลลงใน K-space สามารถทำได้หลายรูปแบบ** โดยจะยกตัวอย่างให้การเก็บข้อมูลจะดำเนินไปทีละบรรทัด โดยสัญญาณจะเกิดขึ้นหลังจากกระตุ้นด้วย RF Pulse โดยมีลำดับดังนี้
|
| 251 |
+
|
| 252 |
+
1. กระบวนการเริ่มต้นขึ้นเมื่อสนามแม่เหล็กเกรเดียนท์ **Gx** และ **Gy** เริ่มทำงานพร้อมกันเพื่อสร้างพิกัดอ้างอิง
|
| 253 |
+
2. ระบบจะเริ่มบันทึกข้อมูลจุดแรกของเฟสในแถวที่ 1 โดยจะเริ่มต้นที่ค่าแอมปลิจูดของ **Gy ทางฝั่งบวกก่อน**
|
| 254 |
+
3. สัญญาณจะถูกบันทึกไล่ไปตามแกน Gx จนได้ข้อมูลครบถ้วนเต็ม 1 แถว
|
| 255 |
+
4. เมื่อจบแถวแรก ระบบจะเริ่มกระบวนการใหม่เพื่อบันทึกข้อมูลในเฟสแถวที่ 2, 3, 4 ต่อไปเรื่อยๆ
|
| 256 |
+
5. การวนรอบนี้จะดำเนินต่อไปพร้อมกับการเปลี่ยนค่า **Gy ให้ลดลงจนไปถึงค่าทางฝั่งลบ** เมื่อบันทึกครบทุกแถวตามจำนวนความละเอียดที่ตั้งไว้ จะถือว่าสิ้นสุดกระบวนการเก็บข้อมูล MRI แบบสองมิติลงบน K-space
|
| 257 |
+
""")
|
| 258 |
+
|
| 259 |
total_anim_steps = 15
|
| 260 |
if 'fill_step' not in st.session_state:
|
| 261 |
st.session_state.fill_step = 0
|
|
|
|
| 275 |
st.session_state.fill_step = 0
|
| 276 |
|
| 277 |
# แถบควบค���มบน
|
| 278 |
+
_, col_ctrl1, col_ctrl2, _ = st.columns([0.5, 2.5, 2.5, 0.5])
|
| 279 |
with col_ctrl1:
|
| 280 |
+
data_mode = st.radio("เลือกรูปแบบ K-Space:", ["ข้อมูลจำลอง (Simulated)", "ข้อมูลจริง (kspace.mat)"])
|
| 281 |
with col_ctrl2:
|
| 282 |
+
st.write("ควบคุมการเติมบรรทัด (Gy):")
|
| 283 |
c1, c2, c3, c4 = st.columns(4)
|
| 284 |
c1.button("⏮ Reset", on_click=reset_anim)
|
| 285 |
c2.button("◀ ก่อนหน้า", on_click=step_backward)
|
|
|
|
| 288 |
|
| 289 |
st.progress((st.session_state.fill_step + 1) / total_anim_steps)
|
| 290 |
|
| 291 |
+
# ใช้ Placeholder เพื่อลดการสั่นกระตุกตอนวาดรูปใหม่
|
| 292 |
+
anim_placeholder = st.empty()
|
| 293 |
+
with anim_placeholder.container():
|
| 294 |
+
col_anim1, col_anim2 = st.columns([1, 1])
|
| 295 |
+
with col_anim1:
|
| 296 |
+
st.image(draw_pulse_sequence(st.session_state.fill_step, total_anim_steps), use_container_width=True)
|
| 297 |
+
with col_anim2:
|
| 298 |
+
st.image(draw_kspace_filling(st.session_state.fill_step, total_anim_steps, data_mode, kspace_bg_image), use_container_width=True)
|
| 299 |
|
| 300 |
|
| 301 |
st.markdown("---")
|