Computer Tomography

In this example, we use data from a computer tomography (CT) study of a cadaver head: https://graphics.stanford.edu/data/voldata/

[1]:
import libcarna

Get the data:

[2]:
data = libcarna.data.cthead()
data.shape, data.dtype
[2]:
((256, 256, 99), dtype('uint16'))

The data is 256 × 256 × 99 pixels (uint16).

Maximum Intensity Projection

We rotate the head so that it stands upright:

[3]:
GEOMETRY_TYPE_VOLUME = 2

# Create and configure frame renderer
mip = libcarna.mip(GEOMETRY_TYPE_VOLUME, sr=400)
r = libcarna.renderer(600, 450, [mip])

# Create and configure scene
root = libcarna.node()

volume = libcarna.volume(
    GEOMETRY_TYPE_VOLUME,
    data,
    parent=root,
    spacing=(1, 1, 2),
).rotate('x', 90).rotate('z', 90)

camera = libcarna.camera(
    parent=root,
).frustum(fov=90, z_near=10, z_far=1000).translate(z=300)

# Render
libcarna.imshow(r.render(camera), mip.cmap.bar(volume))
[3]:
3272
2454
1636
818
0

The spatial structure of the 3D image is difficult to perceive from that rendering.

For a better visual perception, viewing the data from different angles is benefical, that can be achieved with as a subtle animation:

[4]:
# Render as animation
libcarna.imshow(
    libcarna.animate(
        libcarna.animate.rotate_local(camera),
        n_frames=100,
    ).render(r, camera),
    mip.cmap.bar(volume),
)
[4]:
3272
2454
1636
818
0

Hounsfield Unit Normalization

The data from CT scanners usually comes in Hounsfield Units (HU), that range from -1024 to +3071. In the HU scale, air roughly corresponds to -1000 HU, water to 0 HU, and bone tissue to +1000 HU. The image intensities in this dataset are not normalized to the HU scale.

Normalization of CT data to the HU scale is beneficial, because it permits direct identification of air, water-rich tissue, and bone tissue. With HU-normalized images, we can also render Digitally Reconstructed Radiographs (DRR), as shown further below.

LibCarna-Python provides a heuristic method for approximative normalization of CT data to the HU scale, that is based on the histogram of the intensisty values:

[5]:
data_hu = libcarna.normalize_hounsfield_units(data)

We then define a renderable volume with the HU-normalized image intensities:

[6]:
GEOMETRY_TYPE_HU_VOLUME = 3

hu_volume = libcarna.volume(
    GEOMETRY_TYPE_HU_VOLUME,
    data_hu,
    units='hu',
    parent=root,
    spacing=volume.spacing,
    local_transform=volume.local_transform,
    normals=True,
)

Note that units='hu' is needed to correctly interpret the intensities in data_hu. We also employ normals=True to pre-compute the normal vectors of the data (see below).

Direct Volume Rendering

In a Direct Volume Rendering (DVR), surfaces are rendered by simulation of the absorption of light. This simulation is most realstic, when the spatial orientation of the surfaces can be taken into account, which requires that the normals of the volume have been computed (this is why we used normals=True when we created the volume node).

We use a ramp function to strip out the air from the visualization, and we use the hu_volume.normalized auxiliary function to directly supply the HU values for the ramp function:

[7]:
dvr = libcarna.dvr(
    GEOMETRY_TYPE_HU_VOLUME, sr=800, transl=1, diffuse=0.8,
)
dvr.cmap('BrBG', ramp=hu_volume.normalized((0, 100)))

libcarna.imshow(
    libcarna.animate(
        libcarna.animate.rotate_local(camera),
        n_frames=100,
    ).render(
        libcarna.renderer(600, 450, [dvr]),
        camera,
    ),
    dvr.cmap.bar(hu_volume),
)
[7]:
3.1k
2.0k
1.0k
0
-1.0k

Digitally Reconstructed Radiographs

Digitally Reconstructed Radiographs (DRRs) are 2D images created from 3D data, like CT scans, to simulate what a real X-ray image would look like:

[8]:
libcarna.imshow(
    libcarna.animate(
        libcarna.animate.rotate_local(camera),
        n_frames=100,
    ).render(
        libcarna.renderer(
            600, 450, [
                libcarna.drr(
                    GEOMETRY_TYPE_HU_VOLUME, sr=800, inverse=True,
                )
            ],
            bgcolor=libcarna.color.WHITE_NO_ALPHA,
        ),
        camera,
    ),
)
[8]: