Um CT scan é, na sua essência, um array tridimensional onde cada voxel quantifica a atenuação de raios X em Unidades Hounsfield. Essa representação numérica padronizada é o que torna possível segmentar estruturas anatômicas, treinar redes neurais para detecção de lesões e construir pipelines reprodutíveis de análise, tudo a partir de operações sobre arrays NumPy.
Neste tutorial, você vai aprender a carregar um volume de tomografia computadorizada no Python usando SimpleITK, interpretar os metadados espaciais que conectam o array ao sistema de coordenadas do paciente e aplicar técnicas de windowing para visualizar diferentes estruturas na mesma fatia.
São as operações fundamentais que qualquer pipeline de visão computacional 3D em imagens médicas precisa dominar antes de partir para segmentação ou classificação.
Código do Artigo
Acesse o código-fonte deste artigo gratuitamente.
Informe seu email para acessar o código:
✓ Seu código está pronto!
Abrir no Google Colab →Bootcamp de Deep Learning e Visão Computacional
Quer aprender a construir um detector de câncer em tomografias 3D, do zero até o deploy? Compre agora o curso de Deep Learning e Visão Computacional 3D do Sigmoidal.
São 24 horas de aulas gravadas, disponíveis imediatamente após a compra, onde você vai construir um sistema completo de detecção de nódulos pulmonares usando PyTorch e o dataset LUNA16 (o maior benchmark público de CT de tórax), e realizar o deploy de um web app no Gradio..

Você vai aprender a manipular volumes 3D com SimpleITK, converter coordenadas do paciente para índices do array, extrair crops 3D ao redor de candidatos a nódulo, treinar uma CNN 3D com data augmentation, avaliar o modelo com métricas clínicas e fazer deploy com Gradio.
Se você quer trabalhar com visão computacional 3D aplicada a problemas reais de saúde, inscreva-se no Bootcamp. O único pré-requisito é saber programar Python.
O Que é uma Tomografia 3D?
Uma tomografia computadorizada (CT scan) é um exame que produz um volume 3D do interior do corpo. O equipamento dispara raios X de múltiplos ângulos e reconstrói uma série de fatias transversais do paciente.
Na prática, cada CT scan que você vai manipular no Python é um array NumPy tridimensional. Cada posição nesse array é um voxel (o equivalente 3D de um pixel), e o valor numérico armazenado é medido em Unidades Hounsfield (HU), uma escala física padronizada:
- Ar: −1000 HU
- Gordura: −120 a −60 HU
- Água: 0 HU
- Tecido mole (músculo, órgãos): +40 a +80 HU
- Pulmão inflado: −950 a −500 HU
- Osso compacto: +1000 HU ou mais
Diferente de imagens comuns (onde valores de pixel são arbitrários), em um CT scan cada número tem significado físico. É essa propriedade que permite isolar estruturas anatômicas com simples operações de limiar.
Carregando um CT Scan com SimpleITK
No dataset LUNA16, os CT scans são armazenados no formato MHD/RAW: um arquivo .mhd com os metadados (dimensões, espaçamento, origem) e um .raw com os voxels puros. No ecossistema médico em geral, o formato dominante é o DICOM, e em pesquisa também é comum encontrar NIfTI (.nii.gz). A biblioteca SimpleITK lê todos esses formatos e preserva as informações espaciais.
# instalar o SimpleITK !pip install SimpleITK -q
import numpy as np import matplotlib.pyplot as plt import SimpleITK as sitk
O sitk.ReadImage retorna um objeto Image com os metadados. Para obter o array NumPy, basta usar sitk.GetArrayFromImage:
# carregar o CT scan mhd_path = "data/1.3.6.1.4.1.14519.5.2.1.6279.6001.108197895896446896160048741492.mhd" ct_sitk = sitk.ReadImage(mhd_path) ct_array = sitk.GetArrayFromImage(ct_sitk)
Vamos inspecionar o que temos:
print(f"Shape: {ct_array.shape}")
print(f"Dtype: {ct_array.dtype}")
print(f"Dimensões: {ct_array.ndim}D")
Shape: (119, 512, 512) Dtype: int16 Dimensões: 3D
São 119 fatias axiais, cada uma com 512 x 512 pixels. O SimpleITK retorna o array na ordem (Z, Y, X), mas os metadados (GetSpacing, GetOrigin) seguem a convenção (X, Y, Z). Essa inversão é fonte comum de bugs, então fique atento.
Metadados Espaciais: Spacing e Origin
Além dos voxels, um CT scan carrega metadados espaciais que conectam o array ao mundo físico:
spacing = ct_sitk.GetSpacing()
origin = ct_sitk.GetOrigin()
print(f"Espaçamento (x, y, z): {spacing} mm")
print(f"Origem: {origin}")
Espaçamento (x, y, z): (0.742, 0.742, 2.5) mm Origem: (-182.5, -190.0, -313.75)
O espaçamento (spacing) diz a distância física, em milímetros, entre voxels consecutivos. Neste caso, cada pixel dentro de uma fatia mede 0.742 mm, mas a distância entre fatias é 2.5 mm. Isso significa que o volume é anisotrópico: a resolução não é uniforme em todas as direções.
A origem (origin) indica a posição do primeiro voxel no sistema de coordenadas do paciente. Ela é essencial para converter entre índices do array e coordenadas reais em milímetros.
Visualizando Fatias do Volume
A forma mais direta de visualizar um CT scan é exibir fatias individuais com matplotlib. Como cada fatia é um array 2D, basta indexar o primeiro eixo:
# exibir a primeira fatia
fatia = ct_array[0]
plt.imshow(fatia, cmap="gray")
plt.axis("off")
plt.show()

Para ter uma visão geral do volume, podemos exibir fatias espaçadas regularmente:
# exibir 5 fatias distribuidas ao longo do volume
n_fatias = ct_array.shape[0]
indices = [0, n_fatias // 4, n_fatias // 2, 3 * n_fatias // 4, n_fatias - 1]
fig, axes = plt.subplots(1, 5, figsize=(18, 4))
for ax, idx in zip(axes, indices):
ax.imshow(ct_array[idx], cmap="gray")
ax.set_title(f"Fatia {idx}")
ax.axis("off")
plt.tight_layout()
plt.show()

As fatias extremas (0 e 118) mostram pouca anatomia. Conforme avançamos para o centro do volume, pulmões, costelas e coluna vertebral aparecem com clareza.
Windowing: Como Radiologistas Enxergam a Tomografia
Aqui está o conceito que separa quem apenas abre uma imagem médica de quem realmente entende o que está vendo.
Um CT scan tem uma faixa dinâmica enorme. Neste volume, os valores vão de −2048 a +3071 — o que corresponde a uma representação em 12 bits com offset (o valor exato depende do rescale intercept e rescale slope definidos nos metadados DICOM originais). Se você mapear todos esses valores para uma escala de cinza linear, quase todas as estruturas ficam invisíveis. A solução é o windowing (ou janelamento): você escolhe um centro (center) e uma largura (width), e todos os valores fora dessa janela são cortados.
É exatamente o que radiologistas fazem quando alternam entre “janela de pulmão”, “janela de mediastino” e “janela de osso”.
def aplicar_janela(img, center, width):
"""Aplica windowing a uma imagem de CT scan."""
lower = center - width // 2
upper = center + width // 2
return np.clip(img, lower, upper)
A implementação é um simples np.clip. Note que a função retorna os valores em HU já recortados pela janela, e o imshow do Matplotlib normaliza automaticamente para a escala de cinza na exibição. Em um pipeline de produção, você normalizaria explicitamente para [0, 1] ou [0, 255] antes de alimentar um modelo. Veja o efeito na mesma fatia:
fatia_meio = ct_array[ct_array.shape[0] // 2]
janelas = {
"Pulmao (C:-600, W:1500)": (-600, 1500),
"Mediastino (C:40, W:400)": (40, 400),
"Osso (C:400, W:1800)": (400, 1800),
}
fig, axes = plt.subplots(1, 3, figsize=(14, 5))
for ax, (nome, (center, width)) in zip(axes, janelas.items()):
img = aplicar_janela(fatia_meio, center, width)
ax.imshow(img, cmap="gray")
ax.set_title(nome)
ax.axis("off")
plt.tight_layout()
plt.show()

A mesma fatia, três visualizações completamente diferentes:
- Janela de pulmão (C:−600, W:1500): evidencia o parênquima pulmonar e as vias aéreas
- Janela de mediastino (C:40, W:400): revela tecidos moles, vasos e órgãos
- Janela de osso (C:400, W:1800): destaca a estrutura esquelética
Planos Anatômicos: Axial, Sagital e Coronal
Um CT scan 3D pode ser “fatiado” em três planos ortogonais, cada um revelando a anatomia de uma perspectiva diferente:
d, h, w = ct_array.shape
axial = ct_array[d // 2] # fatia no meio do eixo Z
sagital = ct_array[:, :, w // 2] # fatia no meio do eixo X
coronal = ct_array[:, h // 2, :] # fatia no meio do eixo Y
fig, axes = plt.subplots(1, 3, figsize=(14, 5))
axes[0].imshow(axial, cmap="gray")
axes[0].set_title("Axial")
axes[1].imshow(sagital, cmap="gray")
axes[1].set_title("Sagital")
axes[2].imshow(coronal, cmap="gray")
axes[2].set_title("Coronal")
for ax in axes:
ax.axis("off")
plt.tight_layout()
plt.show()

Perceba como as vistas sagital e coronal parecem “esticadas” verticalmente. Isso não é um bug. Lembra do espaçamento anisotrópico? A distância entre fatias (2.5 mm) é 3.4x maior que a resolução dentro de cada fatia (0.742 mm). Por isso, quando recortamos nos eixos sagital ou coronal, a imagem fica distorcida. Corrigir isso exige um resampling para espaçamento isotrópico, que é tema para o próximo tutorial.
Takeaways
- CT scans são arrays 3D com significado físico: cada voxel mede a atenuação de raios X em Unidades Hounsfield. Ar vale −1000, água vale 0, osso compacto vale +1000 ou mais.
- Metadados espaciais importam: o espaçamento e a origem conectam o array ao mundo real em milímetros. Ignorar esses dados é fonte comum de erro.
- Windowing é essencial para visualização: a mesma fatia revela pulmão, tecidos moles ou osso dependendo da janela aplicada. Em produção, normalize para [0, 1] após o clip.
- Três planos anatômicos: axial, sagital e coronal permitem visualizar o volume de diferentes perspectivas. A anisotropia distorce os planos sagital e coronal quando o espaçamento entre fatias é diferente da resolução dentro de cada fatia.















