RasmahRasmah

Volumes and point clouds

Overview

Two data families complete Rasmah's file I/O. Scalar volumes represent a field over a regular 3D grid — the natural form for medical imaging, where a CT or MRI scan is a stack of grayscale slices. Point clouds represent raw scan data — a set of points, optionally with normals, from a laser or photogrammetry scan.

Both enter through the implicit (SDF) path rather than the BRep path: a volume is sampled into a level set, and a point cloud is reconstructed into a surface, each becoming a mesh you can simulate.

Scalar volumes (DICOM & NIfTI)

A DicomVolume is a regular grid of values plus the voxel spacing and the origin of the first voxel. It is callable (trilinearly interpolating the field) and has a bounding box:

Rasmah.DicomVolumeType
DicomVolume(values, spacing, origin)

A regular 3D grid of scalar values values (indexed [rows, cols, frames]) with per-axis voxel spacing and the origin (centre of the first voxel). The volume is callable at a physical point via trilinear interpolation, and segment turns it into an implicit field for meshing.

Fields

  • values: the 3D voxel grid.
  • spacing: voxel size per axis (millimetres when read from DICOM).
  • origin: position of the first voxel centre.
source
vol = DicomVolume(zeros(4, 4, 4), [1.0, 1.0, 1.0], [0.0, 0.0, 0.0])
bounds(vol)
([-0.5, -0.5, -0.5], [3.5, 3.5, 3.5])

DICOM (ISO 12052) is the medical-imaging exchange format; NIfTI is its research-side counterpart. Rasmah maps both onto the same DicomVolume:

Rasmah.read_dicomFunction
read_dicom(io) -> DicomVolume
read_dicom(path) -> DicomVolume

Read one DICOM file (Explicit or Implicit VR Little Endian, monochrome 8-/16-bit pixel data) into a DicomVolume.

source
Rasmah.read_dicom_seriesFunction
read_dicom_series(paths) -> DicomVolume
read_dicom_series(dir; ext=".dcm") -> DicomVolume

Assemble a DICOM series of 2D slices into one DicomVolume, sorted by image position and stacked along z. The z spacing is the median inter-slice distance (or the in-file slice spacing for a single slice).

source
Rasmah.write_dicomFunction
write_dicom(vol, io; instance_number=1, series_number=1) -> io
write_dicom(vol, path; kwargs...) -> path

Write a DicomVolume as a single DICOM file (CT storage, Explicit VR Little Endian, 16-bit monochrome pixel data).

source
Rasmah.write_dicom_seriesFunction
write_dicom_series(vol, dir) -> dir

Write each frame of a DicomVolume as a separate DICOM slice file (slice_0001.dcm, slice_0002.dcm, …) in dir, offsetting each slice along z by the voxel spacing.

source
Rasmah.read_niftiFunction
read_nifti(path)
read_nifti(io::IO)

Read a NIfTI-1/.nii/.nii.gz volume, returning a DicomVolume (or a Vector{DicomVolume} for 4D time series).

source
Rasmah.write_niftiFunction
write_nifti(vol::DicomVolume, path)
write_nifti(vol::DicomVolume, io::IO)

Write a DicomVolume as a single-file NIfTI-1 .nii (float64 voxels).

source

A volume becomes mesheable geometry in two steps — threshold it into a level set and convert that to an SDF:

Rasmah.segmentFunction
segment(vol, threshold; β=nothing) -> ThresholdField

A level-set field whose zero set is the vol == threshold isosurface. With β = nothing the field is the hard level set threshold - vol; a positive β gives the smooth sigmoid σ((threshold - vol)/β) - 1/2, differentiable in the threshold and volume.

source
Rasmah.sample_gridFunction
sample_grid(f, lo, hi, dims) -> DicomVolume

Sample a callable f(x) on a regular dims = (nx, ny, nz) grid over the box [lo, hi], returning the result as a DicomVolume with the implied spacing and origin.

source
vol = DicomVolume(zeros(8, 8, 8), [1.0, 1.0, 1.0], [0.0, 0.0, 0.0])
seg = segment(vol, 0.5)
bounds(seg)
([-0.5, -0.5, -0.5], [7.5, 7.5, 7.5])

Point clouds

Point-cloud readers and writers cover the common scan formats:

Rasmah.read_xyzFunction
read_xyz(path) -> PointCloud

Read a plain ASCII XYZ point-cloud file (one x y z per line, optionally followed by nx ny nz normals) into a PointCloud.

source
Rasmah.write_xyzFunction
write_xyz(points, io; normals = nothing)
write_xyz(points, path; normals = nothing)

Write a 3×n point matrix (optionally with normals) as a plain ASCII XYZ file.

source
Rasmah.read_pcdFunction
read_pcd(path) -> PointCloud
read_pcd(io) -> PointCloud

Read a PCL Point Cloud Data (PCD) file (ASCII, binary, or LZF-compressed) into a PointCloud.

source
Rasmah.write_pcdFunction
write_pcd(points, io; normals = nothing, binary = false)
write_pcd(points, path; normals = nothing, binary = false)

Write a 3×n point matrix (optionally with normals) as a PCD file, ASCII by default or binary with binary = true.

source
Rasmah.read_pointcloudFunction
read_pointcloud(path) -> PointCloud

Read any supported point-cloud file (.xyz/.txt, .pcd, .ptx, .las, .ply, .obj, .stl), dispatching on the file extension. Mesh formats contribute their vertices only.

source

A point cloud is a PointCloud (a 3×N matrix of points, optionally with normals); the reconstruction and primitive-fitting machinery that turns it into a mesh lives in the Meshing chapters.

Time series (PVD)

A sequence of meshes — an animation or a transient simulation — is written as a ParaView Data (.pvd) collection, which the interactive viewer can play:

Rasmah.write_pvdFunction
write_pvd(meshes, path; times=nothing, format=:ascii) -> path

Serialize a sequence of meshes (all TriangleMesh or all TetMesh) to a ParaView Data (.pvd) collection plus one .vtp/.vtu file per frame. times optionally supplies the timestep per frame.

source
Rasmah.read_pvdFunction
read_pvd(path) -> (dir, entries)

Read the manifest of a ParaView Data (.pvd) collection: the directory the frames live in plus the ordered (file, timestep) entries (parsing only the <DataSet> list, not geometry).

source

Next steps

With a volume or point cloud converted to a mesh, the natural follow-ons are the meshing and simulation chapters. For the triangle-mesh and exact-CAD formats, return to File I/O and Exact CAD exchange.