poniedziałek, 26 stycznia 2009

Komponenty DICOMowe dla Qt

0 komentarze
Już jakiś czas temu zastanawiałem się jak z poziomu Qt odczytywać i wyświetlać obrazy medyczne. Oczywiście nie ma sensu samemu implementować obsługi DICOMa, tylko najlepiej skorzystać z gotowej biblioteki, która dysponuje już taką funkcjonalnością.

Kilka dni temu odpytując ponownie Googla jak sobie poradzić z tym problemem, natrafiłem na gotowe rozwiązanie. Na SourceForge powstał projekt Qt4 Dicom Widget set, będący zbiorem komponentów dla Qt. Składa się on z następujących części:
  • QdicomDirListWidget - pokazuje informacje o pacjentach zawarte w DICOMDIR
  • QdicomIconListWidget - wyświetla miniaturki wszystkich zdjęć z wczytanego zbioru
  • QdicomImageWidget - pozwala wyświetlać zarówno pojedyncze obrazy, jak i obrazy zapisane w postaci serii plików (tryb video)
Po skompilowaniu i zainstalowaniu komponentów można z nich korzystać z poziomu QtDesigner.

Poniżej przykład możliwości omawianych komponentów. Można go znaleźć w katalogu qdcws-1.0.1/designer.

sobota, 24 stycznia 2009

VolView w końcu na Linuksa

0 komentarze
VolView, jedno z najlepszych narzędzi do wizualizacji danych medycznych w końcu doczekał się wersji pod Linuksa. Trzecia już wersja programu, która pojawiła się w połowie stycznia na razie jest w wersji beta i pod moim Ubuntu 8.04.1 nie chciała się uruchomić. Zainstalowałem ją jednak w Windowsie i muszę przyznać, że jestem pozytywnie zaskoczony. Odświeżony interfejs jest teraz bardziej intuicyjny. Niektóre narzędzia posiadają zestawy predefiniowanych parametrów, co znacznie przyspiesza wstępny etap obróbki wizualizacji.

Poniżej załączam kilka zrzutów ekranu z najnowszej wersji VolView. Podczas ich generowania skorzystałem z danych OsiriX-Manix, o których pisałem we wczorajszym poście.





piątek, 23 stycznia 2009

VTK - czytamy serię danych

0 komentarze
Analizując przykłady dostępne razem z biblioteką VTK, można zauważyć, że kilka z nich pokazuje jak przetwarzać i wyświetlać dane przestrzenne. Standardowo dostępny przykład (zdjęcie CT głowy) zapisany jest w postaci serii plików binarnych i może być odczytany za pomocą polecenia TCL:

vtkVolume16Reader v16
v16 SetDataDimensions 64 64
v16 SetDataByteOrderToLittleEndian
v16 SetFilePrefix "$VTK_DATA_ROOT/Data/headsq/quarter"
v16 SetImageRange 1 93
v16 SetDataSpacing 3.2 3.2 1.5


Co w przypadku innych formatów danych? Nie ma problemu. Za pomocą VTK możemy wczytywać wiele różnych, popularnych typów plików.

Dane testowe
Poniższe przykłady będą bazować na zdjęciach pobranych ze strony przeglądarki OsiriX. W dziale download klikamy na link DICOM sample image datasets Web Site i wybieramy serię zdjęć "MANIX". Po pobraniu rozpakowujemy do dowolnego katalogu. Jako, że zdjęcia są w formacie DICOM, można je również przekonwertować np. do TIFFa. Ja do tego użyłem ImageJ, który bardzo dobrze wspiera DICOMa.

Odczyt serii danych z formatu DICOM
Obsługa DICOMa w VTK jest świetnie opracowana. Wystarczy wskazać katalog ze zdjęciami, a biblioteka już sama rozpozna typ danych oraz rozdzielczość w każdym z wymiarów. Aby nie było żadnych problemów przy wczytywaniu zdjęć, warto upewnić się, że w katalogu nie ma innych plików, aniżeli pliki DICOM. W przeciwnym razie wyskoczy błąd o niezgodności danych.

vtkDICOMImageReader reader
reader SetDirectoryName "Osirix/manix/cer-ct/ANGIO CT"
reader Update


Odczyt serii danych z formatu TIFF
Tutaj już sami dokładnie musimy określić wszystkie parametry. Najpierw wskazujemy część stałą każdego pliku, który ma zostać wczytany, a następnie część zmienną. Jest to najczęściej licznik mający stałą długość - w poniższym przykładzie wynosi on 3. Później jeszcze określamy rozdzielczość w każdym wymiarze oraz podajemy jak są przeskalowane dane. Warto zwrócić uwagę, że druga wartość w rozdzielczości po Z wskazuje dodatkowo ile jest wszystkich plików (tutaj: 458).

vtkTIFFReader reader
reader SetFilePrefix "Osirix/manix/tiff/ANGIO CT0"
reader SetFilePattern "%s%03d.tif"
reader SetFileNameSliceOffset 0
reader SetFileNameSliceSpacing 1
reader SetDataExtent 0 512 0 512 0 458
reader SetDataSpacing 0.488281 0.488281 0.700012
reader Update


VTK wspiera również inne znane formaty plików graficznych, m.in. jpg (vtkJPEGReader), bmp (vtkBMPReader), png (vtkPNGReader).

Testujemy przykłady VTK dla własnych danych
Jak już wspomniałem na początku, w katalogu VTK jest kilkanaście gotowych programów, które operują na zdjęciu tomograficznym głowy. Nic nie stoi na przeszkodzie, aby te programy zadziałały dla naszych danych. Od czego zacząć? Możemy wejść do katalogu VTK/Examples/Medical/Tcl lub wyszukać sobie wszystkie pliki z rozszerzeniem *.tcl zawierające słowo quarter. Następnie w tych plikach zamieniamy odczyt z plików binarnych (blok rozpoczynający się od: vtkVolume16Reader v16) na odczyt z formatu DICOM. Możemy jeszcze zamienić słowo reader na nazwę poprzedniej zmiennej v16, aby nie musieć aktualizować całego skryptu.

Przykładowe wizualizacje dla danych OsiriX-Manix można zobaczyć poniżej:


czwartek, 22 stycznia 2009

Nanoszenie wyników detekcji na obraz źródłowy w CImg

0 komentarze
W wyniku segmentacji obrazu otrzymujemy obiekt lub grupę obiektów, które spełniają zadane przez nas kryteria. Dla przykładu, poniżej możemy zobaczyć przekrój mózgu oraz wynik detekcji substancji białej z wykorzystaniem algorytmu Connected Threshold z biblioteki Insight Toolkit. Wydaje się, że całkiem nieźle poszło. Jednak patrzac na te dwa obrazy osobno trudno wizualinie ocenić czy czegoś nie zgubliliśmy lub czy gdzieś nie jest za dużo. Na pewno łatwiej by było po naniesieniu wyniku na obraz źródłowy. O tym właśnie będzie ten wpis.


Korzystając z biblioteki CImg pokażę jak napisać własną funkcję oraz program przydatny w takich sytuacjach.

Podejście pierwsze
Najłatwiej można to oczywiście zrobić iterując po obu obrazach i jeśli w danym punkcie obraz wynikowy ma wartość 255 (biały kolor) to na obrazie źródłowym w tym samym miejscu zmieniamy kolor na inny, wcześniej ustalony. Poniższa funkcja działa właśnie w taki sposób.

template<typename T>
void cimg_nanies_wyniki(CImg<T> &image, CImg<T> &result, unsigned char* color)
{
cimg_forXY(image,x,y) {
if (result(x,y) == 255) {
image(x,y,0) = color[0];
image(x,y,1) = color[1];
image(x,y,2) = color[2];
}
}
}


Patrząc na obraz wynikowy, który powstanie w wyniku wykonania tej funkcji, wydaje się, że mamy to czego potrzebowaliśmy. Jest jednak pewien problem. Pokolorowany obszar przysłania to co jest pod nim i nie widzimy, czy gdzieś nie znajduje się obiekt, który nie należy do poszukiwanego obszaru. Najlepiej w takim wypadku zastosować przeźroczystość podczas kolorowania.




Podejście drugie
Zastosowanie przeźroczystości w CImg wymaga kilku dodatkowych zabiegów aniżeli miało to miejsce w poprzednim przykładzie. Najpierw przygotujemy dodatkowy obraz typy float, który wypełnimy wartością 1.0. Następnie zmienimy wszystkie punkty zgodnie z maską, którą tutaj jest obraz z detekcją. Później ten obraz jest nanoszony na obraz początkowy z użyciem funkcji draw_image, która pozwala na ustalenie przeźroczystości.

template<typename T>
void cimg_nanies_wyniki2(CImg<T> &image, CImg<T> &mask, unsigned char* color, float transp)
{
CImg<T> layer_c = mask;

layer_c.draw_rectangle(0, 0, layer_c.dimx(), layer_c.dimy(), color, 1.0);

CImg<T> layer_f1 = mask;
cimg_forXY(layer_f1,x,y)
layer_f1(x,y) = (layer_f1(x,y,0)==255 && layer_f1(x,y,1)==255 && layer_f1(x,y,2)==255)?0.0f:1.0f;
CImg<T> layer_f2=layer_f1.get_shared_channel(0);

image.draw_image(layer_c,layer_f2,0,0,0,0,1.0f,transp);
}
Po wywołaniu tej funkcji otrzymujemy obraz wejściowy z naniesionym wynikiem detekcji. Teraz już znacznie łatwiej ocenić, czy zastosowany przez nas algorytm segmentacji zadziałał poprawnie.



Przykładowa funkcja main
Dodatkowo została zaprezentowana funkcja main() odbierająca parametry z wiersza poleceń i wywołująca funkcję cimg_nanies_wyniki2(). Po jej skompilowaniu otrzymamy gotowy program, który może być wielokrotnie używany, a nawet dodawany do istniejących skryptów w celu prezentacji wyników.

#include <iostream>
#include <ostream>
#include "../CImg.h"

using namespace std;
using namespace cimg_library;

unsigned char white[3]={255,255,255},
black[3] = {0,0,0},
red[3] = {255,0,0},
green[3] = {0,255,0},
blue[3] = {0,0,255},
yellow[3] = {255,255,0},
magenta[3] = {255,0,255},
orange[3] = {255,90,0},
middle[3] = {127,127,127}
;

//Tutaj powinna być funkcja cimg_nanies_wyniki2

int main(int argc,char **argv)
{
cimg_usage("Nanies wyniki");

const char* file_i = cimg_option("-i","input.bmp","Input image");
const char* file_o = cimg_option("-o","output.bmp","Output image");
const char* file_m = cimg_option("-m","mask.bmp","Mask image");
const char col = cimg_option("-c",'r',"Color (r-red,g-green,b-blue,y-yellow,m-magenta,o-orange)");
float transp = cimg_option("-t",0.2f,"Transparency");

unsigned char* color=red;
switch (col) {
case 'r': color = red; break;
case 'g': color = green; break;
case 'b': color = blue; break;
case 'y': color = yellow; break;
case 'm': color = magenta;break;
case 'o': color = orange; break;
default: color = red;
}

CImg<unsigned char> image_in(file_i), mask_in(file_m);
mask_in.normalize(255,0);

cimg_nanies_wyniki2(image_in,mask_in,color,transp);
image_in.save(file_o);

return 0;
}


Przykład wywołania:
program -i input.bmp -m mask.bmp -o output.bmp -t 0.15 -c o

niedziela, 4 stycznia 2009

DocBook w Ubuntu - jak zacząć

3 komentarze
Dłuższy czas temu przygotowałem dla studentów instrukcję jak używać DocBooka do pisania dokumentacji technicznej. Opis dotyczył systemu Windows. Jako, że sam korzystam teraz głównie z Linuksa i wielu studentów również preferuje ten system, czas na mały starter właśnie dla Linuksa.
Poniższy opis powstał w dużej mierze na podstawie dokumentacji dla Ubuntu. Aby nie powtarzać tych samych informacji polecam na początek zapoznać się z nią i dopiero wtedy wrócić na tą stronę. Nie jest to jednak konieczne ponieważ część informacji zostanie powtórzona. Zostaną również przedstawione dodatkowe przykłady nie zawarte w dokumentacji Ubuntu.

Instalacja
Instalujemy następujące pakiety: xsltproc, docbook-xsl, docbook-defguide
sudo apt-get install xsltproc docbook-xsl docbook-defguide

Przykład 1
Pierwszy przykład pokazuje podstawową strukturę dokumentu DocBooka. Tutaj konkretnie mamy artykuł (article). Do innych typów dokumentów jakie możemy utworzyć można zaliczyć: książkę (book), część (part), rozdział (chapter),sekcję (section), dodatek (appendix), odwołanie (refentry), wykaz haseł (glosary). Każdy z tych typów ma oczywiście inną formę, dlatego warto zapoznać się wcześniej z oficjalną dokumentacją.

Plik article1.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<article>
<articleinfo>
<title>Artykuł</title>
<author>
<firstname>Rafał</firstname>
<surname>Petryniak</surname>
<affiliation>
<orgname></orgname>
</affiliation>
</author>
<pubdate>2009.01.03</pubdate>
</articleinfo>

<section>
<title>Sekcja pierwsza</title>
<para>treść</para>
</section>
</article>

Transformacją naszego dokumentu zajmie się zainstalowany przez nas xsltproc. Na wejście podajemy mu nazwę pliku do przetworzenia (article1.xml), nazwę pliku wynikowego (output.html) oraz plik styli xsl (warto zajrzeć do katalogu /usr/share/xml/docbook/stylesheet/nwalsh/, aby zobaczyć jakie inne formaty wyjściowe mamy jeszcze dostępne).

xsltproc -o output.html /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl article1.xml

Przykład 2
Kiedy już utworzymy kilka plików DocBooka, niewygodnym może się okazać generowanie wyjścia dla każdego z osobna. W takim przypadku najwygodniej przygotować sobie plik Makefile, który zrobi to za nas.

Plik Makefile

TARGETS = article1.html article1-copy.html

XSLTPROC = /usr/bin/xsltproc
XSL = /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl

%.html: %.xml $(XSL)
@$(XSLTPROC) -o $@ $(XSL) $<

all: $(TARGETS)

clean:
@rm -f *.html

Teraz wystarczy już wpisać make w konsoli i po chwili zobaczymy pliki wyjściowe.

Przykład 3
Kolejne dwa przykłady pokazują jak sobie poradzić, kiedy mamy do napisania dużą dokumentację i chcielibyśmy rozbić ją na kilka plików.

W pierwszym sposobie skorzystamy z encji zewnętrznych. Dzięki nim możemy do wybranych przez nas zmiennych przypisać zawartość plików podrzędnych, a później wstawić ją w dowolnym miejscu dokumentu. Po przeanalizowaniu poniższych plików wszystko powinno się wyjaśnić.

Plik article3.xml
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
<!ENTITY sekcja1 SYSTEM "article3a.xml">
<!ENTITY sekcja2 SYSTEM "article3b.xml">
]>

<article>
<articleinfo>
<title>Artykuł</title>
<author>
<firstname>Rafał</firstname>
<surname>Petryniak</surname>
<affiliation>
<orgname>Politechnika Krakowska</orgname>
</affiliation>
</author>
<pubdate>2009.01.03</pubdate>
</articleinfo>

&sekcja1;
&sekcja2;

</article>
Plik article3a.xml
<section>
<title>Sekcja pierwsza</title>
<para>treść</para>
</section>
Plik article3b.xml
<section>
<title>Sekcja druga</title>
<para>treść</para>
</section>

Całość kompilujemy podobnie jak w przykładzie pierwszym, wydając polecenie:
xsltproc -o output.html /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl article3.xml

Przykład 4
Ostatni już przykład również pozwala na łączenie dokumentów, tylko tym razem z wykorzystaniem XML Inclusions. Główną zaletą tego podejścia jest możliwość definiowania zagnieżdzeń bardziej złożonych, aniżeli dokument główny - dokumenty podrzędne (przykład 3).

Plik article4.xml
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">

<article xmlns:xi="http://www.w3.org/2001/XInclude">
<articleinfo>
<title>Artykuł</title>
<author>
<firstname>Rafał</firstname>
<surname>Petryniak</surname>
<affiliation>
<orgname>Politechnika Krakowska</orgname>
</affiliation>
</author>
<pubdate>2009.01.03</pubdate>
</articleinfo>

<xi:include href="article3a.xml" parse="xml" encoding="utf-8" />
<xi:include href="article3b.xml" parse="xml" encoding="utf-8" />

</article>

Aby skompilowac ten przykład będziemy musieli dodać kolejny parametr dla polecenia xsltproc, a mianowicie --xinclude. Całość wygląda tak:

xsltproc --xinclude -o test.html /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl article4.xml

Edytory
Listę dostępnych edytorów DocBooka również można znaleźć w dokumentacji Ubuntu. Ja ze swej strony do bardziej zaawansowanych dokumentów polecam XML Mind Editor, a do zupełnie podstawowych wystarczy edytor tekstowy kolorujący składnię.

czwartek, 1 stycznia 2009

Blog reaktywacja

0 komentarze
Po miesiącu przerwy spowodowanej wyłączeniem serwera, na którym znajdował się ten blog i po dwóch miesiącach od ostatniego posta czas reaktywować bloga. Blog startuje z odświeżonym wyglądem i na hostingowanym serwerze Google (miejmy nadzieje, że zapewni to jego większą dostępność). Poprzednia strona, z której zostały zaimportowane starsze wpisy będzie miała bardziej statyczną formę, ściśle związaną z moją pracą na Politechnice Krakowskiej.

A teraz kilka słów o tej stronie:
  • Korzysta ona z silnika blogowego Blogger rozwijanego przez Google.
  • Został zastosowany szablon Star Crash, który kolorystycznie nawiązuje do mojej uczelnianej strony.
  • Szablon dodatkowo został rozszerzony o możliwość komentowania bezpośrednio pod postem (na podstawie [1]).
  • Pasek nawigacyjny z pomocą kilku sztuczek w CSSie został wyłączony (na podstawie [2]).
  • Mechanizm wyszukiwania bazuje na standardowym mechanizmie Bloggera. Został on dodatkowo wzbogacony o kolorowanie wyszukiwanych fraz (na podstawie [3]).
  • Kolorowany jest również kod źródłowy (na podstawie [4]).
  • Dział kategorii ma formę tzw. chmury tagów, w której wielkość napisu oznacza popularność (ilość artykułów) na tym blogu (na podstawie [5]).
Blog, poza opisanymi zmianami wizualnymi i funkcjonalnymi, w miarę możliwości będzie na bieżąco aktualizowany, a tematyka, którą poruszam mam nadzieje, że zainteresuje czytelników.

[1]. Embedded Comment Form Under Post
[2]. How to Hide or Remove Blogger Navbar
[3]. How to highlight search results with JavaScript and CSS
[4]. Code highlighting on blogger
[5]. Setup and configuration for New Blogger Tag Cloud / Label Cloud