Fix CT orientation handling in viewer and affine

This commit is contained in:
admin
2026-06-03 11:07:17 +08:00
parent 3d96bdb706
commit 3e8ddc3468
5 changed files with 269 additions and 262 deletions

34
app.py
View File

@@ -195,10 +195,16 @@ def normalize_slice(image: np.ndarray, p_low: float = 1.0, p_high: float = 99.0)
return np.clip((image - low) / (high - low), 0.0, 1.0)
def orient_for_display(image: np.ndarray) -> np.ndarray:
"""统一显示方向,让三视图在网页中更接近医学浏览器观感。"""
def orient_for_display(image: np.ndarray, plane: str) -> np.ndarray:
"""按平面统一显示方向
return np.rot90(image)
数组轴约定为 (X, Y, Z),其中 Z 轴在当前患者1 DICOM 中从头侧到足侧
递增。冠状面和矢状面应让 Z 轴作为屏幕竖直方向,避免头脚倒置。
"""
if image.ndim == 3 and image.shape[-1] in (3, 4):
return np.swapaxes(image, 0, 1)
return image.T
def get_slice(volume_xyz: np.ndarray, plane: str, index: int) -> np.ndarray:
@@ -267,18 +273,18 @@ def format_mm(value: float) -> str:
return f"{value:.2f}"
def render_image(image: np.ndarray, caption: str, cmap: str = "gray") -> None:
def render_image(image: np.ndarray, caption: str, cmap: str = "gray", plane: str = "Axial") -> None:
fig, ax = plt.subplots(figsize=(4.8, 4.8), dpi=120)
ax.imshow(orient_for_display(image), cmap=cmap, interpolation="nearest")
ax.imshow(orient_for_display(image, plane), cmap=cmap, interpolation="nearest")
ax.set_title(caption, fontsize=10)
ax.axis("off")
st.pyplot(fig, width="stretch")
plt.close(fig)
def render_rgb(image: np.ndarray, caption: str) -> None:
def render_rgb(image: np.ndarray, caption: str, plane: str = "Axial") -> None:
fig, ax = plt.subplots(figsize=(4.8, 4.8), dpi=120)
ax.imshow(orient_for_display(image), interpolation="nearest")
ax.imshow(orient_for_display(image, plane), interpolation="nearest")
ax.set_title(caption, fontsize=10)
ax.axis("off")
st.pyplot(fig, width="stretch")
@@ -290,14 +296,15 @@ def render_heatmap_overlay(
mag_slice: np.ndarray,
alpha: float,
caption: str,
plane: str,
vmin: float,
vmax: float,
show_base: bool,
) -> None:
fig, ax = plt.subplots(figsize=(4.8, 4.8), dpi=120)
if show_base:
ax.imshow(orient_for_display(normalize_slice(base_slice)), cmap="gray", interpolation="nearest")
heat = orient_for_display(mag_slice)
ax.imshow(orient_for_display(normalize_slice(base_slice), plane), cmap="gray", interpolation="nearest")
heat = orient_for_display(mag_slice, plane)
im = ax.imshow(
heat,
cmap="turbo",
@@ -321,7 +328,7 @@ def render_three_views(volume_xyz: np.ndarray, title: str) -> None:
axis = plane_axis(plane)
with col:
index = st.slider(f"{cn_name}", 0, volume_xyz.shape[axis] - 1, volume_xyz.shape[axis] // 2, key=f"{title}-{plane}")
render_image(get_slice(volume_xyz, plane, index), f"{cn_name} #{index}")
render_image(get_slice(volume_xyz, plane, index), f"{cn_name} #{index}", plane=plane)
def render_three_way_views(fixed_xyz: np.ndarray, moving_xyz: np.ndarray, warped_xyz: np.ndarray | None) -> None:
@@ -351,7 +358,7 @@ def render_three_way_views(fixed_xyz: np.ndarray, moving_xyz: np.ndarray, warped
if volume is None:
st.info("待生成")
else:
render_image(get_slice(volume, plane, index), f"{label} #{index}")
render_image(get_slice(volume, plane, index), f"{label} #{index}", plane=plane)
def render_overlay_views(fixed_xyz: np.ndarray, warped_xyz: np.ndarray) -> None:
@@ -369,9 +376,9 @@ def render_overlay_views(fixed_xyz: np.ndarray, warped_xyz: np.ndarray) -> None:
fixed_slice = get_slice(fixed_xyz, plane, index)
warped_slice = get_slice(warped_xyz, plane, index)
if mode == "棋盘格":
render_image(checkerboard(fixed_slice, warped_slice, tile=tile), f"{cn_name} 棋盘格 #{index}")
render_image(checkerboard(fixed_slice, warped_slice, tile=tile), f"{cn_name} 棋盘格 #{index}", plane=plane)
else:
render_rgb(alpha_overlay(fixed_slice, warped_slice, alpha=alpha), f"{cn_name} 融合 #{index}")
render_rgb(alpha_overlay(fixed_slice, warped_slice, alpha=alpha), f"{cn_name} 融合 #{index}", plane=plane)
def render_ddf_views(fixed_xyz: np.ndarray, ddf_xyz: np.ndarray) -> None:
@@ -410,6 +417,7 @@ def render_ddf_views(fixed_xyz: np.ndarray, ddf_xyz: np.ndarray) -> None:
get_slice(fixed_xyz, plane, index),
get_slice(mag, plane, index),
alpha=alpha,
plane=plane,
vmin=vmin,
vmax=vmax,
show_base=show_base,