Nicha1234 commited on
Commit
dd53618
·
verified ·
1 Parent(s): 2e864a1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -48
app.py CHANGED
@@ -57,7 +57,6 @@ st.markdown("""
57
  background-color: #1E88E5;
58
  color: white;
59
  }
60
- /* Info box — force dark text in both light and dark mode */
61
  .kspace-info-box {
62
  text-align: center;
63
  background-color: #dbeafe !important;
@@ -115,12 +114,9 @@ def get_image_from_plot(fig):
115
  buf = io.BytesIO()
116
  plt.savefig(buf, format='png', dpi=100)
117
  plt.close(fig)
118
- return buf.getvalue() # Return bytes — reliably cacheable by st.cache_data
119
 
120
  def st_image(img_bytes, caption=None):
121
- """Display image as inline base64 data URI.
122
- Prevents layout jiggle: browser renders it instantly with no HTTP round-trip,
123
- so no 0-height placeholder → image-pop-in → scrollbar-flicker cycle."""
124
  b64 = base64.b64encode(img_bytes).decode('utf-8')
125
  cap_html = (f'<p style="text-align:center;font-size:14px;color:gray;margin-top:4px">'
126
  + caption + '</p>') if caption else ''
@@ -135,27 +131,19 @@ def st_image(img_bytes, caption=None):
135
  def draw_kspace_diagram():
136
  fig, ax = plt.subplots(figsize=(6, 6))
137
 
138
- # วาดกรอบนอก
139
  rect = plt.Rectangle((-1, -1), 2, 2, fill=False, edgecolor='black', lw=3)
140
  ax.add_patch(rect)
141
 
142
- # วาดแกนลูกศรตัดกันตรงกลาง (อยู่ภายในกรอบ)
143
  ax.annotate('', xy=(0.95, 0), xytext=(-0.95, 0), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=2))
144
  ax.annotate('', xy=(0, 0.95), xytext=(0, -0.95), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=2))
145
 
146
- # ตัวอักษรบอกทิศทาง +kx, -kx, +ky, -ky
147
  ax.text(1.1, 0, '+kx', fontsize=20, fontweight='bold', va='center')
148
-
149
- # ขยับ -kx ออกไปทางซ้ายให้มีช่องว่าง (ha='right' ทำให้ขอบขวาของตัวหนังสืออยู่ที่พิกัด -1.1 ซึ่งไม่ทับกรอบ)
150
  ax.text(-1.1, 0, '-kx', fontsize=20, fontweight='bold', va='center', ha='right')
151
-
152
  ax.text(0, 1.1, '+ky', fontsize=20, fontweight='bold', ha='center')
153
  ax.text(0, -1.1, '-ky', fontsize=20, fontweight='bold', ha='center', va='top')
154
 
155
- # Label บอกชื่อแกน (เอาไว้ข้างนอกกรอบ)
156
  ax.annotate('', xy=(1, -1.4), xytext=(-1, -1.4), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=4))
157
  ax.text(0, -1.5, 'kx (Frequency)', ha='center', va='top', fontsize=16, fontweight='bold')
158
-
159
  ax.annotate('', xy=(-1.5, 1), xytext=(-1.5, -1), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=4))
160
  ax.text(-1.6, 0, 'ky (Phase)', ha='right', va='center', rotation=90, fontsize=16, fontweight='bold')
161
 
@@ -163,7 +151,6 @@ def draw_kspace_diagram():
163
  ax.set_ylim(-1.8, 1.5)
164
  ax.axis('off')
165
 
166
- # ล็อกระยะขอบให้ตายตัวป้องกันการกระตุก
167
  fig.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.1)
168
  return get_image_from_plot(fig)
169
 
@@ -231,59 +218,94 @@ def draw_mri(mri_result):
231
 
232
  @st.cache_data
233
  def draw_pulse_sequence(current_step, total_steps):
234
- fig, axes = plt.subplots(4, 1, figsize=(5, 6), sharex=True, gridspec_kw={'height_ratios': [1, 1.5, 1, 1]})
235
- t = np.linspace(0, 10, 1000)
 
236
 
237
- # 1. RF (แก้ให้คลื่นแคบลงและเว้นช่องไฟตามทฤษฎี)
 
 
 
 
238
  ax = axes[0]
239
- rf90 = np.sinc((t - 1.5)*4) * (t > 1.0) * (t < 2.0)
240
- rf180 = np.sinc((t - 5.0)*4) * (t > 4.5) * (t < 5.5) * 1.5
241
  ax.plot(t, rf90 + rf180, color='black', lw=2)
242
- ax.text(1.5, 1.2, '90°', ha='center', fontsize=12, fontweight='bold')
243
- ax.text(5.0, 1.7, '180°', ha='center', fontsize=12, fontweight='bold')
244
- # ใช้ transAxes เลื่อนพิกัดตัวหนังสือ ป้องกันการโดนตัดขอบซ้าย
245
- ax.text(-0.05, 0.2, 'RF', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.transAxes)
246
  ax.axis('off')
247
 
248
- # 2. Gy
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  ax = axes[1]
250
- gy_vals = np.linspace(1, -1, total_steps)
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  for i, gy_amp in enumerate(gy_vals):
252
  gy = np.zeros_like(t)
253
- gy[(t > 2.5) & (t < 3.5)] = gy_amp
254
  if i == current_step:
255
- ax.plot(t, gy, color='red', lw=3, zorder=10)
256
  else:
257
- ax.plot(t, gy, color='black', lw=1, alpha=0.3)
258
- ax.text(-0.05, 0, 'Gy', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.transAxes)
 
 
259
  ax.axis('off')
260
 
261
- # 3. Gx (แก้เป็นขาขึ้น เครื่องหมายบวก + ตามหลัก Spin Echo)
262
- ax = axes[2]
263
  gx = np.zeros_like(t)
264
- gx[(t > 2.5) & (t < 3.5)] = 0.5 # แก้จาก -0.5 เป 0.5
265
- gx[(t > 7.5) & (t < 9.5)] = 0.5
266
- ax.plot(t, gx, color='black', lw=2)
267
- ax.fill_between(t, gx, 0, alpha=0.3, color='gray')
268
- ax.text(-0.05, 0, 'Gx', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.transAxes)
 
 
 
 
269
  ax.axis('off')
270
 
271
- # 4. Echo
272
- ax = axes[3]
273
- # ปรับเวลา Echo ให้ตรงกับ TE พอดี (8.5)
274
- echo_env = np.exp(-((t - 8.5)**2) / 0.3) * (t > 7.5) * (t < 9.5)
275
  ax.plot(t, echo_env, color='black', lw=2)
276
  ax.fill_between(t, echo_env, 0, alpha=0.3, color='gray')
277
- ax.text(-0.05, 0.5, 'Signal\n(Echo)', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.transAxes)
278
  ax.axis('off')
279
 
280
- # ถ่างระยะขอบซ้าย (left=0.25) ให้กว้างขึ้น คืนพื้นที่ให้ตัวหนังสือ
281
- fig.subplots_adjust(left=0.25, right=0.95, top=0.95, bottom=0.05, hspace=0.2)
282
  return get_image_from_plot(fig)
283
 
284
  @st.cache_data
285
  def draw_kspace_filling(current_step, total_steps):
286
- fig, ax = plt.subplots(figsize=(5, 6))
287
 
288
  ax.set_facecolor('black')
289
  ax.axhline(0, color='gray', lw=1, ls='--')
@@ -308,7 +330,6 @@ def draw_kspace_filling(current_step, total_steps):
308
  fig.patch.set_facecolor('black')
309
 
310
  ax.axis('off')
311
- # ล็อกระยะกรอบตายตัวเพื่อหยุดอาการสั่น
312
  fig.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.05)
313
  return get_image_from_plot(fig)
314
 
@@ -394,7 +415,6 @@ with col_main:
394
  st.markdown("---")
395
  st.markdown("## 📍 1 จุดบน k-space")
396
 
397
- # Center Text
398
  st.markdown("""
399
  <div class="kspace-info-box">
400
  <b>k-space 1 จุด = ข้อมูลของภาพทั้งภาพ และ ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด</b><br><br>
@@ -482,6 +502,4 @@ with col_main:
482
  with col_fimg2:
483
  st_image(draw_mri(mri_result), caption="ภาพ MRI ผลลัพธ์")
484
 
485
- # Spacer: keeps page taller than viewport at all zoom levels so the
486
- # scrollbar never disappears and never causes a layout-shift jiggle.
487
  st.markdown('<div style="height: 300px;"></div>', unsafe_allow_html=True)
 
57
  background-color: #1E88E5;
58
  color: white;
59
  }
 
60
  .kspace-info-box {
61
  text-align: center;
62
  background-color: #dbeafe !important;
 
114
  buf = io.BytesIO()
115
  plt.savefig(buf, format='png', dpi=100)
116
  plt.close(fig)
117
+ return buf.getvalue()
118
 
119
  def st_image(img_bytes, caption=None):
 
 
 
120
  b64 = base64.b64encode(img_bytes).decode('utf-8')
121
  cap_html = (f'<p style="text-align:center;font-size:14px;color:gray;margin-top:4px">'
122
  + caption + '</p>') if caption else ''
 
131
  def draw_kspace_diagram():
132
  fig, ax = plt.subplots(figsize=(6, 6))
133
 
 
134
  rect = plt.Rectangle((-1, -1), 2, 2, fill=False, edgecolor='black', lw=3)
135
  ax.add_patch(rect)
136
 
 
137
  ax.annotate('', xy=(0.95, 0), xytext=(-0.95, 0), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=2))
138
  ax.annotate('', xy=(0, 0.95), xytext=(0, -0.95), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=2))
139
 
 
140
  ax.text(1.1, 0, '+kx', fontsize=20, fontweight='bold', va='center')
 
 
141
  ax.text(-1.1, 0, '-kx', fontsize=20, fontweight='bold', va='center', ha='right')
 
142
  ax.text(0, 1.1, '+ky', fontsize=20, fontweight='bold', ha='center')
143
  ax.text(0, -1.1, '-ky', fontsize=20, fontweight='bold', ha='center', va='top')
144
 
 
145
  ax.annotate('', xy=(1, -1.4), xytext=(-1, -1.4), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=4))
146
  ax.text(0, -1.5, 'kx (Frequency)', ha='center', va='top', fontsize=16, fontweight='bold')
 
147
  ax.annotate('', xy=(-1.5, 1), xytext=(-1.5, -1), arrowprops=dict(arrowstyle='<|-|>', color='black', lw=4))
148
  ax.text(-1.6, 0, 'ky (Phase)', ha='right', va='center', rotation=90, fontsize=16, fontweight='bold')
149
 
 
151
  ax.set_ylim(-1.8, 1.5)
152
  ax.axis('off')
153
 
 
154
  fig.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.1)
155
  return get_image_from_plot(fig)
156
 
 
218
 
219
  @st.cache_data
220
  def draw_pulse_sequence(current_step, total_steps):
221
+ # ปรับเพิ่มเป็น 5 กราฟเพื่อแทรก G_SS เข้าไปให้เหมือนรูปอ้างอิง
222
+ fig, axes = plt.subplots(5, 1, figsize=(6, 6.5), sharex=True, gridspec_kw={'height_ratios': [1.5, 1, 1, 1, 1]})
223
+ t = np.linspace(0, 11, 1000)
224
 
225
+ t_90 = 1.5
226
+ t_180 = 5.0
227
+ t_echo = 8.5
228
+
229
+ # 1. Apply RF
230
  ax = axes[0]
231
+ rf90 = np.sinc((t - t_90)*4) * (t > t_90 - 0.5) * (t < t_90 + 0.5)
232
+ rf180 = np.sinc((t - t_180)*4) * (t > t_180 - 0.5) * (t < t_180 + 0.5) * 1.5
233
  ax.plot(t, rf90 + rf180, color='black', lw=2)
234
+ ax.text(t_90, 1.2, '90°', ha='right', fontsize=12, fontweight='bold')
235
+ ax.text(t_180, 1.7, '180°', ha='left', fontsize=12, fontweight='bold')
236
+ ax.text(-0.05, 0.2, 'Apply RF', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.transAxes)
 
237
  ax.axis('off')
238
 
239
+ # เพิ่มเส้นประบอกเวลา (Dashed Lines)
240
+ for ax_i in axes:
241
+ ax_i.axvline(t_90, color='black', linestyle='--', alpha=0.5)
242
+ ax_i.axvline(t_180, color='black', linestyle='--', alpha=0.5)
243
+ ax_i.axvline(t_echo, color='black', linestyle='--', alpha=0.5)
244
+
245
+ # เพิ่มตัวอักษรบอกช่วงเวลา TE และ TE/2
246
+ ax.annotate('', xy=(t_90, 2.2), xytext=(t_180, 2.2), arrowprops=dict(arrowstyle='<->', color='black'))
247
+ ax.text((t_90+t_180)/2, 2.3, 'TE/2', ha='center', va='bottom', fontsize=12, fontweight='bold')
248
+ ax.annotate('', xy=(t_90, 2.8), xytext=(t_echo, 2.8), arrowprops=dict(arrowstyle='<->', color='black'))
249
+ ax.text((t_90+t_echo)/2, 2.9, 'TE', ha='center', va='bottom', fontsize=12, fontweight='bold')
250
+ ax.set_ylim(-0.5, 3.5)
251
+
252
+ # 2. G_SS (Slice Selection - สีเขียวแบบในรูป)
253
  ax = axes[1]
254
+ gss = np.zeros_like(t)
255
+ gss[(t > t_90 - 0.5) & (t < t_90 + 0.5)] = 0.8
256
+ gss[(t > t_90 + 0.5) & (t < t_90 + 1.0)] = -0.4
257
+ gss[(t > t_180 - 0.5) & (t < t_180 + 0.5)] = 0.8
258
+ ax.plot(t, gss, color='#7cb342', lw=2)
259
+ ax.fill_between(t, gss, 0, alpha=0.2, color='#7cb342')
260
+ ax.text(-0.05, 0, '$G_{SS}$', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.transAxes)
261
+ ax.set_ylim(-1.2, 1.2)
262
+ ax.axis('off')
263
+
264
+ # 3. G_PE (Phase Encoding - สีฟ้าแบบในรูป ทำเป็นกล่องเรียงเส้น)
265
+ ax = axes[2]
266
+ gy_vals = np.linspace(0.8, -0.8, total_steps)
267
+ t_pe_start, t_pe_end = 2.8, 3.8
268
  for i, gy_amp in enumerate(gy_vals):
269
  gy = np.zeros_like(t)
270
+ gy[(t > t_pe_start) & (t < t_pe_end)] = gy_amp
271
  if i == current_step:
272
+ ax.plot(t, gy, color='#2c7bb6', lw=3, zorder=10)
273
  else:
274
+ ax.plot(t, gy, color='#2c7bb6', lw=1, alpha=0.3)
275
+ ax.plot([t_pe_start, t_pe_end, t_pe_end, t_pe_start, t_pe_start], [0.8, 0.8, -0.8, -0.8, 0.8], color='#2c7bb6', lw=1.5)
276
+ ax.text(-0.05, 0, '$G_{PE}$', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.transAxes)
277
+ ax.set_ylim(-1.2, 1.2)
278
  ax.axis('off')
279
 
280
+ # 4. G_FE (Frequency Encoding - สีแดง)
281
+ ax = axes[3]
282
  gx = np.zeros_like(t)
283
+ # มที่คุณบอเป๊ะ: อันแรกติดลบพื่อกวาดได้าซ้าย
284
+ gx[(t > 2.8) & (t < 3.8)] = -0.6
285
+ gx[(t > t_echo - 1.0) & (t < t_echo + 1.0)] = 0.6
286
+ ax.plot(t, gx, color='#d32f2f', lw=2)
287
+ ax.fill_between(t, gx, 0, alpha=0.2, color='#d32f2f')
288
+ ax.text(3.3, -0.4, '-', color='#d32f2f', fontsize=16, fontweight='bold', ha='center', va='center')
289
+ ax.text(t_echo, 0.4, '+', color='#d32f2f', fontsize=16, fontweight='bold', ha='center', va='center')
290
+ ax.text(-0.05, 0, '$G_{FE}$', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.transAxes)
291
+ ax.set_ylim(-1.2, 1.2)
292
  ax.axis('off')
293
 
294
+ # 5. Echo Signal
295
+ ax = axes[4]
296
+ echo_env = np.exp(-((t - t_echo)**2) / 0.15) * (t > t_echo - 1.0) * (t < t_echo + 1.0)
 
297
  ax.plot(t, echo_env, color='black', lw=2)
298
  ax.fill_between(t, echo_env, 0, alpha=0.3, color='gray')
299
+ ax.text(-0.05, 0.5, 'Echo signal', fontsize=14, fontweight='bold', va='center', ha='right', transform=ax.transAxes)
300
  ax.axis('off')
301
 
302
+ # ถ่างระยะขอบซ้ายให้กว้างพอเพื่อไม่ให้ตัวหนังสือตกจอ
303
+ fig.subplots_adjust(left=0.20, right=0.95, top=0.95, bottom=0.05, hspace=0.1)
304
  return get_image_from_plot(fig)
305
 
306
  @st.cache_data
307
  def draw_kspace_filling(current_step, total_steps):
308
+ fig, ax = plt.subplots(figsize=(5, 6.5)) # ปรับความสูงให้สัมพันธ์กัน
309
 
310
  ax.set_facecolor('black')
311
  ax.axhline(0, color='gray', lw=1, ls='--')
 
330
  fig.patch.set_facecolor('black')
331
 
332
  ax.axis('off')
 
333
  fig.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.05)
334
  return get_image_from_plot(fig)
335
 
 
415
  st.markdown("---")
416
  st.markdown("## 📍 1 จุดบน k-space")
417
 
 
418
  st.markdown("""
419
  <div class="kspace-info-box">
420
  <b>k-space 1 จุด = ข้อมูลของภาพทั้งภาพ และ ภาพ 1 พิกเซล = ผลรวมของ k-space ทุกจุด</b><br><br>
 
502
  with col_fimg2:
503
  st_image(draw_mri(mri_result), caption="ภาพ MRI ผลลัพธ์")
504
 
 
 
505
  st.markdown('<div style="height: 300px;"></div>', unsafe_allow_html=True)