czwartek, 15 października 2009

Przycinanie danych do wizualizacji w VTK

Wizualizując dane przestrzenne często nie wystarcza podgląd obiektów "z wierzchu", ale celowe może się okazać zajrzenie "do ich środka". W VTK mamy taką możliwość zarówno podczas wizualizacji powierzchniowej (surface rendering) jak i objętościowej (volume rendering) poprzez odpowiednie zdefiniowanie przeźroczystości. Jednak może się zdarzyć, że to również będzie za mało i będziemy potrzebować dokładniej podejrzeć szczegóły znajdujące się w konkretnym miejscu. Do tego celu służą różne techniki przycinania danych, które zostaną opisane w tym wpisie.

Przycinanie danych za pomocą vtkExtractVOI

Najłatwiejszym sposobem przycinania danych jest użycie do tego celu klasy vtkExtractVOI. Pozwala ona zawęzić pole widzenia (Volume of Interest - VOI) na każdej osi dla ściśle zadanych przedziałów. Aby z niej skorzystać, wystarczy umieścić ją w potoku zaraz po czytniku danych i określić zakres widoczności funkcją SetVOI. Można to zrobić statycznie wpisując konkretne wartości w programie, lub też dynamicznie za pomocą parametrów linii poleceń lub odpowiednich kontrolek graficznych.
vtkExtractVOI extract
extract SetInput [reader GetOutput]
extract SetVOI $xmin $xmax $ymin $ymax $zmin $zmax
Tą technikę można stosować zarówno w wizualizacji powierzchniowej i objętościowej.


ClippingPlanes

Innym sposobem jest użycie płaszczyzn cięcia reprezentowanych w VTK klasą vtkPlanes. W odróżnieniu od poprzedniego rozwiązania jest to podejście zdecydowanie bardziej elastyczne, ponieważ sami możemy określić liczbę takich płaszczyzn oraz ich kąt. W ten sposób możemy wyciąć dowolny fragment danych.

Użycie tej klasy sprowadza się do ustawienia płaszczyzn cięcia dla obiektów mapujących za pomocą funkcji SetClippingPlanes.

Przykład - vtkBoxWidget
Płaszczyzny cięcia możemy zdefiniować sami lub też możemy skorzystać z wygodnej kontrolki graficznej vtkBoxWidget.

Przykładowy kod tworzy kontrolkę na scenie wizualizacji podpina ją do danych wejściowych i ustawia funkcję obsługującą zdarzenia. Kod może zostać w całości wklejony na końcu prezentowanych już we wcześniejszych wpisach skryptów do wizualizacji powierzchniowej i objętościowej.
vtkBoxWidget boxWidget
boxWidget SetInteractor iren
boxWidget SetPlaceFactor 1.0
boxWidget SetInput [reader GetOutput]
boxWidget PlaceWidget
boxWidget InsideOutOn
boxWidget AddObserver InteractionEvent clipVolumeRenderCallback
boxWidget On

[boxWidget GetOutlineProperty] SetRepresentationToWireframe
[boxWidget GetOutlineProperty] SetAmbient 1.0
[boxWidget GetOutlineProperty] SetAmbientColor 1 1 1
[boxWidget GetOutlineProperty] SetLineWidth 1

[boxWidget GetSelectedOutlineProperty] SetRepresentationToWireframe
[boxWidget GetSelectedOutlineProperty] SetAmbient 1.0
[boxWidget GetSelectedOutlineProperty] SetAmbientColor 1 0 0
[boxWidget GetSelectedOutlineProperty] SetLineWidth 3

vtkPlanes planes
proc clipVolumeRenderCallback {} {
boxWidget GetPlanes planes;
isoMapper SetClippingPlanes planes;
}

Należy zwrócić uwagę, że pomiędzy skryptami jest pewna różnica w nazewnictwie zmiennych. Kod zadziała bez problemu w skrypcie powierzchniowym, natomiast w skrypcie objętościowym należy zmienić dwie linijki:
boxWidget SetInput [reader GetOutput]
isoMapper SetClippingPlanes planes;


Ze wszystkich omawianych w tym wpisie technik zalecam to podejście. Jest ono najszybsze i działa bez problemu dla obu rodzajów wizualizacji.

vtkClipVolume

Kolejną możliwością przycięcia danych jest użycie klasy vtkClipVolume. Jak sama nazwa wskazuje wydawać by się mogło, że to jest dedykowany sposób w VTK. Niestety użycie tej klasy wiąże się ze zmianą reprezentacji danych z structured points na unstructured grid. W przypadku wizualizacji powierzchniowej nie ma to większego znaczenia - wszystko zadziała poprawnie, natomiast w wizualizacji objętościowej pojawiają się problemy. Pierwszym z nich jest zmiana kodu. Dla obiektu mapującego należy skorzystać z klasy vtkUnstructuredGridVolumeRayCastMapper. Niby nie jest to wielki problem, jednak przygotowanie wizualizacji jest niezwykle wolne i zajmuje ogromną ilość pamięci. Dla porównania skrypt TCL używający tradycyjnego algorytmu dla przedstawianego modelu zęba (plik 1,5MB) korzysta z ok. 75 MB Ramu (wartość obejmuje nie tylko dane, ale również załadowane biblioteki), a w przypadku nowego obiektu mapującego jest to już ok. 1 800MB!. Inny problem to różnica w wyglądzie wizualizacji jaką dostajemy. Widać to na poniższym obrazku:


Jeżeli szczególnie zależy nam na przycinaniu danych w niestandardowy sposób - np. kulą, jak na powyższym obrazku, a nie mamy wystarczającej ilości ramu, aby poradzić sobie z opisywanymi problemami, możemy wyświetlić dane używając do tego celu znanego z wizualizacji powierzchniowej mapowania wielokątów (klasa vtkPolyDataMapper). Przykład poniżej.
#Prepare sphere
set radius 50
vtkSphere sphere
sphere SetRadius $radius
eval sphere SetCenter [[reader GetOutput] GetCenter]
vtkSphereSource spheres
spheres SetRadius $radius
eval spheres SetCenter [[reader GetOutput] GetCenter]
spheres SetThetaResolution 80
spheres SetPhiResolution 40
spheres SetRadius 2
vtkPolyDataMapper soutlineMapper
soutlineMapper SetInput [spheres GetOutput]
vtkActor soutlineActor
soutlineActor SetMapper soutlineMapper
soutlineActor VisibilityOn
eval [soutlineActor GetProperty] SetColor $antique_white
eval [soutlineActor GetProperty] SetOpacity 0.1
ren1 AddActor soutlineActor

# Generate tetrahedral mesh
vtkClipVolume clip
clip SetInputConnection [reader GetOutputPort]
clip SetClipFunction sphere
clip SetValue 0.0
clip GenerateClippedOutputOff
clip Mixed3DCellGenerationOff
clip InsideOutOn

vtkGeometryFilter gf
gf SetInputConnection [clip GetOutputPort]

vtkPolyDataMapper clipMapper
clipMapper SetInputConnection [gf GetOutputPort]
clipMapper ScalarVisibilityOn
eval clipMapper SetScalarRange 0 2

vtkActor clipActor
clipActor SetMapper clipMapper


Wynik tej operacji jest następujący:


Użycie vtkClipVolume rodzi wiele problemów, ale wydaje się być to jedyna możliwość przycinania danych w niestandardowy sposób na potrzeby wizualizacji objętościowej.

vtkClipPolyData

Ostatnią opisywaną techniką zawężania danych do wizualizacji jest użycie klasy vtkClipPolyData. To podejście dotyczy tylko obiektów reprezentowanych za pomocą wielokątów (polygonal data) i jest zazwyczaj stosowane w wizualizacji powierzchniowej. Jego użycie sprowadza się do przygotowania płaszczyzny obiektu, stworzenia obiektu vtkClipPolyData i przekazanie do niego za pomocą funkcji SetClipFunction obiektu tnącego (np. kuli).


vtkMarchingCubes iso1
iso1 SetInput [reader GetOutput]
iso1 SetValue 0 $isoLevel1

# implicit function for clipping
set radius 43
vtkSphere sphere
sphere SetRadius $radius
eval sphere SetCenter [[reader GetOutput] GetCenter]
vtkClipPolyData clipper
clipper SetInput [iso1 GetOutput]
clipper SetClipFunction sphere
clipper GenerateClipScalarsOn
clipper SetValue 0.0
vtkPolyDataMapper clipMapper
clipMapper SetInput [clipper GetOutput]
clipMapper ScalarVisibilityOff
vtkActor iso1Actor
iso1Actor SetMapper clipMapper


Zaprezentowany kod został użyty do przygotowania poniższej wizualizacji:

Użycie tej techniki przycinania danych jest wskazane jeśli interesuje nas w danym momencie wizualizacja powierzchniowa. Nie ingerujemy w żaden sposób w dane, dlatego możemy stworzyć dowolną ilość obiektów i każdy może zostać przycięty inaczej.

Podsumowanie

Opisane sposoby zawężania pola wizualizacji mogą okazać się niezwykle przydatne jeśli interesują nas wybrane fragmenty obrazu, a nie możemy ich dostrzec ponieważ są zasłonięte przez inne obiekty. Przycinając dane na różne sposoby możemy wyłowić każdy szczegół i dokładnie go przeanalizować.

0 komentarze:

Prześlij komentarz