Visualizando imagens médicas com C++ Carlos Eduardo

Propaganda
Visualizando imagens médicas com C++
Carlos Eduardo Gesser
Sobre esta apresentação
●
Apresentação de desafios técnicos na visualização de imagens
médicas
●
Demonstração de como C++ nos permite resolver estes problemas,
utilizando abstrações de alto nível com nenhum (ou pouco) impacto
no desempenho
●
Técnicas aqui apresentadas podem ser aplicadas em outros domínios
●
Disclaimer:
○ Os trechos de código a seguir são simplificações do código
original, com o intuito de facilitar a apresentação.
○
2
Aspectos como tratamento de erros e tratamento de
concorrência foram abstraídos.
Sobre a Pixeon
3
●
Uma das maiores empresas brasileiras de tecnologia para a saúde.
Surgiu em 2012, a partir da fusão das empresas Pixeon, de
Florianópolis e Medical Systems, de São Bernardo do Campo.
●
Os sistemas da empresa estão presentes em mais de 2000 clientes,
compreendendo hospitais, clínicas e centros de diagnóstico de todos
os Estados Brasileiros e alguns países da América Latina.
●
No portfólio destacam-se soluções de gestão clínica e hospitalar,
processamento e distribuição de exames e telediagnóstico, aplicados
aos segmentos de Radiologia, Oftalmologia, Análises Clínicas, Gestão
de Impressão Médica e Captura de Imagens Médicas, etc.
Sobre Mim
●
1999-2003
○
4
Bacharelado em Ciências da Computação - UFSC
GALS - Gerador de Analisadores Léxicos e Sintáticos
●
2000-2007
Laboratório G-Sigma (Eng. Mec./Elétrica/Automação)
●
2004-2006
Mestrado em Engenharia Elétrica - UFSC
●
2007-
Pixeon
Sobre o Visualizador de Imagens Médicas
Arya
5
●
Em desenvolvimento desde 2009
○ Primeira versão liberada em 2011
●
Desenvolvido em C++ (~11) para Windows, Linux e Mac
○ Visual Studio, GCC e Clang
●
GUI em Qt
●
Boost (smart pointers, threads, function, bind, array, optional,
variant, filesystem, asio, lexical_cast, string algorithms, iostream)
Sobre o Visualizador de Imagens Médicas
6
Visualização de Imagens Médicas
7
●
Requisito primordial: Fidelidade
○ Médico precisa confiar nas informações
e nas imagens que está vendo
●
Requisito inescapável: Desempenho
○ Exames possuem grandes quantidades de imagens
○ Manipulação de imagens é computacionalmente intensiva
○ Médicos ganham por produtividade…
●
C++ permite que se atenda ambos, com abstrações de alto
nível e ainda assim garantindo o desempenho
Imagens médicas DICOM
● DICOM (Digital Imaging and Communications in
Medicine): Padrão para manipular, armazenar,
imprimir e transmitir informações em medicina de
imagem (http://dicom.nema.org).
● São definidos:
○ Serviços
○ Protocolo de comunicação
○ Formato de arquivo
8
Fluxo das imagens médicas
PACS
Laudo
Picture Archiving and
Communication System
Equipamento
9
Visualizador
Imagens médicas DICOM
10
●
Arquivos de imagem contém:
○ Metadados (cabeçalhos)
○ Dados (imagem)
●
Metadados:
○ Informações do paciente
○ Informações do exame
○ Informações da imagem
○ Etc.
●
Pixel Data em diversos formatos possíveis:
○ Grayscale 8 a 16 bits, RGB, etc…
○ Com ou sem compressão (JPEG, etc.)
DICOM Data Set
11
●
Metadados são agrupados em Data Sets
●
Um Data Set representa uma instância de um objeto de informação do mundo
real. Ex.: imagem.
●
Cada arquivo DICOM contém um (e apenas um) Data Set
●
Data Set é um conjunto de Data Elements (Cabeçalhos)
●
Data Element é composto por Tag de identificação e conteúdo
●
Dados possuem codificação variada
Cabeçalhos DICOM
Data Set DICOM
Data
Element
Tag
Data
Element
VR
Data
Element
Value
Length
Data
Element
Value
2 ou 4 bytes
Tipo / Formato do valor, 2 bytes.
Ausente em alguns casos
Grupo + Elemento, 4 bytes
12
...
Data
Element
Pixel Data
Element
Conteúdo de um Data Element
13
●
Tag
○ Identifica unicamente um elemento
○ Composto por dois números de 16 bits cada: Group e Element
○ Elementos são dispostos no arquivo dicom em ordem crescente
●
Value Representation (VR)
○ Identifica o tipo de dado do elemento
○ Definido com código de 2 caracteres
○ Não presente em alguns casos (uso de dicionário)
●
Length
○ Tamanho do conteúdo do elemento
○ Tamanho sempre é divisível por 2
●
Value
○ Conteúdo propriamente dito
Cabeçalhos DICOM - Estrutura de Dados
class DataElement
{
uint16_t m_group;
uint16_t m_element;
char m_vr[2];
uint32_t m_length;
unsigned char* m_data;
// Funções de manipulação/acesso
};
14
Cabeçalhos DICOM - Estrutura de Dados
class DicomFile
{
using DataElementList = std::vector<std::unique_ptr<DataElement>>;
DataElementList m_elements; // Cabeçalhos
ManagedPointer m_pixel_data; // Imagem
public:
const DataElement *find_data_element(uint16_t group,
uint16_t element) const;
const DataElement *find_data_element(const char *name) const;
// Demais funções de manipulação/acesso
};
15
Manipulação de Cabeçalhos DICOM
DicomFile file = …
const DataElement *el = file.find_data_element("StudyDescription");
std::string descr(el->data(), el->length());
const DataElement *el = file.find_data_element("Rows" );
unsigned short rows = *reinterpret_cast<unsigned short*>(el->data());
const DataElement *el = file.find_data_element(0x0020, 0x1041); // Posição da fatia
float slice_loc = std::stof( std::string(el->data(), el->length()) );
16
Manipulação de Cabeçalhos DICOM
DicomFile file = …
const DataElement *el = file.find_data_element("StudyDescription");
std::string descr(el->data(), el->length());
// E se “el” for nulo?
// E o padding?
const DataElement *el = file.find_data_element("Rows" );
unsigned short rows = *reinterpret_cast<unsigned short*>(el->data());
// O endianess está correto?
// unsigned short é o tipo certo?
const DataElement *el = file.find_data_element(0x0020, 0x1041); // Posição da fatia
float slice_loc = std::stof( std::string(el->data(), el->length()) );
// Neste caso era um número codificado como string
17
Manipulação de Cabeçalhos DICOM
DicomFile file = …
const DataElement *el = file.find_data_element("StudyDescription");
std::string descr(el->data(), el->length());
// E se “el” for nulo?
// E o padding?
const DataElement *el = file.find_data_element("Rows" );
unsigned short rows = *reinterpret_cast<unsigned short*>(el->data());
// O endianess está correto?
// unsigned short é o tipo certo?
const DataElement *el = file.find_data_element(0x0020, 0x1041); // Posição da fatia
float slice_loc = std::stof( std::string(el->data(), el->length()) );
// Neste caso era um número codificado como string
18
Value Representation
19
AS
Age String
Idade no formato NNNU
018M, 036Y
DA
Date
Data no formato YYYYMMDD
19851026
DS
Decimal String
Número decimal, como string
42, 3.1415
FL
Float Single
Ponto flutuante 32 bits, binário
0x40490fdb
IS
Integer String
Número inteiro, como string
42
LO
Long String
String de até 64 caracteres
...
SS
Signed Short
Inteiro de 16 bits com sinal, binário
0x2A00
PN
Person Name
Nome de pessoa, componentes separados por ^
Doe^John
SQ
Sequence
Data Set Recursivo
TM
Time
Hora no formato HHMMSS
012200
US
Unsigneg Short
Inteiro de 16 bits sem sinal, binário
0x2A00
Melhorando a Manipulação de Cabeçalhos DICOM
//Value Representations
template<typename Internal, typename External>
struct ValRep{
using raw_type = Internal;
using type = External;
};
//Long String
using LO = ValRep<char*, std::string>;
//Decimal String
using DS = ValRep<char*, float>;
//Unsigned Short
using US = ValRep<uint16_t, int>;
//...
20
Dicionário DICOM
(0002,0010)
(0008,0020)
(0008,0030)
(0008,1030)
(0010,0010)
(0010,0020)
(0010,1005)
(0010,1010)
(0018,0015)
(0020,0032)
(0020, 1041)
(0028,0002)
(0028,0030)
(0028,0100)
(0028,0010)
(0028,0011)
...
21
UI
DA
TM
LO
PN
LO
PN
AS
CS
DS
DS
US
DS
US
US
US
Transfer​SyntaxUID
StudyDate
StudyTime
StudyDescription
PatientName
PatientID
PatientBirthName
PatientAge
BodyPartExamined
ImagePositionPatient
SliceLocation;
SamplesPerPixel
PixelSpacing
BitsAllocated
Rows
Columns
Melhorando a Manipulação de Cabeçalhos DICOM
template<uint16_t G,uint16_t E, typename VR>
struct DicomHeader : GenericDicomHeader<VR> { ... };
//Dicionário
using StudyDescription = DicomHeader<0x0008, 0x1030, LO>;
using SliceLocation
= DicomHeader<0x0020, 0x1041, DS>;
using Rows
= DicomHeader<0x0028, 0x0010, US>;
...
22
Melhorando a Manipulação de Cabeçalhos DICOM
template<typename VR>
struct GenericDicomHeader {
using internal_type = typename VR::raw_type;
using value_type
= typename VR::type;
const DataElement *element;
explicit operator bool() const { return element != nullptr; }
value_type value_or(const value_type v) const {
return !element ? v : *(*this);
}
value_type operator *() const {
return Converter<value_type, internal_type>()(element);
}
};
23
Melhorando a Manipulação de Cabeçalhos DICOM
template<typename Out, typename In>
struct Converter;
template<>
struct Converter<char*, std::string>{
std::string operator()(const DataElement *el) {
return std::string(el->data(), el->length());
}
};
template<>
struct Converter<int, uint16_t>{
int operator()(const DataElement *el) {
return *reinterpret_cast<uint16_t*>(el->data());
}
};
...
24
Melhorando a Manipulação de Cabeçalhos DICOM
template<typename HEADER>
HEADER get(const DicomFile &file)
{
return { file.find_data_element(HEADER::group,
HEADER::element) };
}
25
Melhorando a Manipulação de Cabeçalhos DICOM
DicomFile file = …
std::string descr = get<StudyDescription>(file).value_or("");
int rows = *get<Rows>(file);
if (auto pos = get<SliceLocation>(file)) {
float x = *pos;
...
}
else {
...
}
26
Melhorando a Manipulação de Cabeçalhos DICOM
● Prós
○ Garantia no acesso aos cabeçalhos
○ Agilidade no desenvolvimento
● Contras
○ Tempo de compilação
○ Arquivos malformados ainda são problema
27
Gerenciamento de Memória
28
●
Exame normal de Tomografia:
○ Imagem = 512 x 512 x 16bits = 500k + Cabeçalhos
○ 500 imagens = ~ 250 mega
●
Usuário necessita manter diversos exames abertos
●
Memória física se esgota com poucos exames
●
Necessária solução
para disco (swap)
de
offload
de
blocos
de
memória
Gerenciamento de Memória
unsigned char* ptr = new unsigned char[size];
save_to_disk(ptr);
//memória apontada por ptr é deletada?
//...
ptr = load_from_disk(ptr);
//ptr aponta para novo bloco de memória
//e outros ponteiros que apontavam para o mesmo lugar?
delete[] ptr;
//posso ou devo?
29
Gerenciamento de Memória
● Vamos criar um Smart Pointer para isso
● Gerenciamento transparente da alocação, swap e
desalocação
● Wrapper para std::shared_ptr
30
Gerenciamento de Memória
//Aloca um bloco de memória “swapável”
ManagedPointer ptr = SwapManager::alloc(size);
//Obtem o tamanho
ptr.size()
//Cópia com contagem de referência
auto ptr2 = ptr;
//Checa se é um ponteiro válido
if (ptr2) {
...
}
//Como acessar a memória em alocada?
31
Gerenciamento de Memória
//Aloca um bloco de memória “swapável”
ManagedPointer ptr = SwapManager::alloc(size);
//Obtem o tamanho
ptr.size()
//Cópia com contagem de referência
auto ptr2 = ptr;
//Checa se é um ponteiro válido
if (ptr2) {
...
}
//Como acessar a memória em alocada?
ptr.get() // Erro, operações
*ptr
// não disponíveis
32
Gerenciamento de Memória
33
●
ManagedPointer possui operações de
○ lock para garantir que dados estejam na memória
○ unlock para liberar dados para serem salvos em disco
●
E se alguém tentar acessar memória antes/depois do lock?
●
RAII - Resource Acquisition is Initialization
●
ManagedPointerLock: estrutura auxiliar para acesso à memória
dentro de um escopo
○ Construtor garante que memória esteja acessível
○ Destrutor devolve “controle” ao gerenciador
Gerenciamento de Memória
{
ManagedPointerLock lock(ptr);
//A partir daqui, é garantido que o block está na memória
unsigned char *data = lock.get();
unsigned short *data2 = lock.get_as<unsigned short>();
//disponível até o fim do escopo
}
//A partir daqui a memória de ptr pode
//ser movida para o arquivo, se necessário
34
Gerenciamento de Memória - Ilustração
Managed
Pointer
Managed
Pointer
Managed
Pointer
Managed
Pointer
35
Managed
Pointer
Heap
Gerenciamento de Memória - Ilustração
Managed
Pointer
Lock
Managed
Pointer
Managed
Pointer
Managed
Pointer
Managed
Pointer
36
Managed
Pointer
Heap
Gerenciamento de Memória - Ilustração
Managed
Pointer
Lock
Managed
Pointer
Managed
Pointer
Managed
Pointer
Lock
Managed
Pointer
37
Managed
Pointer
Managed
Pointer
Heap
Gerenciamento de Memória - Ilustração
Managed
Pointer
Lock
Managed
Pointer
Managed
Pointer
Managed
Pointer
Lock
Managed
Pointer
38
Managed
Pointer
Managed
Pointer
Heap
Gerenciamento de Memória - Ilustração
Managed
Pointer
Lock
Managed
Pointer
Managed
Pointer
Managed
Pointer
Lock
Managed
Pointer
Lock
39
Managed
Pointer
Managed
Pointer
Managed
Pointer
Heap
Gerenciamento de Memória - Ilustração
Managed
Pointer
Lock
Managed
Pointer
Managed
Pointer
Managed
Pointer
Lock
Managed
Pointer
Lock
40
Managed
Pointer
Managed
Pointer
Managed
Pointer
Heap
Gerenciamento de Memória - Ilustração
Managed
Pointer
Lock
Managed
Pointer
Managed
Pointer
Managed
Pointer
Lock
Managed
Pointer
Lock
41
Managed
Pointer
Managed
Pointer
Managed
Pointer
Heap
Gerenciamento de Memória - Implementação
class ManagedPointer {
friend class ManagedPointerLock;
std::shared_ptr<ManagedHandle> m_handle;
};
class ManagedPointerLock {
ManagedPointer & m_ptr;
public:
ManagedPointerLock(ManagedPointer& ptr) : m_ptr(ptr) {
m_ptr->m_handle->lock();
}
~ManagedPointerLock() {
m_ptr->m_handle->unlock();
}
unsigned char * get() {
return m_ptr->m_handle->get();
}
};
42
Gerenciamento de Memória - Implementação
class ManagedHandle {
friend class SwapManager;
bool m_locked;
bool m_in_memory;
unsigned char *m_data; //Dados, caso esteja em memória
std::size_t m_file_offset; //Offset, caso esteja em arquivo
void lock(){
SwapManager::ensure_memory(this); //Garante que dados estarão em m_data
m_locked = true;
}
void unlock(){
m_locked = false;
}
};
43
Gerenciamento de Memória - Implementação
class SwapManager {
friend class ManagedHandle;
//Lista dos handles que estão em memória, ordenada por acesso
std::vector<std::shared_ptr<ManagedHandle>> m_mem_handles,
//Lista dos handles que estão em disco
std::vector<std::shared_ptr<ManagedHandle>> m_swp_handles;
std::fstream m_file;
size_t m_free_mem;
//...
void ensure_memory(ManagedHandle *handle);
//...
};
44
Gerenciamento de Memória - Implementação
void SwapManager::ensure_memory(ManagedHandle *handle)
{
if (handle->m_in_memory) return;
for (auto &h : m_mem_handles)
{
if (m_free_mem < handle->size()) break;
if (h->m_locked) continue;
move_to_file(h.get());
}
move_to_memory(handle);
}
45
Gerenciamento de Memória
● Prós
○ Muito mais memória disponível para imagens
○ Sistema seguro e elegante de acesso
● Contras
○ Forma não trivial de acesso à memória
46
Manipulação de Imagens
47
●
Arquivo DICOM pode conter imagens em diversos formatos
●
Cabeçalhos relevantes para imagens
○ Transfer Syntax UID (0002,0010)
■ Compressão. Ex: JPEG
○ Rows (0028,0010) e Columns (0028,0011):
■ Dimensões da imagem
○ Bits Allocated (0028,0100):
■ Tamanho de cada canal (8 ou 16 bits)
○ Samples Per Pixel (0028,0002):
■ Número de canais da imagem (1 ou 3)
Manipulação de Imagens
●
48
Cabeçalhos que inficam como interpretar dados
○ Bits Stored (0028,0101):
■ Quantos bits são, de fato, utilizados
○ High Bit (0028,0102):
■ Alinhamento dos Bits Stored dentro dos Bits Allocated
○ Pixel Representation (0028,0103):
■ Indica se pixels podem ter valor negativo (1) ou não (0)
○ Photometric Interpretation (0028,0004):
■ Interpretação dos canais da imagem
● MONOCHROME1 (branco → preto)
● MONOCHROME2 (preto → branco)
● RGB
● …
Manipulação de Imagens
Exemplo:
Pixel Data = Array de bytes
Bits Allocated = 16
Bits Stored = 12, High Bit = 11
49
Manipulação de Imagens
Após processamento inicial, tudo é normalizado para 8, 16 ou 24 bits
Imagem 8 bits
Imagem 16 bits
Imagem 24 bits (RGB)
50
Manipulação de Imagens
class ImageRaw
{
ManagedPointer m_data;
std::size_t m_width;
std::size_t m_height;
Depth m_depth;
};
enum Depth { depth8
= 8, depth16 = 16, depth24 = 24 };
using pixel8 = unsigned char;
using pixel16 = unsigned short;
using pixel24 = std::array<pixel8, 3>;
51
Manipulação de Imagens
pixeon::ManagedLock lock(image.getData());
for (std::size_t x=0; x<image.witdh(); ++x) {
for (std::size_t y=0; y<image.height(); ++y) {
}
}
52
Manipulação de Imagens
pixeon::ManagedLock lock(image.getData());
for (std::size_t x=0; x<image.witdh(); ++x) {
for (std::size_t y=0; y<image.height(); ++y) {
auto offset = x + y * image.witdh();
}
}
53
Manipulação de Imagens
pixeon::ManagedLock lock(image.getData());
for (std::size_t x=0; x<image.witdh(); ++x) {
for (std::size_t y=0; y<image.height(); ++y) {
auto offset = x + y * image.witdh();
if (image.depth() == depth8) {
}
else if (image.depth() == depth16) {
}
else /* if (image.depth() == depth24) */ {
}
}
}
54
Manipulação de Imagens
pixeon::ManagedLock lock(image.getData());
for (std::size_t x=0; x<image.witdh(); ++x) {
for (std::size_t y=0; y<image.height(); ++y) {
auto offset = x + y * image.witdh();
if (image.depth() == depth8) {
auto pixel = lock.get_as<pixel8>()[offset];
...
}
else if (image.depth() == depth16) {
auto pixel = lock.get_as<pixel16>()[offset];
...
}
else /* if (image.depth() == depth24) */ {
auto pixel= lock.get_as<pixel24>()[offset];
...
}
...
}
}
55
Manipulação de Imagens
pixeon::ManagedLock lock(image.getData());
for (std::size_t x=0; x<image.witdh(); ++x) {
for (std::size_t y=0; y<image.height(); ++y) {
auto offset = x + y * image.witdh();
if (image.depth() == depth8) {
auto pixel = lock.get_as<pixel8>()[offset];
...
}
else if (image.depth() == depth16) {
auto pixel = lock.get_as<pixel16>()[offset];
...
}
else /* if (image.depth() == depth24) */ {
auto pixel= lock.get_as<pixel24>()[offset];
...
}
...
}
}
56
Preferível evitar
branching em um
loop em uma
imagem de 20M
pixels
Manipulação de Imagens
pixeon::ManagedLock lock(image.getData());
if (image.depth() == depth8) {
for (std::size_t x=0; x<image.witdh(); ++x) {
for (std::size_t y=0; y<image.height(); ++y) {
auto offset = x + y * image.witdh();
auto pixel = lock.get_as<pixel8>()[offset];
...
}
}
}
else if (image.depth() == depth16) {
for (...
}
else /* if (image.depth() == depth24) */ {
for (...
}
57
Manipulação de Imagens
pixeon::ManagedLock lock(image.getData());
if (image.depth() == depth8) {
for (std::size_t x=0; x<image.witdh(); ++x) {
for (std::size_t y=0; y<image.height(); ++y) {
auto offset = x + y * image.witdh();
auto pixel = lock.get_as<pixel8>()[offset];
...
}
}
}
else if (image.depth() == depth16) {
for (...
}
else /* if (image.depth() == depth24) */ {
for (...
}
58
Agora o código
está replicado
em três lugares
Manipulação de Imagens
template<typename PixelType>
class ImageView
{
const PixelType *m_data;
std::size_t m_width, m_height;
public:
ImageView(const PixelType *data, std::size_t width, std::size_t height);
inline const PixelType &operator[](unsigned index) const {
return m_data[index];
}
inline const PixelType *data() const { return m_data; }
...
};
60
Manipulação de Imagens
template<typename Func>
inline void execute_over_image(const ImageRaw &image, Func &&func)
{
ManagedPointer img_ptr = image->data();
ManagedPointerLock lock(img_ptr);
switch(depth) {
case depth8:
func(ImageView<pixel8>(reinterpret_cast<const pixel8*>(lock.get()),
image->width(), image->height()));
break;
case depth16:
func(ImageView<pixel16>(reinterpret_cast<const pixel16*>(lock.get()),
image->width(), image->height()));
break;
case depth24:
func(ImageView<pixel24>(reinterpret_cast<const pixel24*>(lock.get()),
image->width(), image->height()));
break;
}
}
59
Manipulação de Imagens - Exemplo
struct DumpToFile
{
std::ofstream file;
DumpToFile(const char *filename) : file(filename, std::ios::binary) {}
template <typename Pixel>
void operator()(ImageView<Pixel> view) const
{
const char *fdata = reinterpret_cast<const char*>(view.data());
file.write( fdata, sizeof(Pixel) * view.size() );
}
};
execute_over_image(image, DumpToFile("image.bin"));
61
Manipulação de Imagens - Exemplo
int min = 999999, max = 0;
execute_over_image(image, [](const auto &view) {
for ( unsigned i = 0; i < image.size(); ++i ) {
const int pix = get_lum( view[i] );
min = std::min(pix, min);
max = std::max(pix, max);
}
});
template<typename PixelType>
inline typename int get_lum(PixelType p) {
return p;
}
template<>
inline int get_lum(pixel24 p) {
return (306 * p[0] + 601 * p[1] + 116 * p[2]) / 1024;
}
62
Manipulação de Imagens - Exemplo
struct BlurAlgorithm
{
template <typename Pixel>
void operator()(ImageView<Pixel> in,
WritableImageView<Pixel> out) const
{
//algoritmo para imagens de 1 canal
}
void operator()(ImageView<pixel24> in,
WritableImageView<pixel24> out) const
{
//algoritmo para imagens de 3 canais
}
};
transform_image(input, output, BlurAlgorithm());
63
Manipulação de Imagens
● Prós
○ Branching exteriorizado
○ Tratamento uniforme para os 3 formatos
● Contras
○ Fazer operações sobre imagens fica um pouco
mais complicado do que um simples loop
64
Obrigado.
Bonus Slides!
Reconstrução Multiplanar - MPR
Geração de novas imagem a partir de uma pilha de imagens originais
2
Reconstrução Multiplanar
●
●
Geração de imagem
parametrizada por coordenadas
do plano de corte e espessura
Cada pixel da imagem resultante calculado como:
○ Média dos pixels (AVG)
○ Maior valor de pixel (MIP)
○ Menor valor de pixel (MinIP)
Reconstrução Multiplanar
void render(...) {
...
for (int x = 0; x<output_witdh; ++x) {
for (int y = 0; y<output_height; ++y) {
int value = (mpr_type == MinIP) ? 99999 : 0;
for (int z = -thickness/2; z<thickness/2; ++z) {
int pixel = volume[ transform(x, y, z) ];
if
(mpr_type == AVG) value += pixel;
else if (mpr_type == MIP) value = std::max(value, pixel);
else
value = std::min(value, pixel);
}
if (mpr_type == AVG) value /= thickness;
result[ {x, y} ] = value;
}
}
...
}
Reconstrução Multiplanar
void render(...) {
...
for (int x = 0; x<output_witdh; ++x) {
for (int y = 0; y<output_height; ++y) {
int value = (mpr_type == MinIP) ? 99999 : 0;
for (int z = -thickness/2; z<thickness/2; ++z) {
int pixel = volume[ transform(x, y, z) ];
if
(mpr_type == AVG) value += pixel;
else if (mpr_type == MIP) value = std::max(value, pixel);
else
value = std::min(value, pixel);
}
if (mpr_type == AVG) value /= thickness;
result[ {x, y} ] = value;
}
}
...
}
Reconstrução Multiplanar
template<typename Acc >
void render(...)
{
...
for (int x = 0; x<output_witdh; ++x)
{
for (int y = 0; y<output_height; ++y)
{
int value = Acc::initialize();
for (int z = -thickness/2; z<thickness/2; ++z)
{
int pixel = volume[ transform(x, y, z) ];
value = Acc::accumulate(value, pixel);
}
result[ {x, y} ] = Acc::finalize(value, thickness);
}
}
...
}
Reconstrução Multiplanar
struct AvgAcc
{
static int initialize() { return 0; }
static int accumulate(int value, int pixel) { return value + pixel; }
static int finalize(int value, int thickness) { return value / thickness; }
};
struct MipAcc
{
static int initialize() { return 0; }
static int accumulate(int value, int pixel) { return std::max(value, pixel); }
static int finalize(int value, int thickness) { return value; }
};
struct MinipAcc
{
static int initialize() { return 99999; }
static int accumulate(int value, int pixel) { return std::min(value, pixel); }
static int finalize(int value, int thickness) { return value; }
};
Reconstrução Multiplanar
if (mpr_type == AVG)
{
render<AvgAcc>(...);
}
else if (mpr_type == MIP)
{
render<MipAcc>(...);
}
else
{
render<MinipAcc>(...);
}
Download