Scipy.spatial.Делоне не возвращает нужную поверхностьPython

Программы на Python
Ответить
Anonymous
 Scipy.spatial.Делоне не возвращает нужную поверхность

Сообщение Anonymous »

Я хочу восстановить поверхность органа, используя точки, содержащиеся в файле *.dcm (DICOM). Однако при использовании функции Делоне я получаю форму, которая не соответствует поверхности желаемой формы, но она гораздо грубее и не имеет желаемых деталей. Может ли кто-нибудь проверить, что мне следует изменить, чтобы моя реконструированная поверхность соответствовала желаемой? Я также приложу реконструированную и желаемую поверхность.
Желаемая форма поверхности:
Изображение

Реконструированная поверхность с помощью Делоне:
Изображение

#!/usr/bin/env python3
"""
RTSTRUCT ROI triangulation (MATLAB delaunayTriangulation + freeBoundary equivalent)

Requirements:
pip install pydicom numpy scipy

Optional (for saving):
pip install trimesh pyglet

Usage:
python triangulate.py --rtstruct path/to/file.dcm --roi TECH_PTV_6600 --out ptv_mesh.ply
"""

from __future__ import annotations

import argparse
from dataclasses import dataclass
from typing import Tuple, List, Optional, Dict

import numpy as np
import pydicom
from scipy.spatial import Delaunay

class SurfaceMesh:
def __init__(self, vertices, faces):
self.vertices = vertices # (V, 3) float64
self.faces = faces # (F, 3) int64 (triangle vertex indices)

def load_dicom(path: str):
return pydicom.dcmread(path)

def build_roi_name_to_number(rs) -> Dict[str, int]:
"""
StructureSetROISequence entries have ROIName and ROINumber. This function saves the ROIName with corresponding ROINumber
"""
roi_map = {}
if not hasattr(rs, "StructureSetROISequence"):
raise ValueError("Not an RTSTRUCT: missing StructureSetROISequence.")
for roi in rs.StructureSetROISequence:
roi_map[str(roi.ROIName)] = int(roi.ROINumber)
return roi_map

def find_roi_contour_item(rs, roi_number: int):
"""
Find the ROIContourSequence item that references roi_number.
"""
if not hasattr(rs, "ROIContourSequence"):
raise ValueError("RTSTRUCT missing ROIContourSequence.")
for item in rs.ROIContourSequence:
if int(item.ReferencedROINumber) == int(roi_number):
return item
raise ValueError(f"ROIContourSequence item not found for ROINumber={roi_number}.")

def extract_roi_point_cloud_mm(rs, roi_name: str) -> np.ndarray:
"""
Collect all contour points (x,y,z) for the ROI into a single (N,3) array in mm.
This is the point cloud used by the MATLAB code as input to delaunayTriangulation.
"""
roi_map = build_roi_name_to_number(rs)
if roi_name not in roi_map:
available = "\n".join(sorted(roi_map.keys()))
raise ValueError(f'ROI "{roi_name}" not found. Available ROIs:\n{available}')

roi_number = roi_map[roi_name]
roi_contour = find_roi_contour_item(rs, roi_number)

if not hasattr(roi_contour, "ContourSequence"):
raise ValueError(f'ROI "{roi_name}" has no ContourSequence.')

pts_all = []
for c in roi_contour.ContourSequence:
data = np.asarray(c.ContourData, dtype=np.float64).reshape(-1, 3) # extract all the points of the current slice into data in format (N, 3)
pts_all.append(data) # append array into pts_all

pts = np.vstack(pts_all) # stacking all the points into a single (N, 3) array

# Remove exact duplicates (helps Delaunay stability)
pts = np.unique(np.round(pts, decimals=6), axis=0)
if pts.shape[0] < 4:
raise ValueError("Not enough unique points to build a 3D triangulation.")
return pts

def free_boundary_from_tetrahedra(delaunay: Delaunay) -> np.ndarray:
tets = delaunay.simplices # (T,4)

f0 = tets[:, [0, 1, 2]]
f1 = tets[:, [0, 1, 3]]
f2 = tets[:, [0, 2, 3]]
f3 = tets[:, [1, 2, 3]]
faces = np.vstack([f0, f1, f2, f3]) # (4T,3)

faces_sorted = np.sort(faces, axis=1)
faces_sorted = np.ascontiguousarray(faces_sorted)

unique_faces, counts = np.unique(faces_sorted, axis=0, return_counts=True)
boundary_faces = unique_faces[counts == 1]
return boundary_faces.astype(np.int64)

def triangulate_point_cloud_surface(points_mm: np.ndarray) -> SurfaceMesh:
"""
Equivalent of:
DT = delaunayTriangulation(x,y,z)
[Faces,Vertices] = freeBoundary(DT)
"""
# Delaunay tetrahedralization
dt = Delaunay(points_mm)

# Boundary triangles
faces = free_boundary_from_tetrahedra(dt)

# Vertices are just the Delaunay input points
vertices = np.asarray(dt.points, dtype=np.float64)

return SurfaceMesh(vertices=vertices, faces=faces)

def save_mesh(mesh: SurfaceMesh, out_path: str) -> None:
"""
Optional: save as PLY/OBJ/STL using trimesh if available.
"""
try:
import trimesh
except ImportError as e:
raise RuntimeError(
"Saving requires trimesh. Install with: pip install trimesh"
) from e

tm = trimesh.Trimesh(vertices=mesh.vertices, faces=mesh.faces, process=False)
#tm.show()
tm.export(out_path)

def main():
ap = argparse.ArgumentParser()
ap.add_argument("--rtstruct", default="U:\Data\RS1.2.752.243.1.1.20260211130851705.2580.11322.dcm", help="Path to RTSTRUCT DICOM (.dcm) file")
ap.add_argument("--roi", default="TECH_PTV_6600", help='ROI name, e.g. "TECH_PTV_6600"')
ap.add_argument("--out", default="ptv_mesh.ply", help="Optional: output mesh file (ply/obj/stl)")
args = ap.parse_args()

rs = load_dicom(args.rtstruct)
pts = extract_roi_point_cloud_mm(rs, args.roi)
mesh = triangulate_point_cloud_surface(pts)

print(f"ROI: {args.roi}")
print(f"Point cloud: {pts.shape[0]} points")
print(f"Surface mesh: {mesh.vertices.shape[0]} vertices, {mesh.faces.shape[0]} triangles")
print(f"Surface mesh: {len(mesh.vertices)} vertices, {len(mesh.faces)} triangles")

if args.out:
save_mesh(mesh, args.out)
print(f"Saved mesh to: {args.out}")

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

ax.plot_trisurf(mesh.vertices[:,0],
mesh.vertices[:,1],
mesh.vertices[:,2],
triangles=mesh.faces,
linewidth=0.2,
alpha=0.7)

plt.show()

if __name__ == "__main__":
main()



Подробнее здесь: https://stackoverflow.com/questions/798 ... ted-suface
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»