Webapplicatie voor interactieve 3D-beeldweergave Matthias...
Transcript of Webapplicatie voor interactieve 3D-beeldweergave Matthias...
Matthias Cornet
Webapplicatie voor interactieve 3D-beeldweergave
Academiejaar 2010-2011Faculteit Ingenieurswetenschappen en ArchitectuurVoorzitter: prof. dr. ir. Herwig BruneelVakgroep Telecommunicatie en Informatieverwerking
Master in de toegepaste informaticaMasterproef ingediend tot het behalen van de academische graad van
Begeleiders: Dirk Van Haerenborgh, ir. Johan De BockPromotor: prof. dr. ir. Wilfried Philips
Matthias Cornet
Webapplicatie voor interactieve 3D-beeldweergave
Academiejaar 2010-2011Faculteit Ingenieurswetenschappen en ArchitectuurVoorzitter: prof. dr. ir. Herwig BruneelVakgroep Telecommunicatie en Informatieverwerking
Master in de toegepaste informaticaMasterproef ingediend tot het behalen van de academische graad van
Begeleiders: Dirk Van Haerenborgh, ir. Johan De BockPromotor: prof. dr. ir. Wilfried Philips
I
Voorwoord
Dankwoord
Ik wil volgende personen bedanken voor hun hulp en begeleiding tijdens de totstandkoming van deze
masterproef. De Heer Ing. Dirk Van Haerenborgh, begeleider van dit werk. Zijn hulp en advies tijdens het
schrijven van de applicatie waren zeer bevorderend voor het eindresultaat. De Heer ir. Johan De Bock, zijn
inzichten in vooral de medische aspecten van deze webapplicatie waren belangrijk voor dit werk. Verder wil ik
ook de promotor prof. Dr. Ir. Wilfried Philips bedanken voor het mogelijk maken van dit onderwerp.
Toelating tot bruikleen
"De auteur geeft de toelating deze masterproef voor consultatie beschikbaar te stellen en delen van de
masterproef te kopiëren voor persoonlijk gebruik. Elk ander gebruik valt onder de beperkingen van het
auteursrecht, in het bijzonder met betrekking tot de verplichting de bron uitdrukkelijk te vermelden bij het
aanhalen van resultaten uit deze masterproef."
"The author gives permission to make this master dissertation available for consultation and to copy parts of
this master dissertation for personal use. In the case of any other use, the limitations of the copyright have to
be respected, in particular with regard to the obligation to state expressly the source when quoting results
from this master dissertation."
30/05/2011
II
Overzicht
Webapplicatie voor interactieve 3D-beeldweergave
Door Matthias Cornet
Masterproef ingediend tot het behalen van de academische graad van Master in de toegepaste informatica Promotor: prof. dr. ir. Wilfried Philips Begeleiders: Dirk Van Haerenborgh, ir. Johan De Bock Vakgroep Telecommunicatie en Informatieverwerking Voorzitter: prof. dr. ir. Herwig Bruneel Faculteit Ingenieurswetenschappen en Architectuur Academiejaar 2010-2011
Samenvatting
In deze masterproef werd er een onderzoek gedaan naar de haalbaarheid en implementatie van een
interactieve webapplicatie voor de visualisatie van driedimensionale beeldensets. Het betreft een applicatie die
zich vooral situeert in de medische sector, maar ook toepasbaar is op elke 3D beeldenset die ondersteund is
door het HTML canvas-element.
In het eerste hoofdstuk wordt de theoretische basis gelegd voor het verdere verloop van de masterproef. In
het eerste gedeelte wordt de onderzoeksvraag opgesteld en de methodologie besproken. Er worden nadien
enkele principes en theoretische stukken uitgelegd die nodig zijn voor het vervolg van de masterproef. Dit
hoofdstuk bespreekt in hoofdzaak het 2D canvas element, en WebGL als open standaard voor 3D visualisaties.
Het tweede hoofdstuk gaat dieper in op het 2D canvas element. Dit element dient als rendering context waar
de 3D beeldenset op getekend zal worden. Met de komst van HTML5 is het immers mogelijk geworden om op
een vooraf gedefinieerd canvas-element afbeeldingen te tekenen en pixelwaarden te lezen. Op deze manier
wordt het mogelijk om de pixelwaarden (Rood, Groen, Blauw, Alpha) uit te lezen. Nadat de pixels uitgelezen
zijn kunnen ze gebruikt worden om op hetzelfde canvas-element nieuwe afbeeldingen te maken.
In hoofdstuk drie gebeurt de bespreking van het WebGL gedeelte. Deze standaard omvat een API die het
mogelijk maakt om direct, zonder tussenkomst van andere software-componenten, beelden te tekenen in de
grafische kaart. Dit betekent een groot snelheidsvoordeel voor visualisaties in internetbrowsers.
III
Hoofdstuk vier bespreekt de uiteindelijke werking van de applicatie. De applicatie werkt volledig client-side. Dit
heeft als voordeel dat er geen servers opgezet moeten worden, en dat de applicatie offline kan werken. Dit
hoofdstuk gaat nadien gedetailleerd in op de werking van de applicatie zelf, en welke keuzes er gemaakt zijn
om bepaalde problemen op te lossen. Er wordt afgesloten met een algemene conclusie.
Trefwoorden: HTML5, WebGL, canvas, 3D, 2D, dataset, visualisatie, webapplicatie, medisch
IV
Extended abstract
A Web application for interactive 3D visualizations.
This master-dissertation deals with a study of a web application for the visualization of 3D datasets. To be more
precise, it contains a study after the feasibility and implementation of a web application. With current
development in HTML5 and WebGL, both open web standards, this application demonstrates the possibilities
of these new technologies and the future of the web as we know it.
In the first chapter, a theoretical basis is given of the HTML5 standard and WebGL as 3D visualization standard.
In this chapter common principles and chosen methods are explained to give the user an understanding of how
HTML5 and WebGL actually work, and how they can work together. It gives an overview of what is possible
with HTML5, and what not. The web application will mainly be used for the visualization of medical datasets.
These datasets usually contain a volume of 2D image sets which together form a 3D image. To give an example,
consider the following image.
This example image displays an image dataset, which is a 3D volume. Each
number stands for a 2D image, but the columns and rows of the image
contain the pixels of the other dimensions of the 3D visualization. In this way,
the only thing needed to create a 3D image is getting the pixeldata from the
images.
Chapter two deals with the problem of getting the pixel data from the image dataset. With the upcoming
HTML5 standard, official support for the 2D canvas context is given. This means that images can be drawn onto
a canvas 2D context, which gives the developer access to the pixel data. The following image shows which
methods are available to make this possible.
The first part of the algorithm consists of drawing each image from the
dataset onto the 2D canvas. After this is done, the getImagedata
method on the 2D context gives access to the pixel data. With the
method createImageData it’s possible to use the obtained pixels to
create a new image object.
V
In Chapter three the generated images are used in a WebGL context. In contrast with chapter two, a WebGL
context on a given canvas element is fully 3D compatible, whereas a 2D canvas can only be used to draw 2D
scenes.1 This chapter deals with the problem of displaying all three image slices onto four different 3D
canvases.
The first three canvases display a semi-static visualization
of the three slices. To accomplish this, the images need to
be converted to textures, so they can be drawn using
WebGL. The fourth canvas needs to be a 3D visualization
of the three other canvases. The Imagedata of these
elements need to be combined in a 3D scenery. The
following image gives an example of how this is
accomplished.
The blue plane stands for the sagittal slice, the green plane stands for the transverse slice, and the red plane
stands for the coronal slice. This way a 3D visualization is created of the three slices.
After these chapters, the fourth and last chapter explains how the whole application is build and which design
choices were made. The application has a couple of ways for interaction with the user. Whenever the user
clicks inside one of the three semi-static canvas elements, the other two canvases will change their image and
the planes will move to the x,y location of the point clicked.
Whenever the user scrolls on a canvas element, the next or previous image will be shown. The scroll speed can
be adjusted by using the appropriate buttons. The sliders next to the canvas elements can be used to zoom
faster through the dataset of each canvas element. A screenshot of the actual application is can be found in
appendix B.
1 Exception: 3D libraries on 2D canvases. ( e.g. https://github.com/mrdoob/three.js/)
VI
Inhoudstafel
Voorwoord ............................................................................................................................................................... I
Overzicht ................................................................................................................................................................. II
Extended abstract .................................................................................................................................................. IV
Inhoudstafel ........................................................................................................................................................... VI
Hoofdstuk I HTML5 en WebGL ......................................................................................................................... 1
I.1 Inleiding ...................................................................................................................................................... 1
I.2 Onderzoek en methodologie ..................................................................................................................... 1
I.3 HTML5 ........................................................................................................................................................ 2
I.3.1 Canvas 2D .......................................................................................................................................... 2
I.3.2 Multiple input en Drag & drop .......................................................................................................... 4
I.4 WebGL en 3D Beeldensets ......................................................................................................................... 4
I.4.1 WebGL ............................................................................................................................................... 5
I.4.2 Buffers en textures ............................................................................................................................ 6
I.4.3 3D beeldenset ................................................................................................................................... 6
I.5 Conclusie .................................................................................................................................................... 8
Hoofdstuk II Genereren van de 3D slices........................................................................................................... 9
II.1 Image preloading ................................................................................................................................... 9
II.2 Script met timeout ............................................................................................................................... 10
II.2.1 Functie Render() .............................................................................................................................. 10
II.2.2 Textures ........................................................................................................................................... 12
II.3 3D Array als alternatief ........................................................................................................................ 13
II.3.1 Vergelijking 3D array en origineel script ......................................................................................... 14
II.4 Conclusie .............................................................................................................................................. 15
Hoofdstuk III Genereren van de visualisatie ..................................................................................................... 16
III.1 De canvas elementen. ......................................................................................................................... 16
III.1.1 Aanroeping .................................................................................................................................. 16
III.1.1 Werking ....................................................................................................................................... 17
III.2 Genereren naar 3D scene .................................................................................................................... 20
VII
III.3 Alternatieve oplossingen ..................................................................................................................... 22
III.4 Conclusie .............................................................................................................................................. 23
Hoofdstuk IV Werking van de applicatie ........................................................................................................... 24
IV.1 Applicatie Interface.............................................................................................................................. 24
IV.1.1 Offline applicatie-gebruik ........................................................................................................... 24
IV.1.2 Input scherm ............................................................................................................................... 25
IV.1.3 Verwerkingsfase .......................................................................................................................... 30
IV.1.4 Output scherm ............................................................................................................................ 31
IV.2 Beperkingen van de applicatie............................................................................................................. 38
IV.2.1 Geheugenbeperkingen ................................................................................................................ 38
IV.2.2 Alternatieve oplossingen ............................................................................................................ 38
IV.3 Algemene Conclusie en beantwoording onderzoeksvraag .................................................................. 39
Bijlage A .................................................................................................................................................................... I
Bijlage B ................................................................................................................................................................ XIX
Referenties .......................................................................................................................................................... XXII
1
Hoofdstuk I HTML5 en WebGL
I.1 Inleiding
Deze masterproef handelt over de implementatie van een webapplicatie voor interactieve driedimensionale
beeldweergave. In dit hoofdstuk wordt de onderzoeksvraag gedefinieerd en de gehanteerde methodologie
besproken. Nadien wordt er een theoretisch kader uitgewerkt, waar de begrippen en ideeën van de gebruikte
technologieën als onderbouw zullen dienen, voor de verdere opbouw van deze masterproef. Dit hoofdstuk sluit
af met een conclusie van de besproken elementen.
I.2 Onderzoek en methodologie
Deze masterproef heeft als doel een onderzoeksvraag te beantwoorden, het is de webapplicatie op zich die
een antwoord moet bieden op deze onderzoeksvraag. Deze webapplicatie dient een bepaalde 3D beeldenset in
te laden in een compatibele browser2, en deze te visualiseren zonder gebruik te maken van eender welke
browserafhankelijke plug-in. Dit wil zeggen dat de applicatie volledig onafhankelijk en uniform moeten
functioneren in een compatibele browser die ondersteuning biedt voor de gebruikte (open) standaard. Het is
immers zo dat het bezitters van 3D beeldensets momenteel genoodzaakt zijn om software te gebruiken voor
de visualisaties van hun data. Deze masterproef probeert een antwoord te geven op dit probleem door een
webapplicatie te schrijven. Met deze informatie kan nu een onderzoeksvraag opgesteld worden:
Onderzoek de mogelijkheid en implementatie van een webapplicatie die een 3D beeldenset weergeeft op een
onafhankelijke manier.
Om dit te verwezenlijken zullen twee fases gedefinieerd en doorlopen worden. In een eerste fase zal de
mogelijkheid van bestaande, soms onafgewerkte, standaarden geanalyseerd worden in hun toepasbaarheid op
het eerder gestelde probleem. In de tweede fase zullen deze technieken en mogelijkheden gebruikt worden
om een webapplicatie te bouwen. Hierbij is het belangrijk om bij elke beslissing de alternatieven te bespreken,
en de gehanteerde keuze te verantwoorden. Zoals immers later zal blijken zijn er meerdere mogelijkheden om
deze webapplicatie te bouwen, maar er zijn verschillende voor -en nadelen aan elke keuze.
In het eerste deel van de theoretische fase worden de mogelijkheden van HTML5 besproken, en hoe deze een
invloed hebben op de mogelijkheden van de webapplicatie. In het tweede deel wordt WebGL bekeken als
mogelijkheid voor de visualisatie van de beeldenset. HTML5 is de webstandaard, WebGL is een standaard voor
3D visualisatie.
2 Google Chrome en Mozilla Firefox 4.x+. Zie later.
2
I.3 HTML5
HTML5 is de laatste – tot op vandaag nog onafgewerkte – open standaard voor de weergave van html
webpagina’s. (1) Deze nieuwe versie heeft een aantal nieuw mogelijkheden die een belangrijk onderdeel
vormen van deze masterproef. Doordat deze standaard nog niet definitief is, is het wel mogelijk dat er in de
toekomst nog veranderingen kunnen doorgevoerd worden. Alles wat dus in deze masterproef aangehaald
wordt in verband met HTML5, is gebaseerd op de draft standaard uitgegeven op 5 april 2011. (2) Bijgevolg
kunnen er geen beloftes gemaakt worden over de beschikbaarheid van toekomstige functionaliteiten.
De lezer van deze masterproef kan zich terecht de vraag stellen waarom er niet gekozen wordt voor een
standaard die wel definitief en browseronafhankelijk is. Adobe’s Flash en Microsoft’s Silverlight zijn
alternatieven voor HTML5. De reden voor de keuze van HTML5 in combinatie met WebGL is dat Flash en
Silverlight – hoewel beiden gratis - closed source technologieën zijn. (3) (4) De bedoeling van deze masterproef
is om de open source mogelijkheden te onderzoeken. HTML5 en WebGL functioneren ook zonder de installatie
van een plug-in, iets wat niet van Flash of Silverlight kan gezegd worden. Bovendien zijn HTML5 en WebGL
vrijgesteld van royalty’s, en kan in principe elk platform overweg met deze technologie. (Op bijv. Linux
systemen is Flash minder goed ondersteund dan op het Windows platform). Vanuit deze standpunten vallen de
opties van Adobe en Microsoft af.
I.3.1 Canvas 2D
HTML5 heeft enkele belangrijke vernieuwingen. (5) Zo zijn er bijv. de content elementen: audio, video, section,
progress, nav, meter, time, aside en canvas. Het is vooral het <canvas> element (6) dat belangrijk is. Het canvas
element is een html-element waarop allerhande visuele bewerkingen kunnen uitgevoerd worden. De
mogelijkheid bestaat om verschillende vormen te tekenen, maar interessanter is de functionaliteit om
afbeeldingen te tekenen op een canvas element. Om dit te bereiken moet van een gegeven canvas element
eerst een context gedefinieerd worden. Er is op het moment van schrijven ondersteuning voor twee context
soorten: ‘2d’ en ‘experimental-webgl’. Het bekomen van een API om de visualisaties te maken op het canvas
element gebeurt via volgende syntax:
var context = canvas . getContext(contextId [, ... ])
Hierbij is canvas de referentie naar het <canvas> element uit het DOM (Document Object Model). Met de
context API kunnen er nu visualisaties gemaakt worden. De belangrijkste zijn drawImage(), createImageData(),
getImageData() en putImageData(). Deze Laatste drie zijn de belangrijkste voor pixelmanipulaties.
3
Met DrawImage(Image, dx, dy) wordt er op de gegeven context in het cartesisch coördinatenstelsel met (0,0) in
de linker bovenhoek een beeld getekend op het canvas element op plaats dx, dy. (7)Er zijn ook mogelijkheden
om een afbeelding te verkleinen en dan te tekenen op plaats dx,dy. In dit geval zal drawImage() vier
argumenten bevatten. Met zes argumenten is het mogelijk om de afbeelding te ‘clippen’, deze nadien te
verkleinen ‘scale’ en deze op positie (dx,dy) te plaatsen.
Figuur 1 - Cartesisch coördinatenstelsel (bron: Eigen verwerking)
Via de methode getImageData() is het nu mogelijk om de pixelwaardes te bekomen van de zojuist getekende
afbeelding. (8) getImageData() geeft een object terug van het type Imagedata. Dit bevat twee long’s width en
height en een CanvasPixelArray object data. Dit object is een array met 4 bytes voor elke pixel. (zie Figuur 2)
Pixel 1 Pixel 2 Pixel 3 Pixel n
R G B A R G B A R G B A R G B A
Figuur 2 - CanvasPixelArray object (bron: eigen verwerking)
Elke kleurwaarde is een integer tussen 0 en 255. Deze array is rechtstreeks schrijfbaar en op deze manier
kunnen pixels een andere kleur toegewezen krijgen.
Via de methode createImageData(dx, dy) is het mogelijk om een blanco afbeelding te maken met afmeting
(dx,dy). Deze afbeelding wordt standaard op transparant zwart geïnitialiseerd. Met de methode
putImageData() is het mogelijk om een afbeelding van het type imageData weer te geven op een canvas
element. (8)
Met deze besproken methoden is het nu mogelijk om volgende sequentie van handelingen uit te voeren:
4
Figuur 3 – Pixelmanipulatie (bron: eigen verwerking)
Door bovenstaande sequentie in een lus te doorlopen is het mogelijk om van een gegeven afbeelding de
pixelwaarden uit te lezen, en deze in een nieuw gecreëerd afbeeldingobject te injecteren om zo een geheel
nieuwe afbeelding op te bouwen. Het HTML canvas element heeft nog verschillende andere methodes en
mogelijkheden, maar deze vallen buiten het doel van deze masterproef en zullen hier aldus niet besproken
worden. Voor meer informatie omtrent het canvas element wordt er verwezen naar de literatuur. (6)
I.3.2 Multiple input en Drag & drop
Met de komst van HTML5 heeft het W3 consortium er ook voor gekozen om o.a. de input/output van html te
herzien. (5) Zo heeft het input element volgende attributen gekregen: autocomplete, min, max, multiple,
pattern en step. Het is vooral multiple dat interessant is. Dit attribuut zorgt er voor dat een file upload vanaf nu
meerdere bestanden kan selecteren in één handeling. Dit was niet mogelijk in eerdere versies van html.
Verder is er ook ondersteuning gekomen voor ‘Drag and Drop’ events. (9) Deze functionaliteit wordt simpelweg
bekomen door aan een element het attribuut ‘draggable’ toe te voegen. Door een eventlistener op te zetten
voor het event ‘dragstart’ kan de data die gesleept wordt meegegeven worden. Bij de bespreking van de
webapplicatie wordt er dieper ingegaan op de werking van deze techniek.
Naast de mogelijkheden van HTML5 is het ook belangrijk om WebGL – de open standaard voor
driedimensionale visualisaties in internetbrowsers – te bespreken. Dit gebeurt in het volgende onderdeel.
I.4 WebGL en 3D Beeldensets
In deze sectie is het de bedoeling uitleg te verschaffen over WebGL als drijvende kracht van de visualisatie en
de data zelf die gepresenteerd dient te worden, de 3D Beeldenset.
5
I.4.1 WebGL
Web-based Graphics Library of afgekort WebGL is de API die ontwikkelaars de mogelijkheid geeft om
visualisaties weer te geven in een internetbrowser op een canvas element. (10) Het verschil met het eerder
besproken canvas element is dat WebGL een andere context definieert dan 2D. De beelden die in WebGL
weergeven worden zijn driedimensionaal, en kunnen gebruik maken van hardwareacceleratie via de grafische
kaart (Indien beschikbaar). (11) Er moet vermeld worden dat sommige browsers ook hardware-acceleratie
ondersteunen voor het 2D canvas, zie o.a. (12) en (13). Het voordeel dat WebGL heeft is de 3D engine, terwijl
het 2D canvas het met een 2D-engine moet stellen. WebGl wordt ontwikkeld door Khronos Group, en is een
open source standaard gevrijwaard van royalty’s. (10)
WebGL is de omzetting van OpenGL ES 2.0 voor webbrowsers. OpenGL ES 2.0 is een open standaard die
ontwikkeld werd om te functioneren op zogenaamde ‘embedded devices’. (10) Dit zijn PDA’s mobiele
telefoons, autosystemen etc. Javascript is de programmeertaal die gebruikt wordt bij het aanspreken van de
API. Het valt buiten het bestek van deze masterproef om de volledige standaard te bespreken. Daarom zullen
enkel de belangrijkste eigenschappen van deze API besproken worden. (12)
Een van de belangrijkste onderdelen binnen WebGL om tot een visualisatie te komen zijn de Vertex shaders en
Fragement shaders. (15) Shaders zijn programma’s geschreven in een aparte taal, de opengl es shading
language. De vertex shader verwerkt inkomende data (zie o.a. Figuur 12) en past transformaties toe, berekent
belichtingeffecten, voert verplaatsingen uit, berekent kleuren etc. Indien we een vierkant tekenen, moet deze
shader toegepast worden op elke hoek. (In principe zes hoeken aangezien een vierkant in de webapplicatie uit
twee rechthoekige driehoeken bestaat, zie later).
Eenmaal de vertex shader alle bewerkingen uitgevoerd heeft wordt de data doorgegeven aan de Primitive
Assembly fase. Via een set van tekencommando’s kunnen primitieven getekend worden en kunnen de
hoekpunten samengebracht worden tot individuele geometrische objecten. (Bijvoorbeeld door driehoeken,
lijnen of punten te combineren). In Hoofdstuk III wordt er concreet ingegaan op deze fase bij het tekenen van
de visualisatie. De rasterization fase zet de getekende primitieve om in fragments en dit wordt doorgegeven
aan de fragment shader. Fragments zijn pixels die op het scherm getekend kunnen worden en in de literatuur
spreekt men dan ook soms over pixel shaders i.p.v. fragment shaders. (12) De fragment shader zal deze pixels
een kleur geven en het uiteindelijke resultaat zal een pixel zijn in het RGBA formaat.
De per-fragment operaties gaan een aantal tests doen op de fragments. De pixel ownership-test gaat
bijvoorbeeld nagaan of een pixel moet getoond worden op het scherm of niet. Een window kan immers bij een
ander window overlappen en dan zal OpenGL ES/WebGL concluderen dat een bepaalde pixel niet behoort tot
de context en dus niet getoond moet worden. De scissor test controleert of een pixel wel binnen de rechthoek
valt zoals bepaald door OpenGL ES. De depth tests controleert of een object dat achter een ander object
getekend is wel getoond moet worden. (Dieper gelegen objecten moeten slechts deels of soms niet getekend
worden). (12)
6
In de laatste fase wordt de informatie naar het framebuffer gestuurd, en tenzij het een zelf aangemaakt
framebuffer object betreft, zal het object getekend worden op het scherm. Het is namelijk mogelijk om niet te
tekenen naar het scherm maar naar een object van het type framebuffer. Achteraf kunnen hier dan
bewerkingen op gedaan worden zoals bijv. render-to-texture technieken. Dit zal later aan bod komen bij de
bespreking van de visualisatie van de applicatie.
I.4.2 Buffers en textures
Het canvas element wordt zoals eerder besproken aangeroepen vanuit getContext(). Deze keer wordt er
gedefinieerd dat het om een ‘webgl’ context gaat, om zo een WebGLRenderingContext object aan te maken.
(11) Een Drawing Buffer wordt meteen aangemaakt na het renderingcontext object. Deze buffer bestaat uit
een Color, Depth en Stencil buffer en zijn nodig om een scene op het scherm te tekenen. Verder is het object
createTexture en de methode texImage2D een van de meest cruciale van deze masterproef. Deze zorgen er
voor dat gegenereerde beelden daadwerkelijk getoond kunnen worden op het scherm.
texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, Object Data)
Dit wil zeggen dat er een data object - in dit geval een ImageData pixels object ( zie I.3.1) – gekoppeld kan
worden aan een texture object. ‘Target’ staat voor het doel van de actie, en zal in dit geval TEXTURE_2D zijn.
Een andere mogelijkheid als doel is TEXTURE_CUBE_MAP. Dit is geen 2D beeld maar een 3D cube waar elk van
de zes vlakken een beeld bevat. Internalformat is belangrijk aangezien dit bepaalt in welk formaat de data zal
opgeslaan worden in het texture object. Mogelijk waarden zijn: GL_RGBA, GL_RGB, GL_LUMINANCE_ALPHA,
GL_LUMINANCE, GL_ALPHA. Aangezien de beeldensets altijd uit grijswaarden bestaan is het onnodig om vier
bytes per pixel bij te houden waar één byte voldoende is. GL_LUMINANCE is de mate van helderheid of
lichtsterkte per oppervlakte-eenheid en heeft voldoende aan een byte per pixel. Later in deze masterproef zal
blijken dat deze keuze belangrijk is voor het geheugengebruik van de webapplicatie en de gebruikerservaring.
De overige parameters van texImage2D() worden later in het onderdeel over de WebGL implementatie
besproken.
I.4.3 3D beeldenset
De beeldenset die het onderdeel van de visualisatie vormt in de applicatie bestaat uit een volume van
tweedimensionale beelden. De meegeleverde datasets met deze applicatie bestaan uit 230 afbeeldingen van
256 op 256 pixels (brain-vessels) en 456 afbeeldingen van 512 op 512 pixels (head-ct). Deze beeldensets
bevatten scans uit de medische sector, en dat zullen ook de beeldensets zijn die het vaakst in deze applicatie
gebruikt zullen worden. Het is echter wel de bedoeling dat eender welk volume visualiseerbaar is met deze
applicatie.
7
Deze applicatie werkt volledig in de webbrowser zonder verwerking van data in een server. Doordat de
applicatie dus volledig client-side werkt, zijn de beeldformaten die getekend kunnen worden op het canvas-
element de enige ondersteunde beeldformaten. (8) Deze formaten zijn statische bitmaps zoals PNG, GIF en
JPEG’s. vector documenten zoals eenpagina PDF’s, XML bestanden met een SVG element. Geanimeerde
bitmap’s zoals APNG’s en geanimeerde GIF’s en geanimeerde vector afbeeldingen zoals XML bestanden met
een SVG element. (8)
Doordat de beeldenset uit een volume van 2D beelden bestaat, is het de bedoeling om uit dit volume de
informatie te halen om de overige twee dimensies op te bouwen. Een illustratie van dit probleem is te vinden
in onderstaande Figuur 4.
Figuur 4 - 3D beeldenset: Pixels (bron: eigen verwerking)
De cijfers (1, 2, 3, … ,n) duiden de beelden uit de beeldenset aan. Het is de bedoeling om uit elk beeld de rijen
(blauw) en kolommen (rood) te gebruiken als data in nieuw te genereren beelden. Zoals aangehaald in I.3.1 is
dit mogelijk door pixelmanipulaties te doen op een 2D canvas element. Een getekende afbeelding heeft haar
pixelwaarden beschikbaar via de methode getImageData(). Op deze manier kunnen de drie dimensies die nodig
zijn voor een 3D visualisatie opgebouwd worden. Hoe dit precies gebeurt en via welk algoritme wordt
besproken in Hoofdstuk II. Merk op dat bovenstaande afbeelding verwarrend kan overkomen in die zin dat elke
pixel in realiteit geen kubus is zoals getekend in de afbeelding. Elke pixel wordt simpelweg hergebruikt als
kolom of rij in een nieuwe afbeelding.
8
I.5 Conclusie
Dit hoofdstuk heeft een beknopte inleiding gegeven over het doel van deze masterproef en hoe dit te bereiken
is. Enkel de belangrijkste technieken werden besproken aangezien de volgende hoofdstukken dieper zullen
ingaan op de daadwerkelijke implementatie in een webapplicatie.
Het canvas element met een 2D context vormt een van de belangrijkste bouwstenen van de applicatie,
aangezien alle pixelmanipulaties hierop uitgevoerd zullen worden. Door toegang tot de pixelwaarden te
verkrijgen is het mogelijk om zelf afbeeldingen samen te stellen, en zo de overige twee dimensies van de
beeldenset te berekenen. In het volgende hoofdstuk zal aan bod komen op welke manieren dit kan gebeuren,
en welke manier de efficiëntste is. De techniek achter WebGL werd kort besproken, hoewel het de bedoeling is
dat dit vooral ‘hands-on’ besproken wordt in Hoofdstuk III.
9
Hoofdstuk II Genereren van de 3D slices
In dit hoofdstuk is het de bedoeling verder in te gaan op de problematiek van het genereren van de drie slice
richtingen. Het bekomen van deze extra twee dimensies uit de originele beeldenset brengt een aantal
problemen met zich mee. Er wordt besproken hoe het algoritme zijn werkt doet, maar het is even belangrijk
om na te gaan wat de alternatieven zijn, en wat hun voor-en nadelen zijn.
II.1 Image preloading
De eerste fase in het berekenen van de drie slice richtingen betreft het cachen van de beeldenset. Dit betekent
dat Javascript de beelden inlaadt in zijn cache om er nadien bewerkingen op uit te kunnen voeren. (13) Dit is
belangrijk omdat bewerkingen op afbeeldingen pas kunnen gebeuren wanneer deze volledig beschikbaar zijn.
Indien deze applicatie gewoonweg via een loop alle afbeeldingen zou aanmaken, zullen sommige afbeeldingen
klaar zijn voor andere. Dit asynchroon inladen van afbeeldingen zorgt dat na de loop de applicatie gestart kan
worden, maar dat mogelijk nog niet alle afbeeldingen ingeladen zijn. Sommige afbeeldingen zullen beschikbaar
zijn, andere nog niet. Indien dit niet het geval is loopt men een zeer grote kans op fouten. Zie voor een
voorbeeld van een dergelijke fout Figuur 11.
In bijlage A kan de code gevonden worden van de webapplicatie. De preloading function start op lijn 216. De
code zal via een for-loop met een limiet gezet op het aantal beelden in de dataset (var numImages) een reeks
beelden van een bepaalde plaats inladen. Aangezien Javascript geen mogelijkheid heeft tot IO (Input/Output),
moet er genoegen genomen worden met deze manier van werken. (meer informatie over de beperkingen in
Javascript in onderdeel IV.2). Deze code is ook zo geschreven dat de bestanden standaard de naam
dataxxxx.png hebben, waar x voor een cijfer staat. Dit is een van de beperkingen van Javascript. In het
hoofdstuk rond de werking van de applicatie wordt besproken hoe variabelen bij het starten geïnitialiseerd
worden, en hoe het mogelijk is om een extra variabele in te voeren die bijv. een andere bestandsnaam dan
dataxxxx.png kan bevatten.
De code in het .onload event zal pas uitgevoerd worden wanneer een afbeelding volledig ingeladen is. Tijdens
het .onload event wordt er elke iteratie nagegaan of het aantal ingeladen beelden reeds bereikt is. Op deze
manier kunnen er nooit afbeeldingen zijn die nog niet ingeladen zijn, aangezien de applicatie pas verder zal
gaan indien het aantal succesvol ingeladen afbeeldingen gelijk is aan het totaal aantal afbeeldingen. Dit is een
efficiënte manier om het te vroeg starten van de rest van de applicatie te voorkomen. De lezer zal ook
opmerken dat deze functie uit twee delen bestaat. Een gedeelte dat enkel zal starten als de meegegeven
variabele code de waarde 1 bevat, en een deel dat zal starten als code de waarde 2 bevat. Dit is zo gekozen om
een ‘dropbox’ mogelijk te maken. (Zie IV.1).
10
II.2 Script met timeout
Vanaf lijn 265 kan men de code terugvinden van het script dat de verwerking van de beelden uitvoert. Drie
arrays worden aangemaakt die de beeldensets zullen bevatten. Nadien wordt er een canvas element gemaakt
waar de bewerkingen op uitgevoerd zullen worden, en dit wordt nadien aan het DOM toegevoegd via
div.appendChild(can);. Na de verwerking (functie render();) wordt het canvas element weer verwijderd.
II.2.1 Functie Render()
Render zet de beeldenset om naar de drie slices. In het begin van deze functie worden enkele variabelen
gedeclareerd. De eerste twee definiëren het 2D canvas element waarop enkele regels lager de bewerkingen op
zullen gebeuren. Canvas, canvas2 en canvas3 worden gedeclareerd om de textures die gemaakt zullen worden
in het WebGL te binden met de juiste context.
try {
gl1 = canvas.getContext("experimental-webgl");
gl1.viewportWidth = canvas.width;
gl1.viewportHeight = canvas.height;
} catch (e) {
}
if (!gl1) {
alert("Error: Browser does not support Webgl");
}
Deze snippet toont hoe een webgl context vastgelegd wordt zodat de textures later aan de juiste context
verbonden kunnen worden. Lijn 322 tot 328 toont hoe twee for-loops blanco afbeeldingen van het type
ImageData aanmaken. In deze afbeeldingen komt de pixel data voor de sagittaal en coronaal afbeeldingen.
renderImg() is de functie die na de iteraties over alle afbeeldingen uit de dataset, de uiteindelijke drie slice
richtingen zal opgevuld hebben. De body3 van deze functie tekent de afbeelding uit imageArray[i] en vult de
transversalImages array op met de data die het zojuist getekend heeft. Met het .data attribuut kan de
CanvasPixelArray bekomen worden, en kunnen hier pixels uit gelezen worden. Nadien zijn er twee for-loops die
de sagittaal en coronaal afbeeldingen genereren.
In een eerdere versie van het script werd er gewerkt met limieten in de loops die als ‘max’ waarden het aantal
kolommen en rijen van de afbeelding hadden. Aangezien WebGL echter geen ondersteuning (14) biedt voor
texture-afmetingen die geen macht van twee zijn is er gekozen om een omsloten kubus te plaatsen rond
datasets die geen macht van twee zouden zijn. De variabele ‘max’ zal dus altijd een integer bevatten die een
macht van twee is, en alle drie de slice richtingen zullen deze afmetingen hebben. Max is zo gedefinieerd dat
het de dichtste macht van twee is tot het grootste been van de kubus. (Later hierover meer in III.2).
3 Algoritme verkregen via begeleiding met Mr. ing. Dirk Van Haerenborgh.
11
De eerste for-loop zal het sagittaal beeld opbouwen en doet dit door bij elke iteratie een pixel weg te schrijven
tijdens het doorlopen van de rijen. Idx bepaalt de kolom, terwijl k*step de rij bepaalt. De variabele L duidt de
RGBA waarde aan voor de pixel. Deze pixel wordt uiteindelijk weggeschreven op positie (idx2 +(k*step)+l). Dit
wil zeggen dat de hele kolom op de eerste kolom komt van het eerste sagittaal beeldje. (in het geval van i=0).
De volgende iteratie zal J=1 zijn, en zal de tweede kolom (van nog steeds het eerste beeldje) op de eerste
kolom komen van de tweede sagittaal afbeelding. De laatste kolom van de eerste afbeelding zal op de eerste
kolom van de laatste sagittaal afbeelding komen. Bij de volgende afbeelding gebeurt deze sequentie opnieuw.
Figuur 5 - Sagittaal en coronaal iteratie (bron: eigen verwerking)
Het bekomen van de coronaal afbeeldingset gebeurt op een vergelijkbare manier. Elke iteratie zal van de
originele imagedata de pixel op positie k gebruikt worden om de coronaal pixel op positie idx2+l te vormen. Op
deze manier worden de rijen van de eerste afbeelding geplaatst op de eerste rij van elke coronaal afbeelding.
De rijen van de tweede afbeelding worden geplaatst op de tweede rij van elke coronaal afbeelding enz.
Na deze twee loops wordt de progressBar() functie opgeroepen die we origineel als argument meegegeven
hebben. (15) De balk zelf wordt gevuld met een percentage van de totaal aantal afbeeldingen die reeds
verwerkt zijn. De variabele ‘i’ staat voor de afbeelding en numImages staat voor het totaal aantal afbeeldingen.
Na deze update van de progressbar wordt de functie opnieuw opgeroepen met een timeout van 0
milliseconden. De reden achter dit is dat tijdens de uitvoering van een lange loop (zoals in deze applicatie),
internetbrowsers na een bepaalde tijd een melding zullen geven met de vraag of men het script wil stoppen
omdat de browser denkt dat het script in een oneindige loop zit. Dit is duidelijk ongewenst gedrag, maar kan
voorkomen worden. Door setTimeOut() in combinatie met arguments.callee te gebruiken, kan men itereren
door een anonieme functie en dus een for-loop simuleren. De setTimeout methode zorgt ervoor dat de functie
een kleine vertraging kent, waardoor de browser niet vastloopt. Zelfs indien de delay op 0 milliseconden
ingesteld is. (18)
12
II.2.2 Textures
Na het aanmaken van de afbeeldingen is het de bedoeling dat deze gebruikt kunnen worden in WebGl. (lijn 395
e.v.) De keuze om al in dit gedeelte textures te bouwen van de afbeeldingen heeft te maken met het geheugen
management van Javascript. De ontwikkelaar hoeft zich geen zorgen te maken om de opruiming van het
geheugen (garbage collector), maar moet wel een aantal regels in acht nemen. Het is daarom bijv. belangrijk
om variabelen zo weinig mogelijk in de global scope te declareren omdat deze variabelen niet door de garbage
collector opgeruimd zullen worden. (13) De applicatie bestaat grosso modo uit twee delen: een gedeelte dat
de drie slice richtingen berekent, en een gedeelte dat de visualisaties uitvoert. Door deze twee strikt
gescheiden te houden, is het mogelijk om geen overbodige globale variabelen te declareren en dus onnodig
geheugengebruik tegen te gaan.
De omzetting naar textures gebeurt drie keer op onderstaande manier:
for (var t=0; t < height; t++) {
var texture = gl1.createTexture();
var container = corImages[t];
handleLoadedTexture(texture, container);
corTexture.push(texture);
}
function handleLoadedTexture(texture, container) {
gl1.bindTexture(gl1.TEXTURE_2D, texture);
gl1.pixelStorei(gl1.UNPACK_FLIP_Y_WEBGL, true);
gl1.texImage2D(gl1.TEXTURE_2D, 0, gl1.LUMINANCE, gl1.LUMINANCE,
gl1.UNSIGNED_BYTE, container);
gl1.texParameteri(gl1.TEXTURE_2D, gl1.TEXTURE_MAG_FILTER, gl1.LINEAR);
gl1.texParameteri(gl1.TEXTURE_2D, gl1.TEXTURE_MIN_FILTER, gl1.LINEAR);
gl1.bindTexture(gl1.TEXTURE_2D, null);
}
De eerste for-loop overloopt elke afbeelding met als limiet de hoogte van de transversale afbeelding. Het
betreft hier het renderen van de coronaal afbeelding. Deze loop heeft dus als limiet de hoogte van de originele
transversaal afbeeldingen, zie ook Figuur 6. Er is hier ook bewust gekozen om niet als limiet de variabele max te
gebruiken, aangezien er maar evenveel textures nodig zijn dan de hoogte (height). Max is de variabele die de
dichtste macht van twee is rond de beeldenset om maximale compatibiliteit met WebGL te bekomen. Daarom
is het gebruik van de variabele height in de rest van de applicatie geen optie.
13
Figuur 6 – Coronaal, Sagittaal en transversaal (bron: Eigen verwerking)
handleLoadedTexture() is een functie die de afbeeldingen laadt in een texture en deze koppelt aan een texture
buffer, dewelke nadien in een texture array geplaatst worden. De eerste lijn duidt aan dat de zojuist
aangemaakte texture gebonden wordt aan een texture target (GL.TEXTURE_2D). Dit wil zeggen dat alle
volgende texture operaties op deze texture uitgevoerd zullen worden. (12)
In een volgende stap gebeurt een eerste operatie, de texture wordt over de y-as gedraaid. Dit komt door het
verschillende coördinatensysteem van WebGL en de 2D canvas afbeeldingen. Afbeeldingen uit een 2D canvas
hebben oplopende y-coördinaten als men de y-as naar onder volgt, WebGL werkt met het omgekeerde
systeem. In de derde lijn wordt er effectief een afbeelding gekoppeld aan de texture via texImage2D() en
gestuurd naar de grafische kaart. Het doel (target) is nog steeds GL.TEXTURE_2D, de argumenten zijn verder
LUMINANCE voor lichtintensiteit en de data betreft een unsigned byte, de container bevat daarnaast het
ImageData object. Gl.texParmeteri() bepaalt verder hoe WebGL de textures moet up –en downscalen. De
laatste lijn maakt de huidige texture terug null;
Na het doorlopen van deze volledige functie renderImages() kan er opdracht gegeven worden om het tweede
gedeelte van de applicatie te starten. Dit gebeurt door op lijn 437 de functie runCST() aan te roepen.
II.3 3D Array als alternatief
De 3D array is een alternatieve werkwijze voor het besproken script in II.2. Het principe achter deze manier van
werken is dat de uitgelezen pixels slechts één keer nodig zijn om een afbeeldingenset te vormen. In dit
onderdeel wordt er besproken wat de voor-en nadelen zijn van deze techniek. Een driedimensionale array
wordt bekomen door drie for-loops te doorlopen en telkens een nieuwe array aan te maken.
Deze 3D-array is in principe een vierdimensionale array aangezien er een vierde dimensie nodig is om de vier
pixelwaarden op te slaan. De 3D array is opgeslaan in een globale variabele omdat deze als stockage dient om
een texture op aanvraag uit te maken. Een afbeelding uit een slice richting kan dan heel eenvoudig bekomen
worden door een nieuwe afbeelding aan te maken via createImageData() en deze op te vullen met de juiste
pixeldata.
14
sagitalImages = ctx.createImageData(numImages, numRows);
var px2=0;
var rij=0;
for (var j = 0; j < numColumns; j++){
var step = numRows * 4;
var idx = k*4;
for (var k = 0; k < numColumns; k++){
for (var l = 0; l < 4; l++){
sagitalImages.data[px2] = array3D[j][rij][k][l]
px2++;
}
}
}
Deze manier van werken is equivalent aan het eerder besproken algoritme. Deze oplossing is echter niet
gekozen omwille van efficiëntie en geheugengebruik. Deze zullen besproken worden in een korte vergelijkende
studie.
II.3.1 Vergelijking 3D array en origineel script
Geheugengebruik voor GC Geheugengebruik na GC Uitvoeringstijd
3D Array ± 1600 MB ± 1470 MB 6.4 sec - 6.8 sec
Origineel script ± 450 MB ± 150 MB 6.7 sec - 7.0 sec Tabel 1 - Vergelijking 3D-array / originele script
4
Bovenstaande tabel geeft een overzicht van het geheugengebruik en de uitvoeringstijd van beide alternatieven.
De uitvoeringstijd is voor beiden ongeveer hetzelfde, namelijk rond de 6,5 à 7 seconden voor het doorlopen
van het script. Het geheugengebruik van de 3D array is voor het optreden van garbage collector ongeveer vier
keer groter. Nadat de garbage collector ongebruikte variabelen opgeruimd heeft is het verbruik van de 3D
array een factor 10 groter dan het originele script. Dit zorgt ervoor dat de keuze voor het originele script snel
gemaakt is.
De 3D array kan ook niet gebruikt worden om er meteen alle textures uit te genereren. De uitvoeringstijd is iets
lager dan die van het originele script, maar er moeten wel nog alle textures uit gemaakt worden, dus zal de
uiteindelijke uitvoeringstijd sowieso hoger uitvallen. Aangezien de uitvoeringstijd sowieso hoger is dan het
originele script, is er voor gekozen om niet uit te zoeken hoeveel hoger precies. Mocht het zo zijn dat de
marginale uitvoeringstijd beperkt is, geldt nog steeds dat het geheugengebruik te hoog is in vergelijking met
het originele script.
4 Benchmark systeem: Intel Core 2 Duo P8400 @2,26GHz, 4GB DDR2 ram, ATI Mobility Radeon HD 3450
256MB, Windows 7 Professional x64
15
II.4 Conclusie
Dit hoofdstuk heeft het eerste gedeelte van de webapplicatie besproken. In een eerste fase gebeurt het
preloaden van de afbeeldingen. Dit is nodig omdat er zich anders bugs en fouten kunnen voordoen in het
verdere verloop van de applicatie. Als alle afbeeldingen beschikbaar zijn, is het mogelijk om de drie slice
richtingen te bekomen door de afbeeldingen te renderen op een 2D canvas element. Hier moet enkel nog de
pixel data van de rijen en kolommen gekopieerd worden in nieuwe afbeeldingen om zo een volledige slice
richting te bouwen. In de laatste fase worden de afbeeldingen gekoppeld aan textures zodat WebGL deze kan
visualiseren. Het volgende hoofdstuk legt uit hoe de visualisatie precies gebeurt.
16
Hoofdstuk III Genereren van de visualisatie
Dit hoofdstuk bespreekt de visualisatie van de textures gegenereerd in het eerst deel van de applicatie. Merk
op dat hier de algemene werking van het WebGL gedeelte besproken wordt, niet hoe dit aangestuurd wordt
via de interface. Hoofdstuk IV bepaalt de interface van de applicatie en hoe WebGL reageert op eventuele
input. Dit hoofdstuk bespreekt in een eerste deel de canvas elementen waar de visualisaties op gebeuren. Het
tweede deel toont hoe het driedimensionale gedeelte precies werkt, en welke keuzes er gemaakt zijn om het
design mogelijk te maken. In een laatste gedeelte wordt er een alternatief voor WebGL besproken via
zogenaamde 3D engines op 2D canvas elementen.
III.1 De canvas elementen.
Dit gedeelte bespreekt hoe de canvas elementen gestart worden, en hoe de scene gerenderd wordt op het
canvas element.
III.1.1 Aanroeping
Er is gekozen om de visualisatie uit te voeren via vier canvas elementen. Deze voorstellingswijze heeft een
aantal voordelen t.o.v. de alternatieve voorstelling, dewelke worden besproken in III.3. Een voorbeeld van de
voorstelling via vier canvas elementen kan gevonden worden in Figuur 9, Bijlage B.
Doordat de textures reeds aangemaakt zijn in een eerder stuk programmacode en deze bijgehouden worden in
een globale variabele, is het nu nog mogelijk om afzonderlijke functies op te roepen voor elk van de canvas
elementen. De functies zien er als volgt uit:
runWebGL( document.getElementById("canvasCoronal") , 1 );
runWebGL( document.getElementById("canvasSagittal") , 2 );
runWebGL( document.getElementById("canvasTransversal") , 3);
Op deze manier is het mogelijk om het volledige renderen van een WebGL scene onafhankelijk te houden. Het
enige wat als parameter meegegeven moet worden is het canvas element zelf waar de visualisatie op komt, en
een integer die aangeeft welk canvas element het betreft. Het zou ook mogelijk zijn om via een if{} else {}
constructie te achterhalen welk canvas element het betreft, en dan van hieruit de juiste code aanroepen in
runWebGL(). Dit compliceert echter de applicatie onnodig en is daarom niet gewenst.
17
III.1.1 Werking
Om de visualisatie mogelijk te maken zijn een aantal elementen nodig. Het canvas element moet geïnitialiseerd
worden en de shaders (vertex en fragment) moeten correct aangemaakt worden. Dit gebeurt in initGL() en
initShaders(). Er moet informatie zijn over hoe de textures weergegeven moeten worden en vooral waar.
Hiervoor dienen de buffers en deze worden gestart via initBuffers(). Verder zijn er nog functies die de interactie
met de interface regelen maar dit wordt besproken in het volgende hoofdstuk. De laatste functie, drawScene(),
tekent alle buffers naar de standaard framebuffer, het scherm.
III.1.1.1 InitGL() en initShaders();
InitGL() is de functie die het canvas element definieert, en hierop een webgl context laadt. Deze code werd
reeds besproken in II.2.1. initShaders() is een standaard script dat de shaders inlaadt voor zowel de fragment
als vertex shaders.5 (16) In het index bestand kan van lijn 12 tot 38 het stuk code gevonden worden dat de
shaders bepaalt.
III.1.1.2 initBuffers()
Er worden een reeks buffers aangemaakt die de data bevatten van de planes die getekend gaan worden . Voor
elk standaard canvas element wordt er een buffer gemaakt die 2D data weergeeft. Voor het vierde canvas
element worden er andere buffers aangemaakt die uit een kubus bestaan. De 2D canvas elementen hebben
echter genoeg aan een vierkant. De juiste buffer zal ook aangemaakt worden a.d.h.v. de integer die
meegegeven werd tijdens het oproepen van de runWebGL() functie. Als het getal ‘1' bijv. meegegeven werd,
zullen de coronaal buffers geladen worden voor het betreffende canvas element.
Voor elk vierkant worden drie buffers bijgehouden. Deze zijn SquareXVertexPositionBuffer,
SquareXVertexTextureCoordBuffer en SquareXVertexIndexBuffer. De eerste buffer houdt de coördinaten bij
van waar precies in het cartesisch coördinatenstelsel het vierkant moet komen. In Figuur 8 wordt getoond hoe
het assenstelsel opgebouwd is. Volgende snippet toont de coördinaten voor de hoeken van het coronaal
vierkant:
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
vertices = [
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
];
5 Schriftelijke toestemming verkregen van Giles Thomas, auteur van learning WebGL. De WebGL code in deze
masterproef is gebaseerd op zijn ‘learning webgl’ lessen.
18
Het vierkant start links onderaan op het x,y coordinaat (-1,-1). Doordat het vierkant in het midden staat is er
een z-waarde van 0.0. Indien men het vierkant meer naar achter wil kan er gekozen worden voor een z-waarde
die negatief is. Een positieve z-waarde brengt het vierkant meer naar voor. Dit vierkant zoekt verder de uiterste
coördinaten op om zo een vierkant te vormen. De squareVertexTextureCoordBuffer bevat de coördinaten voor
de textures. Deze coördinaten komen overeen met de coördinaten die bepaald zijn in de vorige buffer. Het
verschil zit in het feit dat er geen z waarden nodig zijn aangezien het een 2D beeld betreft.
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexTextureCoordBuffer);
var textureCoords = [
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
];
De laatste buffer is een buffer die weergeeft welke hoeken van de VertexPositionBuffer moeten hergebruikt
worden. Bij de drawScene functie worden voor de visualisatie van een vierkant twee rechthoekige driehoeken
gebruikt om een vierkant te tekenen. Het is zo dat WebGL ondersteuning biedt voor gl.triangles, gl.lines en
gl.sprites. (12) Er is gekozen om de vierkanten op te bouwen uit twee rechthoekige driehoeken. Hierbij dient de
squareVertexIndexBuffer als buffer voor het hergebruik van de hoeken uit positionBuffer.
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareVertexIndexBuffer);
var squareVertexIndices = [
0, 1, 2, 0, 2, 3,
];
Onderstaande Figuur 7 geeft een duidelijker beeld van wat deze laatste buffer precies doet. De hoeken 1 en 3
worden slechts een keer gebruikt, maar hoeken 0 en 2 worden tweemaal gebruikt.
Figuur 7 - Hergebruik hoeken
19
De lezer kan op dit punt de opmerking maken waarom er voor drie canvassen verschillende buffers worden
gemaakt die dezelfde waarden bevatten. De reden voor deze manier van werken bestaat uit twee elementen.
Ten eerste zijn sommige textures (bijv. sagittaal) na het renderen in fase 1 gedraaid. Dit is vrij eenvoudig op te
lossen door de texture coördinatenbuffer aan te passen. De volgorde van de coördinaten veranderen zorgt er
voor dat de afbeeldingen met de klok meedraaien. Ten tweede is het niet mogelijk om een buffer meerdere
malen te gebruiken op verschillende canvas contexten. (19) Wanneer een buffer object gebonden wordt aan
een rendering context, zegt men in principe dat WebGL alle volgende bufferoperaties zal uitvoeren op het
gebonden buffer object. Indien met het buffer object wil binden aan verschillende GL_ARRAY_BUFFER doelen,
zal dit niet lukken omdat een eerder buffer object reeds gebonden is. (14)
Dit werd ook empirisch bevestigd tijdens het ontwikkelen van webapplicatie. Figuur 10 in bijlage B is een
screenshot van de error die WebGL aan de console zal doorgeven indien men probeert om een buffer object of
texture die gebonden is aan een andere context, te binden aan zijn eigen context.
III.1.1.3 drawScene()
De functie drawScene() is de functie die instaat voor de weergave van de textures en vierkanten in de canvas
elementen. Wederom zullen de juiste buffers getekend worden a.d.h.v. de integer die gekozen is bij de start
van runWebGL(). Bij het tekenen van elk vierkant wordt eerst de mvMatrix op een matrix stack geplaatst
(push). Zo kan deze matrix na aanpassing tijdens drawScene() opnieuw van de stapel gehaald worden (pop), om
zo eventuele aanpassingen teniet te doen. De mvMatrix is de matrix die eventuele aanpassingen van het
vierkant, (inzoomen, roteren, verplaatsingen) uitvoert. De projectie matrix of pMatrix is de matrix die er voor
zorgt dat objecten die verder verwijdert zijn in de scene kleiner voorgesteld worden. (16) Volgende snippet
toont de rest van de functie.
mvPushMatrix();
mat4.translate(mvMatrix, [0.0,0.0, -2.42]);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexTextureCoordBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,
squareVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, corTexture[beeldC]);
gl.uniform1i(shaderProgram.samplerUniform, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareVertexIndexBuffer);
setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, squareVertexIndexBuffer.numItems,
gl.UNSIGNED_SHORT, 0);
mvPopMatrix();
20
mat4.translate bepaalt dat het vierkant zich in het midden van de scene (x: 0, y:0) moet bevinden en een z-
waarde moet hebben van -2.42. Deze laatste waarde is empirisch bepaald om zo een volledige opvulling van
het canvas element te bekomen. Verder worden de drie buffers gebruikt om een vierkant te bekomen.
Gl.activeTexture zet TEXTURE0 als actieve texture. Er kunnen maximaal tijdens elke call van drawElements 32
textures getekend worden. (11)
Al de textures die in de eerste fase getekend werden zijn beschikbaar via TEXTURE0. setMatrixUniforms() is een
functie die zorgt dat de aanpassingen aan de mvMatrix en pMatrix doorgestuurd worden naar de grafische
kaart. Gl.drawElements tekent de driehoeken met hergebruik van bepaalde hoeken om zo een vierkant te
vormen.
III.2 Genereren naar 3D scene
Dit gedeelte gaat dieper in over de werking van het vierde canvas element. Deze canvas werkt volgens
hetzelfde principe als de overige drie canvassen maar geeft een 3D visualisatie weer in plaats van een statisch
2D beeld. Hier is het de bedoeling om de overige drie slices, coronaal sagittaal en transversaal, te combineren
tot een 3D voorstelling. Onderstaande Figuur 8 geeft een voorstelling van de drie planes in een kubus. In deze
kubus stelt het blauwe vierkant het sagittaal beeld voor, het groene het transversale en het rode vierkant stelt
het coronaal beeld voor.
Om deze visualisatie mogelijk te maken zijn er andere buffers nodig dan degene van de statische canvassen. De
coördinaten moeten aangepast worden zodat bijv. een coronaal beeld vlak getoond wordt. Hiervoor is er
gebruik gemaakt van de z-waarde. In de applicatie kan men de code voor het aanmaken van de buffers voor de
kubus vinden van lijn 1053 tot 1266.
Figuur 8 - Cartesisch coördinatenstelsel (bron: Eigen verwerking en (17))
21
De functie tick() is de laatste functie die een bepalend onderdeel is van drawScene(). Deze functie zorgt voor
het aanroepen van een nieuw annimationframe. (16) Internetbrowsers hebben verschillende manieren om de
verversing van getekende scene’s uit te voeren, daarom is er gekozen om een browseronafhankelijk stuk code
in te voegen in de head-sectie van de index-pagina (webgl-utils.js6). Bij elke uitvoering van de functie
requestAnimFrame(tick) wordt de getekende scene ververst en wordt de callback functie ‘tick’ meegegeven.
Wat de textures betreft, is er gekozen voor een oplossing die het mogelijk maakt om data te transfereren
tussen canvas elementen. Zoals reeds eerder besproken III.1.1.2 is het niet mogelijk om data te transfereren
tussen WebGL elementen aangezien deze een verschillende context hebben. Wat wel mogelijk is om data uit
een bepaald canvas element te gebruiken in een ander canvas element, is het gebruik van de functie
.toDataURL(). (6)
Men kan .toDataURL() oproepen op een gegeven canvas element. Bijv:
var canvas1 = document.getElementById("canvasCoronal");
var myImage = canvas1.toDataURL("image/png");
In het voorbeeld uit bovenstaande snippet zal myImage een afbeelding bevatten die gerenderd werd op het
canvas, onafhankelijk of het nu een WebGL context of een 2D context is. Het is verder nog mogelijk om andere
bestandsformaten te kiezen dan png. Indien geen argumenten meegegeven worden met .toDataURL() dan zal
een standaard png gebruikt worden.
De functie die instaat voor de opslag van de png afbeeldingen uit de drie canvas elementen is
renderCSTtoTex(). Deze functie wordt eenmalig opgeroepen om de initiële textures voor de 3D scene te
renderen. Deze functie bestaat uit drie variabelen die de het canvas element uit het DOM omvatten, en een
variabel myImage die een .toDataURL() methode gebruikt op een canvas element om een png afbeelding te
nemen.
Wanneer dit gebeurt, moet de WebGL context van het vierde canvas element gedefinieerd worden zodat er
textures gemaakt kunnen worden van deze afbeeldingen. Door hier te definiëren dat het om textures gaan
voor het vierde canvas element (“fullVolume”), zullen deze probleemloos getoond kunnen worden aangezien
ze voor de juiste context gemaakt worden. De code voor het aanmaken van textures is reeds gekend en is te
vinden vanaf lijn 65.
6 Google Inc, 2010.
22
III.3 Alternatieve oplossingen
Een alternatieve voorstellingswijze van de vier canvas elementen bestaat in het renderen van één canvas
element met daarin een combinatie van de drie planes coronaal, sagittaal en transversaal en een 3D canvas
element. Dit heeft enkele voor –en nadelen. Een eerste voordeel is dat het 3D element geen gebruik hoeft te
maken van een .toDataUrl() van de overige canvassen. Dit zorgt voor een snellere weergave en vloeiender
geheel van het 3D volume.
Een nadeel is dat een belangrijke eigenschap van de applicatie een ‘click & go’ functie is. Deze functie is veel
moeilijker implementeerbaar in het geval van één canvas element aangezien het coördinaatsysteem dan maar
één keer aanwezig is. Het is mogelijk om dit toch te implementeren, maar dit gaat dan ten koste van de
accuraatheid van de applicatie. Omwille van deze moeilijkheden is deze techniek niet gekozen.
Een tweede alternatief is het renderen van de afbeeldingen op een 2D canvas element zonder het gebruik van
WebGL. Er bestaan Javascript engines die een 3D scene kunnen renderen.7 Het grote nadeel aan deze
oplossingen is dat de snelheid te wensen overlaat aangezien de 3D engine ontbreekt en de grafische kaart niet
direct aangesproken wordt zoals in WebGL.
Het tweede nadeel is dat WebGL betere kansen heeft voor de toekomst dan deze engines. De ondersteuning
voor deze techniek is dan ook helemaal niet verzekerd voor de toekomst. Een voordeel is dat deze techniek op
meer internetbrowsers werkt dan WebGL. Momenteel hebben enkel Chrome, Firefox 4 en Safari onder Mac Os
X ondersteuning voor WebGL. De browsers die deze applicatie ondersteunt zijn Google Chrome en Mozilla
Firefox 4.
7 https://github.com/mrdoob/three.js/
23
III.4 Conclusie
Dit hoofdstuk besprak de visualisatie van de drie slice richtingen coronaal, sagittaal en transversaal in een
visualisatie van drie canvas elementen. De drie canvassen werden dan gecombineerd tot een 3D voorstelling
die een visualisatie geeft van de data. Er werd besproken hoe deze techniek precies werkt en op welke manier
de beelden op het scherm getoond worden.
24
Hoofdstuk IV Werking van de applicatie
In dit hoofdstuk is het de bedoeling van de werking van de applicatie te bespreken. Dit is waarschijnlijk het
belangrijkste hoofdstuk omdat hier in detail nagegaan wordt hoe de applicatie precies zijn werk doet en waar
de problemen en compromissen zitten. Dit zal hoofdzakelijke chronologische doorlopen worden tijdens de
bespreking van de applicatie interface.
IV.1 Applicatie Interface
De applicatie-interface is de wijze waarop interactie plaatsvindt met de gebruiker en tevens het belangrijkste
onderdeel van de applicatie. Een applicatie die goed werkt maar een slechte interface heeft, zal een minder
goede gebruikservaring bieden dan wanneer er een intuïtieve, doordachte interface is. Om dit te bekomen is er
gebruik gemaakt van drie fasen. De eerste fase is een input fase waar de gebruiker aangeeft welke beeldenset
geladen moet worden. Een tweede (tijdelijke) fase geeft aan wat de stand is van de verwerking van de
beeldenset. Een derde en laatste fase betreft de effectieve visualisatie van de beeldenset. Deze fasen zullen in
volgende drie punten uitgebreid besproken worden.
IV.1.1 Offline applicatie-gebruik
Alvorens te bespreken hoe de applicatie opgebouwd is, is het belangrijk om aan te geven hoe de applicatie
offline werkt. Het is immers niet mogelijk om de applicatie direct offline te gebruiken zonder dat er enkele
aanpassingen gebeuren aan de internetbrowser. Indien de lezer van deze masterproef meteen de applicatie
opstart en test, zal men concluderen dat er geen visualisatie komt. Er zal bovendien een melding komen
“Cannot read settings.csv file”. De reden hiervan ligt in het feit dat internetbrowsers een beveiliging ingebouwd
hebben om bestanden van de lokale schijf te lezen. Bij chrome zal men typisch de volgende fout krijgen in de
console:
Uncaught Error: SECURITY_ERR: DOM Exception 18
Internetbrowsers hebben deze beveiliging ingebouwd zodat er geen offline bestanden kunnen gelezen worden.
Wil men de applicatie echter toch offline gebruiken, moet men in de shortcut van google chrome volgende text
toevoegen:
--allow-file-access-from-files
Op deze manier zal Google Chrome wel als offline applicatie werken. Mozilla Firefox 4 heeft dezelfde
beveiliging ingebouwd. Onderstaande fout zal in de console verschijnen.
uncaught exception: [Exception... "Security error" code: "1000" nsresult: "0x805303e8
(NS_ERROR_DOM_SECURITY_ERR)"
25
Om dit te voorkomen moeten men volgende stappen ondernemen:
In de adresbalk typt men: about:config en entert. Vervolgens filtert men op ‘origin’ en zet men de waarde van
‘security.fileuri.strict_origin_policy’ op false. Nadat deze aanpassing doorgevoerd is, kan de gebruiker de
applicatie offline gebruiken.
Of deze aanpak geoorloofd is hangt af van het doel van de applicatie en de organisatie. Deze maatregelen die
ingebouwd zijn in de browsers hebben een doel, en normaal gezien zou men deze parameters niet mogen
aanpassen omwille van de veiligheid van het systeem. Men kan de applicatie enkel via bovenstaande manier
offline gebruiken, en het hangt dan ook van de gebruiker af of hij deze beveiligingsrisico’s aanvaardt.
IV.1.2 Input scherm
Het input scherm is het eerste scherm waarmee de gebruiker geconfronteerd wordt. In bijlage B kan men een
screenshot aantreffen van het betreffende startscherm. Bovenaan het scherm bevindt er zich een titel die
dezelfde naam draagt als deze masterproef. Onder deze titel bevinden zich drie zones die informatie bevatten
over de betreffende datasets.
IV.1.2.1 Zone 1
Deze zone bevat informatie over de aanwezige datasets op de server. Hier moet de opmerking gemaakt
worden dat de applicatie volledig client-side werkt. Dit wil zeggen dat er geen gebruik gemaakt is van eventuele
server-side scripting. De enige code die in deze applicatie gestart wordt is in de pc/client van de gebruiker zelf.
Deze design keuze brengt wel enkele beperkingen met zich mee. Zo is het met Javascript bijv. niet mogelijk met
het lokale bestandssysteem te interageren. Dit wil zeggen dat mappen niet gelezen kunnen worden, en dat er
niet gelezen of geschreven kan worden naar de lokale schijf. (Dit zou een beveiligingsprobleem betekenen
aangezien kwaadwilligen toegang zouden kunnen krijgen tot het lokale bestandssysteem met alle gevolgen van
dien).
Er zijn echter wel mogelijkheden om het gebrek aan bestandsinteractie te omzeilen. Het niet mogelijk om
mappen en hun inhoud weer te geven, maar een bestand uitlezen (met informatie over de bestandslocaties) is
wel mogelijk. Een methode die al gedurende een redelijke periode mogelijk is in Javascript is XMLHttpRequest.
(13) Deze API zorgt er voor dat browsers informatie kunnen ophalen van een server zonder een pagina te
verversen. Deze techniek werd gebruikt om in de eerste zone de beschikbare datasets op te lijsten. Een lijst van
knoppen worden getoond om aan te geven welke datasets er beschikbaar zijn, en dit zowel online als offline.
(Offline indien de applicatie gebruikt zou worden als een lokale applicatie, maar deze applicatie werkt even
goed online). Indien er een map met driedimensionale data bijgekomen is en men het settings.csv bestand
bijgewerkt heeft, dan zal deze beschikbaar worden in de webpagina. Mappen die verwijderd werden zullen niet
meer getoond worden. Hoe dit in zijn werk gaat zal uitgelegd worden later in deze masterproef, maar hier kan
reeds gezegd worden dat dit gebeurt a.d.h.v. het uitlezen van een settings.csv bestand, meer in onderdeel
IV.1.2.6.
26
IV.1.2.2 Zone 2
Deze zone toont informatie over de beschikbare dataset. Dit gebeurt door met de muis te ‘hoveren’ over de
knop van de dataset. Tijdens dit event zal Javascript in een vooraf gedefinieerde div informatie uit het eerder
vermelde settings.csv bestand beschikbaar stellen in deze zone. De wijze waarop dit precies gebeurt komt aan
bod tijdens de bespreking van de applicatiecode. Hier kan reeds gezegd worden dat Javascript interactie heeft
met het DOM, en hier dynamisch aanpassingen doet aan de div.
IV.1.2.3 Zone 3
De laatste zone geeft vier keuzemogelijkheden weer voor de groottes van de canvas elementen. Deze groottes
zijn: small 128px, normal (originele pixels), large 512px en Extra large 1024px. Wanneer de radio buttons
aangeklikt worden, zal er een variabele ingesteld worden die juiste grootte doorgeeft aan de rest van de
applicatie. De groottes zijn zo gekozen zodat de gebruiker zelf kan aangeven welke het beste past op zijn
scherm. Het zou ook mogelijk zijn om een automatische aanpassingen doen aan de beschikbare resolutie van
de browser. Onderstaande pseudocode geeft aan hoe dit mogelijk is.
var hoogte = window.innerHeight
var groottecanvas = null;
functie {
Array = [0,2,4,8,16,32,64,128,256,512,1024,2048,4096];
for (var i=0; i<Array.length; i++){
if( hoogte < Array[i])
groottecanvas = (Array[i-1]/2);
break;
}
}
Deze functie gaat eerst de viewport opslaan van het scherm. Deze methode is niet voor iedere browser
dezelfde. Chrome en Firefox (18) hanteren dezelfde implementatie, Internet Explorer gebruikt echter een
andere manier dus een browseronafhankelijke implementatie is nodig, ondanks het feit dat Internet Explorer
nog geen WebGL ondersteunt.
De functie gaat verder na of de variabele hoogte kleiner is dan een integer op plaats i in de array. Deze array
bestaat uit een reeks van machten van twee dewelke nodig zijn voor de goede werking van WebGL. Vanaf het
moment dat de hoogte kleiner geworden is dan het getal op de ide
plaats, betekent dit dat het gekozen getal te
groot is voor het scherm. Het vorige getal uit de array wordt dan gedeeld door twee, en opgeslaan in de
variabele groottecanvas. Er wordt door twee gedeeld aangezien er vier canvas elementen zijn waarbij er twee
onder elkaar staan en dus elk de helft (ongeveer) van de viewport innemen.
27
IV.1.2.4 Onderste rij: Dropbox
Op de onderste rij is er gekozen om een drop box of drop zone te plaatsen. (19) Deze zone biedt de
mogelijkheid om een lokale dataset in te laden in de webpagina. Ondanks dat het feit dat Javascript een client-
side technologie is, is het wel mogelijk van de lokale schijf bestanden in de browser te laden. (20) Het is
mogelijk om een file upload element in te voegen in de browser. Het is echter met HTML5 mogelijk om een
input element toe te voegen met een multiple field op true gezet. (20) Bijv:
<input type="file" id="input" multiple="true"
onchange="handleFiles(this.files)">
Op deze manier is het mogelijk om een ‘browse-knop’ in te voegen in de webpagina, en meteen een volledige
dataset te selecteren omwille van het multiple veld dat op true staat. De reden waarom de lezer geen browse
knop vindt in Figuur 13, is omwille van een limiet in Firefox 4. Tijdens het schrijven van de applicatie bleek het
niet mogelijk te zijn om de volledige dataset ‘head-ct’ te selecteren via het input field. Het was enkel mogelijk
om 388 afbeeldingen te selecteren, 389 afbeeldingen en meer was niet mogelijk. Ondanks het feit dat er in de
HTML5 File API geen melding is van een limiet in de input box, doet alles wijzen op fysische limiet op het aantal
karakters in deze input box. Google Chrome heeft deze limiet niet, maar omwille van het feit dat de applicatie
even goed moet werken op beide browsers is er gekozen om deze functie niet toe te voegen.
De Drop box/zone biedt dezelfde functionaliteit als het file upload element, maar heeft geen beperking op het
aantal afbeeldingen van de dataset. HTML5 kan een filelist bekomen van de afbeeldingen die zojuist in de
dropzone gesleept werden. (19) Via deze lijst kunnen nu afbeeldingen aangemaakt worden en kan de applicatie
verder werken als normaal.
IV.1.2.5 Functie start()
Wanneer de gebruiker eenmaal op een bepaalde knop van de datasets klikt, of een volledige dataset ‘dropt’ in
het venster, worden de juiste variabelen ingesteld en kan de verwerking beginnen. Hoe dit precies gebeurt
wordt uitgelegd in het volgende onderdeel.
IV.1.2.6 Applicatiecode achter de input pagina
Om de eerste zone te voorzien van knoppen met een link naar de juiste dataset is er een functie geschreven die
een settings.csv bestand uitleest. Onderstaand snippet toont de kern van het uitlezen van settings.csv.
try{
var arrayRead = [];
var text = "";
var req = new XMLHttpRequest();
req.open('GET', 'data/settings.csv', false);
req.send(null);
text += req.responseText;
if ( text.toString() == ""){
//No local system, trying online request
var req2 = new XMLHttpRequest();
req2.open('GET',
'http://studwww.ugent.be/~mcornet/data/settings.csv', false);
28
req2.send(null);
if(req.status == 200){
text += req2.responseText;
}
}
try{
arrayRead = text.split(",");
laatste = (arrayRead.length/4);
for (var t = 0; t < (arrayRead.length/4)+1 ; t++){
var temp = [];
temp[0] = arrayRead.shift();
temp[1] = arrayRead.shift();
temp[2] = arrayRead.shift();
temp[3] = arrayRead.shift();
dataArray.push(temp);
}
}
De volledige code kan gevonden worden in index.html vanaf lijn 181.
XMLHttpRequest is een API die vragen (request) kan stellen aan een server en onmiddellijk antwoord kan
krijgen. In de webapplicatie trachten we een bestand uit te lezen, en a.d.h.v. deze informatie de beschikbare
datasets in een lijst weer te geven. In een eerste fase moet er een XMLHttpRequest object aangemaakt
worden. Nadien kan er via object.open(‘GET’, ‘locatie, false/true) een connectie geopend worden. Standaard
zal de applicatie eerst kijken of het een offline file-systeem betreft, en kan nadien via req.responseText het
bestand gelezen en opgeslaan worden. (21) De status code die teruggestuurd wordt naar de client is de http
status code. Indien de webapplicatie zich bevindt op een webserver, is er een andere manier nodig om
XMLHttpRequest te gebruiken. Als de status code gelijk is aan 200 (succes) kan er overgegaan worden tot het
opslaan van settings.csv als text.
In een tweede gedeelte wordt de variabele text gesplitst op komma’s. Het resultaat van deze actie is een array.
Aangezien alle datasets in één csv-bestand staan, is het makkelijker om een tweedimensionale array te maken.
Op deze manier kan men bvb de naam van de 3e dataset vinden via: “dataArray*2+*0+. Één array voor alle
datasets zou de zaken nodeloos compliceren daarom is er gekozen voor deze oplossing. De manier van werken
is als volgt: uit de originele array worden de eerste vier element genomen via array.shift(). Deze methode
verwijdert het eerst element uit een array. Er wordt een nieuwe array aangemaakt en deze wordt in de
hoofdarray geplaatst (dataArrary). Na deze operatie is het mogelijk om van elke datasets de informatie uit te
lezen uit dataArray.
Het settings.csv bestand bevat verder vier elementen gescheiden door een komma. Deze zijn de naam van de
dataset en tevens ook de mapnaam in data/. Het aantal afbeeldingen, breedte (width) en hoogte (height). Een
belangrijke opmerking hier is dat er in het settings.csv bestand na het allerlaatste element, nooit een komma
mag staan aangezien de applicatie dan denkt dat er nog een variabele volgt.
29
Na deze functie, is het mogelijk om via DOM-interactie dynamisch de knoppen aan te maken. Via
document.createElement kan men een nieuw element aanmaken en dit dan in het DOM plaatsen. Vanaf lijn
229 in index.html kan men de volledige code vinden voor het aanmaken van de knoppen.8
Zone 2 wordt op een vrij eenvoudige manier gemaakt via twee functies: hover() en out(). Deze functies passen
het DOM aan elke keer de gebruiker met de muis over een knop staat. De informatie uit dataArray[] wordt
uitgelezen en tussen <p></p> geplaatst en toegevoegd aan het DOM. Deze code kan gevonden worden vanaf
lijn 288 in index.html. De laatste zone zal bij een onchange event van de radio knoppen een functie oproepen
check(num). Deze functie zet de variabele radio op de waarde die meegegeven werd met de parameter.
De laatste code die besproken dient te worden is de drop zone, lijn 248. (22) Deze zone wordt gemaakt via een
div met een achtergrondafbeelding9 waarop functies gestart worden wanneer er bepaalde events plaatsvinden.
De drop functie is de belangrijkste, deze functie vraagt de bestandslijst op via e.dataTransfer en object.files.
Nadat deze lijnen uitgevoerd zijn heeft Javascript de beschikking over de bestandslijst en kunnen de
afbeeldingen aangemaakt worden.
IV.1.2.7 Redirect()
Vanaf het ogenblik dat de knop ingedrukt wordt, of er een dataset gesleept wordt, start de applicatie met
verwerken. Dit gebeurt via de functie redirect() en redirect2(filelist). Redirect() is vrij eenvoudig; deze functie
stelt enkel parameters in zoals path, numImages width, height en size. De twee belangrijkste elementen uit
deze functie zijn de bepaling van de variabele max en sizeCubeC(S)(T). De oorsprong van deze variabelen
schuilt in de problematiek met datasets die geen macht van twee zijn. (14) WebGL ondersteunt slechts
gedeeltelijk Non-power-of-two textures/afbeeldingen, en daarom is er gekozen om datasets die geen macht
van twee zijn aan te passen.
De aanpassing bestaat erin dat er een kubus zal gebouwd worden omheen de dataset zodat deze toch een
macht van twee wordt. De afmetingen van de kubus is dan afhankelijk van de grootste van volgende drie
variabelen: height, width en numImages. NumImages bepaalt immers de diepte van de kubus, terwijl height en
width de hoogte en breedte bepalen. Het is nu de bedoeling een macht van twee te kiezen die volgt op
variabele die het grootst is. Op deze manier kan het canvaselement aangepast worden aan deze max-variabele,
en zullen rechthoekige datasets toch visualiseerbaar zijn in de applicatie. Een gedeelte van de kubus zal
weliswaar zwart zijn omdat er niet genoeg data is om een volledige kubus te visualiseren. De visualisatie betreft
immers een balk. De gebruiker kan de dataset ‘rectangle-test’ uitproberen om de mogelijkheden van een
rechthoekige dataset te testen.
8 Design knoppen: GPL via http://www.oscaralexander.com/. Design gecentreerde box in index.html: public
domain via http://sperling.com/examples/box/ 9 Achtergrondafbeelding ‘drop images here’: Eigen verwerking
30
Het tweede belangrijke element uit deze functie is de maximale afstand waarover een plane mag verschuiven.
Als de dataset geen perfecte kubus is, dus bijv. niet 256x256x256 pixels of een andere macht van twee, dan zal
er sowieso ergens een gedeelte van de kubus zwart zijn wegens geen data. Op dat ogenblik mogen bepaalde
planes nooit bewegen tot in dit zwarte gedeelte maar maximaal tot er juist voor. De volgende drie variabelen
zorgen hier voor.
sizeCubeC = ((height/max)*2);
sizeCubeS = ((width/max)*2);
sizeCubeT = ((numImages/max)*2);
Aangezien de grootte van de kubus in WebGL ‘2’ is, (van -1 tot 110
), en max de grootste macht van twee is
waarin de dataset net past, dienen bovenstaande variabelen om aan te geven hoever de drie planes maximaal
mogen bewegen. Neem de dataset brain-vessels als voorbeeld. De diepte (numImages) is in dit geval 230
terwijl breedte en hoogte 256 zijn. Het transversaal plane mag dan max tot en met afbeelding 230 schuiven,
maar nooit verder. Vanaf 230 tot 256 is er immers geen data beschikbaar. Door de 230 afbeeldingen te delen
door de variabele max, bekomt men een perunage dat men vermenigvuldigt met de grootte van de kubus, 2.
De transversaal plane mag dan maximaal tot positie 1,796875 bewegen. Dit principe geldt ook voor de overige
drie planes. Coronaal en Sagittaal zullen in dit geval over de volledige grootte mogen bewegen aangezien
((256/256)*2)=2 is.
Een laatste belangrijke variabele is ‘stap’. Deze variabele geeft aan met welke afstand een plane mag bewegen.
De vorige drie variabelen gaven aan tot waar men mag bewegen, de variabele stap geeft aan met welke
afstand elke beweging men mag verplaatsen. Aangezien max de grootste macht van twee is, en de grootte van
de kubus 2 is, is de stap die elke keer mag gezet worden gelijk aan de deling van deze beiden. Het is de
bedoeling dat elke plane zich over een gelijke afstand verplaatst bij elke beweging, en dit ongeacht het aantal
beelden elke slice richting bezit. Enkel door elke slice richting dezelfde stappen te laten bewegen is het
mogelijk een correcte visualisatie te geven.
IV.1.3 Verwerkingsfase
De verwerkingsfase is reeds grotendeels besproken in II.1. Wat echter nog niet besproken is, is de manier
waarop datasets via de drop zone afgehandeld worden. Indien men een dataset sleept in het browservenster,
wordt de functie redirect2() opgeroepen, en start de applicatie de functie numImages() met een getal als
parameter. Indien dit getal gelijk is aan 2, dan wordt een apart gedeelte van renderImages() uitgevoerd.
Hieronder een snippet van de code.
for (var i = 0 ; i < files.length; i++) {
var file = files[i];
imageArray[i] = new Image();
imageArray[i].onload = function(){
10
Zie Figuur 8
31
loadedImages++;
if (loadedImages == files.length){
textureRender();
}
}
imageArray[i].src = file;
var reader = new FileReader();
reader.onload = (function(aImg) { return function(e) { aImg.src =
e.target.result; }; })(imageArray[i]);
reader.readAsDataURL(file);
}
Deze for-loop heeft dezelfde functionaliteit dan de for-loop die start op lijn 223. Deze loop handelt echter de
filelist uit de dropzone af, terwijl de eerste for-loop een bestandslijst uit settings.csv afhandelt. Deze loop
maakt in een array afbeeldingen aan, en controleert elke keer of alle afbeeldingen aangemaakt zijn vooraleer
textureRender() te starten (preloading). De filereader zorgt voor het asynchroon laden van de afbeeldingen en
het koppelen van het .src attribuut. ReadAsDataURL() zorgt voor het laden van de afbeelding. (22)
De gebruiker krijgt tijdens het verwerken een progressbar te zien. Deze wordt ook dynamisch aangepast via
DOM-interactie.
IV.1.4 Output scherm
Na de inputfase start de applicatie met de laatste fase van de interface, de visualisatie. Wanneer alle textures
aangemaakt zijn, zal de functie runCST() gestart worden. Deze functie zal eerst de stijl van de progressbar op
“none” zetten. Op deze manier is de progressbar niet meer zichtbaar en kan de rest van de visualisatie getoond
worden. Zo is ook het gebruik van meerdere pagina’s voor elke interface overbodig geworden. De stijl van de
outerwrapper, het div object waar de canvas elementen en sliders in verwerkt zijn, wordt weergegeven via
onderstaande code. Dit is hetzelfde principe als het verbergen van de progressbar.
document.getElementById("outerwrapper").style.display = "block";
Na dit gedeelte is het de bedoeling dat elk van de drie canvaselementen gestart worden. Deze starten via
runWebGL(canvas, nummer), waarbij canvas het canvas-element uit het DOM is en ‘nummer’ de waarde is die
aangeeft welke slice richting het betreft.
De functie renderCSTtoTex() wordt gestart en creëert drie textures van de canvas-elementen. Nadat dit
voltooid is kan de functie runWebGL(fullVolume, 4) gestart worden. Zo is de 3D visualisatie van de beeldenset
compleet, en heeft de gebruiker een overzicht van de beeldenset in coronaal, sagittaal, transversaal en een
volledig volume in een 3D kubus. Na dit gedeelte kan de gebruiker interageren met de webapplicatie, door
gebruik te maken van de verschillende visuele elementen. Het betreft de slider elementen, de Mouse-scroll
events, het klikken met de muis in het canvas, en het gebruik van het 3D volume en de knoppen. Deze worden
in de volgende punten behandeld.
32
IV.1.4.1 Sliders & canvas grootte
In dit onderdeel worden de sliders besproken en de groottes van de canvas elementen. De sliders zijn een van
de belangrijkste manieren van interactie met de dataset. De sliders die gebruikt worden in deze webapplicatie
maken gebruik van de bibliotheek jQuery Tools.11
Deze bibliotheek, gratis en vrijgegeven zonder
licentiesysteem, is afgeleid van het populaire jQuery12
(General Public License), maar richt zich vooral op
stijlvolle componenten voor moderne websites.
De sliders worden geïnitialiseerd tijden de functie initUI(). Deze functie start de sliders, maar stelt ook de
groottes van de canvassen in. Tijdens de input-fase, kon men de radio-button kiezen met de grootte die het
beste past bij het scherm van de gebruiker. Deze variabele size had een waarde tussen 1 en 4, en via deze
getallen kunnen nu de groottes van de canvassen aangepast worden. Onderstaande snippet toont hoe dit
gebeurt.
//Dynamically give the webgl canvas the correct height and width
if(size == 1 )c_size = 128;
if(size == 2 )c_size = max;
if(size == 3 )c_size = 512;
if(size == 4 )c_size = 1024;
document.getElementById("canvasCoronal").setAttribute("width", c_size);
document.getElementById("canvasCoronal").setAttribute("height", c_size);
document.getElementById("canvasSagittal").setAttribute("width", c_size);
document.getElementById("canvasSagittal").setAttribute("height", c_size);
document.getElementById("canvasTransversal").setAttribute("width", c_size);
document.getElementById("canvasTransversal").setAttribute("height",
c_size);
document.getElementById("fullVolume").setAttribute("width", c_size);
document.getElementById("fullVolume").setAttribute("height", c_size);
Het correct aantal pixels wordt ingesteld naar gelang de waarde van size. Max is de variabele die de grootte van
de kubus aangeeft. Dit wil dus zeggen dat indien men bijv. een dataset heeft van 256x256x230 zoals in de
‘brain-vessels’ dataset, men canvaselementen heeft van 256 op 256 pixels. Het neemt dus originele waarde aan
van de dataset waarin minimaal alles gevisualiseerd kan worden. De overige drie groottes zullen een vaste
waarde instellen. Aanpassingen aan het DOM is wederom de wijze hoe hier tewerk is gegaan. De canvassen
hebben de duidelijke naam canvas+slice richting, en het 3D gedeelte noemt fullVolume. Door de attributen aan
te passen voert men de aanpassingen effectief door.
De initialisatie van de sliders gebeurt via de code die start op lijn 483. De constructor voor de initialisatie van de
sliders is gegeven in onderstaande snippet.
$("#graph_cor").rangeinput({
11
http://flowplayer.org/tools/release-notes/index.html 12
http://jquery.org/license/
33
Door de opgave van .ranginput weet jQuery Tools dat het een slider object betreft, en zal de slider
gevisualiseerd kunnen worden. Indien het script niet zou laden heeft jQuerytools een fallback systeem waarin
de browser native sliders getoond worden. De belangrijkste events die jquery tools aanbiedt zijn het onSlide en
change event. Het change event zal een bepaalde code uitvoeren zodra de onderliggende waarde van de slider
verandert. Het onSlide event zal bepaalde code uitvoeren van zodra de slider zelf verandert. Dit is bijzonder
handig om snel door de beeldenset te ‘zoomen’.
onSlide: function (event, step) {
beeldC = step;
coronalMove = (1-(step * stap));
},
change: function (event, value) {
beeldC = value;
coronalMove = (1-(value * stap));
}
Indien de eventlistener een verandering merkt in de slider zelf, voert deze de onSlide functie uit. Deze functie
bestaat erin om de waarde van de slider door te geven aan de variabele beeldC. Deze variabele is te allen tijde
de afbeelding die getoond zal worden in het coronaal canvas. coronalMove is de variabele die de positie
bijhoudt van het coronaal plane in de kubus. Aangezien de visualisatie van deze plane start op het uiterste van
de kubus (1) zijn alle veranderingen in positie een verandering die afgetrokken worden van dit uiterste.
Hoeveel dit juist is hangt af van het geselecteerde beeld (step) en de waarde van stap. Merk op dat stap voor
elke slice richting hetzelfde is. Door beide getallen te vermenigvuldigen bekomt men de positie naar waar de
plane mag verplaatst worden.
Gelijkaardige code wordt uitgevoerd wanneer de slider verandert (change event). De lezer kan zich afvragen
waarom het change event nog nodig is. Het is inderdaad zo dat de webapplicatie volledig kan werken met
enkel het onslide event. Wanneer de gebruiker echter in de slider zelf klikt, en niet gebruik maakt van de
handlebar, heeft men het change event nodig. Omwille van de volledigheid is daarom deze functie in de
applicatie aanwezig.
De minimum en maximum waardes van de sliders zijn ook een belangrijk punt. Deze moeten de uitersten van
de beeldenset weerspiegelen. De uitersten zijn zoals reeds vermeld vastgelegd door de variabelen height,
width en numImages. Onderstaande snippet toont hoe deze variabelen de maximum waardes (minimum is
altijd 0) aangepast worden.
//Give the sliders the correct max values
document.getElementById("graph_cor").setAttribute("max", (height-1));
document.getElementById("graph_sag").setAttribute("max", (width-1));
document.getElementById("graph_trans").setAttribute("max", (numImages-1));
Merk op dat er steeds 1 afgetrokken wordt aangezien de afbeeldingen bij 0 starten.
34
De hoogte van de sliders wordt standaard ingesteld op 200px. Het zou evenwel verkeerd zijn om voor elke
grootte van de canvaselementen een standaard sliderhoogte te gebruiken. Daarom is het de bedoeling dat de
hoogte van de slider mee varieert met de grootte van de canvassen. De code (23) op lijn 472 leest het laatste
element uit het stylesheet.css bestand uit en verandert de waarde height naar de grootte van de canvassen. Op
deze manier verandert de hoogte van de slider dynamisch mee met de webapplicatie.
IV.1.4.2 Scroll-event
De volgende functionaliteit die voor interactie met de gebruiker zorgt, betreft het scrollen met de muis. In de
webapplicatie is er voor gezorgd dat de gebruiker d.m.v. het scrollwiel door de dataset kan zoomen. Een
voorwaartse beweging zorgt voor het selecteren van de volgende afbeelding. Een achterwaartse beweging
zorgt voor het selecteren van de vorige afbeelding. Onderstaand code toont hoe dit is verwezenlijkt.
if(canv == 1){
var ch = ((ev.detail || ev.wheelDelta) > 0) ? -cor_speed : cor_speed;
beeldC += ch;
coronalMove -= (ch * stap);
conform(1);
ev.preventDefault();
setGraphicalInput(1);
}
Deze snippet toont het voorbeeld van het coronaal scrollen, maar hetzelfde principe is toegepast op
transversaal, sagittaal en het volledige volume. Wanneer een scroll event heeft plaatsgevonden op een canvas
element, zal de eventlistener dit registreren en de code uitvoeren. Merk op dat elk canvas gestart wordt met
de functie runWebGL(). Hierna wordt de variabele canvas gelijkgezet aan de waarde die meegegeven werd met
de parameters. De eventlistener luistert naar een scroll event op de ‘canvas’ variabele. Op dat ogenblik weet
de functie niet of het een coronaal, sagittaal, transversaal of fullVolume element betreft. Daarom wordt er een
integer ‘canv’ aangemaakt om na te gaan welke element het precies is. Indien event.details (24) of
ev.wheelDelta groter is dan 0, dan is er een beweging geweest, en dient er een verandering van de afbeelding
te gebeuren.
In eerste instantie zullen beeldC en coronalMove aangepast worden. Wederom moet hier opgemerkt worden
dat het coronaal plane standaard start in 1. Aanpassingen zullen dus gebeuren door x aantal stappen (stap) af
te trekken van 1 om zo een beweging mogelijk te maken. Nadat deze aanpassingen gebeurd zijn, wordt er een
functie conform(nummer_vh_canvas) opgeroepen die controleert of een gegeven canvas element niet buiten
zijn grenzen is getreden. De conform functie voor coronaal ziet er als volgt uit.
if(code==1){
if ( beeldC <= (-1.0) ){
beeldC = 0;
coronalMove = 1.0;
}
if ( beeldC > (height-1)){
beeldC = height-1;
35
coronalMove = (-1.0 * (sizeCubeC -1));
}
Dit voorbeeld kent hetzelfde principe als de andere slice richtingen, en is te vinden vanaf lijn 702. Indien er een
afbeelding geselecteerd werd die kleiner dan of gelijk is aan -1, dan wordt de start-afbeelding ingesteld (0), en
de variabele coronalmove wordt op 1 (standaard)) gezet. Indien de geselecteerde afbeelding echter het
maximale overschrijdt (voor coronaal is dit de variabele height-1), wordt deze op de maximale waarde (height-
1) ingesteld. CoronalMove zal ingesteld worden (-1 * (sizeCubeC -1)). sizeCubeC was de maximale afstand
waarover een plane kon bewegen. Dit was echter berekend op eens schaal startende in 0. Indien men start in
1.0, ligt het einde van schaal dus negatief. Bovenstaande berekening zorgt dus voor een omzetting naar een
negatieve grens.
Nadat de aanpassingen gebeurd zijn, en er nagegaan is of ze voldoen aan de grenzen, zal de functie tick() een
nieuwe scene tekenen en zullen de juiste afbeeldingen getekend worden. De sliders zelf moeten ook aangepast
worden aan de nieuwe waarde. Daarom heeft jQuery Tools een API ingebouwd om de huidige waarde te
kunnen aanpassen.
var apicor = $("#graph_cor").data("rangeinput");
In combinatie met,
if(code==1)apicor.setValue(beeldC);
Zorgt voor een aanpassing van de waarde van de slider.
IV.1.4.3 Click & Go
De laatste manier waarop de gebruiker in interactie kan treden met de applicatie is via het klikken in het canvas
element. Het beoogde gedrag van de applicatie is wanneer men bijv. in het coronaal canvas klikt, de overige
twee canvas elementen hun planes verschuiven naar die positie, en de correcte afbeelding tonen.
Onderstaande code toont wat er uitgevoerd wordt als men bijv. in het coronaal plane klikt.
if (code == 1){
var x;
var y;
if (e.pageX || e.pageY) {
x = e.pageX;
y = e.pageY;
}
else {
x = e.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
x -= Math.round($("#canvasCoronal").offset().left);
y -= Math.round($("#canvasCoronal").offset().top);
36
console.log(x,y);
if(size == 1)x=x*2,y=y*2;
if(size == 3)x=Math.round(x/2),y=Math.round(y/2);
if(size == 4)x=Math.round(x/4),y=Math.round(y/4);
if(x > (numImages-1)) x = numImages-1;
if(x < 0.00001) x=0;
if(y < 0.00001) y=0;
transversalMove = (- 1 + (x * stap));
var corspec = (max - y);
if (corspec > width ) corspec = width;
sagittalMove = (- 1 + (corspec * stap));
beeldS = (corspec);
beeldT = (x);
setGraphicalInput(2);
setGraphicalInput(3);
}
Wanneer men in het coronaal plane klikt moeten de transversaal en sagittaal planes reageren, zie ook Figuur
14. Transversaal moet bewegen voor x-aantal stappen. Sagittaal moet bewegen voor y-aantal stappen.
Wanneer het event opgemerkt wordt door de eventlistener, worden de variabelen x en y aangemaakt. Via
e.clientX/Y (25) en scrollLeft kan men de afstand tot een canvas element bekomen. Maar dan is het nog de
bedoeling om de afstand tot het canvas element zelf ervan af te trekken zodat men de netto x en y waarden
overhoudt.
Nadien is het de bedoeling dat wanneer men op plaatsen klikt die geen data bevatten, de applicatie
automatisch naar de maximale waarde gaat voor de betreffende as. Bijv. Wanneer men in coronaal klikt (zie
Figuur 14) voorbij pixel 230 op de x-as. Deze strook is zwart aangezien men geen data meer heeft voorbij pixel
230. Dit gaat op voor verschillende situaties bij transversaal en sagittaal. Ook wanneer het een rechthoekige
dataset betreft, is het de bedoeling dat men niet in het zwarte gedeelte kan klikken en daarbij de planes naar
die positie kan laten verschuiven. Indien men toch in een zone klikt die geen data bevat, gaat men altijd
verschuiven naar de maximale waarden waarvoor geen data is.
IV.1.4.4 Updates van het volledige volume
Aangezien het 3D volume afhankelijk is van de overige drie canvas-elementen, dient er ten gepaste tijden een
update te gebeuren. De update van de visualisatie gebeurt na elke interactie van de gebruiker met de
applicatie. Dit betekent dat na elke klik, muis-scroll, beweging van de slider of klik in het slider-element er een
update dient te gebeuren van het vierde canvas-element. Indien dit geïmplementeerd zou worden zonder
meer, kan de applicatie onstabiel worden.
37
Indien men bijv. de slider gebruikt worden er zeer snel nieuwe events geregistreerd. De drie slice richtingen
hebben echter geen probleem om zich tijdig aan te passen aan de nieuwe waarde die het slide-event met zich
meebrengt. Het updaten van het vierde canvas-element vormt echter wel een probleem aangezien het
aanmaken van een nieuwe texture uit de drie canvassen langer duurt. Als resultaat zal de console vaak een
reeks fouten weergeven bij events omdat het aanmaken van textures nog niet voltooid is. Het is zelfs mogelijk
dat de applicatie crasht.
Om dit probleem op te lossen is er gekozen om met een simpele setTimeOut() te werken. Elke keer als er zich
een event voordoet start er een timer van 500 milliseconden. Wanneer er echter sneller dan 500 milliseconden
een nieuw event komt, zal de timer terug op 500 gezet worden. De timer (var timer) zal simpelweg bij een
nieuw event de vorige waarde overschrijden. Op deze manier zullen er zich nooit fouten voordoen aangezien er
pas een update zal komen van het vierde canvas-element wanneer er 500 milliseconden geen events zijn
geweest.
IV.1.4.5 Kubus visualisatie
Zoals reeds op meerdere punten besproken werd, is de kubus gevisualiseerd door drie planes. De beweging
van de kubus zelf is nog een belangrijk eigenschap binnen de interactie. Door middel van de de linker muisknop
kan men de kubus rond zijn middelpunt (0,0,0) laten draaien. De functie van lijn 1024 tot 1044 zorgt voor deze
functionaliteit. De beweging van de planes is ook een niet triviale eigenschap. In normale omstandigheden kan
een plane verschoven worden door de x, y of z positie aan te passen. Maar dan gebeurt dit t.o.v. de originele
mvMatrix. Het is de bedoeling dat een plane zijn eigen assen volgt bij een verschuiving. Indien bijv. het
coronaal plane 10° gekanteld is, dan is de x,y en z-as niet meer dezelfde. Dan moet een verschuiving nu ook
deze 10° in overweging nemen.
Daarvoor is een extra matrix gedefinieerd, cubeMatrix genaamd. Men moet hiervoor de rotationMatrix (zie lijn
1024 tot 1044) vermenigvuldigen met deze matrix. Nadien moet de positie van de plane (bijv. coronalMove)
vertaald worden naar deze matrix, zodat bewegingen effectief zichtbaar zijn. Nadien moet deze matrix
vermenigvuldigd worden met de oorspronkelijke matrix (mvMatrix). Op deze manier zullen de drie planes als
het ware binnen deze kubus blijven, en langs zijn assen bewegen. Indien men de kubus dan draait zullen
bewegingen aangepast worden.
IV.1.4.6 Knoppen
Er is bij de visualisatie ook gekozen voor enkele knoppen die extra functionaliteit toevoegen aan de applicatie.
De reset changes knop verzet de canvas elementen naar hun oorspronkelijke toestand. Ook de eventuele
draaiing van de kubus wordt op nul gezet. De snelheid van het scrollen staat standaard ingesteld op 1x. Via de
knoppen coronal, sagittaal en transverse speed kan men deze snelheid aanpassen. (snelheden van 1, 2, 5, 10,
20 en 50).
38
IV.2 Beperkingen van de applicatie
Tijdens het schrijven van deze applicatie werd er meerdere malen tegen een aantal grenzen aangekeken. De
bevindingen waren vooral de beperkingen van het geheugen, de garbage collector en de afwezigheden van
input/output.
IV.2.1 Geheugenbeperkingen
Deze applicatie heeft een kleine beperking in de 3D-visualisatie, het vierde canvas element moet wachten op
het einde van een event vooraleer het een update kan doen van haar textures. Dit is hoofdzakelijk een
beperking omwille van het omvangrijke geheugengebruik. In eerdere versies van deze webapplicaties werd er
niet gewerkt met vier WebGL elementen. Aangezien de drie statische afbeeldingen niet hoeven te bewegen of
in 3D getoond worden, werd er gekozen voor drie 2D canvas elementen waarop de afbeeldingen getekend
werden. Voor het 3D volume werd dan een WebGL canvas element opgezet. Dit had het voordeel dat de drie
2D canvas elementen dezelfde functionaliteit hadden dan deze webapplicatie, terwijl er toch een volledig
dynamische WebGL scene was.
Het nadeel aan deze opstelling was het geheugengebruik. Tijdens het renderen van de grotere dataset (head-
ct), liep het geheugengebruik op in Firefox 4 naar meer dan 4GB. Google Chrome crashte tijdens het
berekenen, (zie Figuur 15). De reden voor dit enorme verbruik ligt in het feit dat na het berekenen van de drie
slice richtingen, deze data beschikbaar moest blijven voor de 2D canvas elementen. Daar bovenop kwam nog
het geheugen gebruik van het 3D WebGL gedeelte. Dit zorgde voor meer dan 4 GB aan geheugengebruik.
IV.2.2 Alternatieve oplossingen
Een van de oplossingen die niet geïmplementeerd werd maar wel mogelijk is, is een gedeelte serverside
scripting. Op deze manier kan men bijvoorbeeld volledige mappen, zip-files etc uploaden naar een web-server.
Nadien kunnen deze dan bekeken worden met de webapplicatie. Ook zou het bijvoorbeeld mogelijk zijn om het
aantal afbeeldingen in een map automatisch te tellen, en de afmetingen correct uit te lezen. Zo zou het
settings.csv bestand overbodig kunnen gemaakt worden. De implementatie van dit server-side gedeelte ligt
echter buiten het doel van deze masterproef aangezien de kernproblematiek aangepakt en besproken werd.
39
IV.3 Algemene Conclusie en beantwoording onderzoeksvraag
In hoofdstuk 1 werd er een algemene inleiding gegeven over de werking van 2D canvas elementen en WebGL
elementen. Dit was de basis voor het verdere verloop van de masterproef. In Hoofdstuk II kwam de werking
van de rendering fase aan bod. Deze fase had tot doel de drie slice richtingen te berekenen, en hiervan textures
te bouwen. De reden van het reeds aanmaken van textures in deze fase is het gescheiden houden van
berekeningen en visualisatie. Dit maakt het makkelijker voor de garbage collector om sneller geheugen vrij te
maken. In het derde hoofdstuk werd het WebGL gedeelte besproken. Hierin was het belangrijk om uit te leggen
hoe een visualisatie precies mogelijk is in WebGL.
Het vierde hoofdstuk is veruit het belangrijkste hoofdstuk aangezien hier in detail besproken werd hoe de
applicatie precies werkt. Het input-scherm geeft de gebruiker de mogelijkheid een beschikbare dataset te
selecteren, of een eigen dataset te lezen. Tijdens het verwerken krijgt de gebruiker een ‘loading’-venster te
zien. Na deze twee fases komt de uiteindelijke visualisatie van de beeldenset. Er zijn drie normale canvas
elementen die elk een slice richting visualiseren. Het vierde element toont een 3D scene van de overige
elementen. De interface biedt verder de mogelijkheid om door de dataset te ‘zoomen’ via de sliders en de
muis. Wanneer de gebruiker klikt in het canvaselement, zullen de overige slices zich verplaatsen naar die
positie.
Deze masterproef deed een onderzoek naar de haalbaarheid en implementatie van een WebGL applicatie voor
3D beeldweergave. De onderzoeksvraag was een vraag die polst naar de haalbaarheid en implementatie. De
haalbaarheid is met deze webapplicatie zeker en vast bewezen. De applicatie kan probleemloos
driedimensionale beeldensets tonen, ongeacht of deze nu rechthoekig zijn of een macht van twee. Bovendien
is er volledige interactie met de gebruiker d.m.v. de muis. Wat de implementatie betreft kan deze nog
geperfectioneerd worden indien er server-side scripting geïmplementeerd wordt. Op deze manier kan men de
laatste beperking zoals het manueel instellen van een settings.csv bestand oplossen. Ondanks deze kleine
verbetermogelijkheden kan tegenover de onderzoeksvraag een succesvolle webapplicatie gezet worden die
aan de gestelde eisen voldoet.
I
Bijlage A
0001 var numImages = null;
0002 var width;
0003 var height;
0004 var max;
0005 var path = null;
0006 var size = null;
0007 var c_size;
0008
0009 var corTexture = [];
0010 var sagTexture = [];
0011 var transTexture = [];
0012 var corTex;
0013 var sagTex;
0014 var transTex;
0015
0016 var firstRun = true;
0017 var resetRotation = false;
0018 var gl1;
0019 var gl2;
0020 var gl3;
0021
0022 var coronalMove = 1.0;
0023 var beeldC = 0;
0024 var transversalMove = -1.0;
0025 var beeldT = 0;
0026 var sagittalMove = 1.0;
0027 var beeldS = 0;
0028 var zoom = -3.5;
0029 var cor_speed = 1;
0030 var cur_speedC = 0;
0031 var sag_speed = 1;
0032 var cur_speedS = 0;
0033 var trans_speed = 1;
0034 var cur_speedT = 0;
0035 var speedArr = ["1", "2", "5", "10", "20", "50"];
0036
0037 var stap = null;
0038 var sizeCubeC = null;
0039 var sizeCubeS = null;
0040 var sizeCubeT = null;
0041
0042 var apicor;
0043 var apisag;
0044 var apitrans;
0045 var apifull;
0046
0047 function start(code){
0048 //controls the main application
0049 renderImages(code);
0050 initUI();
0051 }
0052
0053 function runCST(){
0054 //Remove the progressbar first
0055 document.getElementById("progress").style.display = "none";
0056 document.getElementById("outerwrapper").style.display = "block";
0057
0058 //Runs the three webgl canvas elements given the textures rendered from renderImages()
0059 runWebGL( document.getElementById("canvasCoronal") , 1 );
0060 runWebGL( document.getElementById("canvasSagittal") , 2 );
0061 runWebGL( document.getElementById("canvasTransversal") , 3);
0062 renderCSTtoTex();
0063 }
0064
0065 function renderCSTtoTex(){
0066 var canvas1 = document.getElementById("canvasCoronal");
0067 var myImage = canvas1.toDataURL("image/png");
0068
0069 var canvas2 = document.getElementById("canvasSagittal");
0070 var myImage2 = canvas2.toDataURL("image/png");
0071
0072 var canvas3 = document.getElementById("canvasTransversal");
0073 var myImage3 = canvas3.toDataURL("image/png");
0074
0075 var canvas = document.getElementById("fullVolume");
0076 try {
0077 gl = canvas.getContext("experimental-webgl");
II
0078 gl.viewportWidth = canvas.width;
0079 gl.viewportHeight = canvas.height;
0080 } catch (e) {
0081 }
0082 if (!gl) {
0083 alert("Error: Brower does not support WebGL");
0084 }
0085
0086 corTex = gl.createTexture();
0087 corTex.image = new Image();
0088 corTex.image.onload = function () {
0089 handleLoadedTexture(corTex);
0090 }
0091 corTex.image.src = myImage;
0092
0093 sagTex = gl.createTexture();
0094 sagTex.image = new Image();
0095 sagTex.image.onload = function () {
0096 handleLoadedTexture(sagTex);
0097 }
0098 sagTex.image.src = myImage2;
0099
0100 transTex = gl.createTexture();
0101 transTex.image = new Image();
0102 transTex.image.onload = function () {
0103 handleLoadedTexture(transTex);
0104 }
0105 transTex.image.src = myImage3;
0106
0107 function handleLoadedTexture(texture) {
0108 gl.bindTexture(gl.TEXTURE_2D, texture);
0109 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
0110 gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, texture.image);
0111 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
0112 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
0113 gl.bindTexture(gl.TEXTURE_2D, null);
0114 }
0115 runWebGL( document.getElementById("fullVolume"), 4);
0116 }
0117
0118 function updateCtoTex(){
0119 var canvas1 = document.getElementById("canvasCoronal");
0120 var myImage = canvas1.toDataURL("image/png");
0121
0122 var canvas = document.getElementById("fullVolume");
0123 try {
0124 gl = canvas.getContext("experimental-webgl");
0125 gl.viewportWidth = canvas.width;
0126 gl.viewportHeight = canvas.height;
0127 } catch (e) {
0128 }
0129 if (!gl) {
0130 alert("Error: Brower does not support WebGL");
0131 }
0132
0133 corTex = gl.createTexture();
0134 corTex.image = new Image();
0135 corTex.image.onload = function () {
0136 handleLoadedTexture(corTex);
0137 }
0138 corTex.image.src = myImage;
0139
0140 function handleLoadedTexture(texture) {
0141 gl.bindTexture(gl.TEXTURE_2D, texture);
0142 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
0143 gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, texture.image);
0144 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
0145 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
0146 gl.bindTexture(gl.TEXTURE_2D, null);
0147 }
0148 }
0149
0150 function updateStoTex(){
0151
0152 var canvas2 = document.getElementById("canvasSagittal");
0153 var myImage2 = canvas2.toDataURL("image/png");
0154
0155 var canvas = document.getElementById("fullVolume");
0156 try {
0157 gl = canvas.getContext("experimental-webgl");
0158 gl.viewportWidth = canvas.width;
0159 gl.viewportHeight = canvas.height;
III
0160 } catch (e) {
0161 }
0162 if (!gl) {
0163 alert("Error: Brower does not support WebGL");
0164 }
0165
0166 sagTex = gl.createTexture();
0167 sagTex.image = new Image();
0168 sagTex.image.onload = function () {
0169 handleLoadedTexture(sagTex);
0170 }
0171 sagTex.image.src = myImage2;
0172
0173 function handleLoadedTexture(texture) {
0174 gl.bindTexture(gl.TEXTURE_2D, texture);
0175 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
0176 gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, texture.image);
0177 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
0178 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
0179 gl.bindTexture(gl.TEXTURE_2D, null);
0180 }
0181 }
0182
0183 function updateTtoTex(){
0184
0185 var canvas3 = document.getElementById("canvasTransversal");
0186 var myImage3 = canvas3.toDataURL("image/png");
0187
0188 var canvas = document.getElementById("fullVolume");
0189 try {
0190 gl = canvas.getContext("experimental-webgl");
0191 gl.viewportWidth = canvas.width;
0192 gl.viewportHeight = canvas.height;
0193 } catch (e) {
0194 }
0195 if (!gl) {
0196 alert("Error: Brower does not support WebGL");
0197 }
0198
0199 transTex = gl.createTexture();
0200 transTex.image = new Image();
0201 transTex.image.onload = function () {
0202 handleLoadedTexture(transTex);
0203 }
0204 transTex.image.src = myImage3;
0205
0206 function handleLoadedTexture(texture) {
0207 gl.bindTexture(gl.TEXTURE_2D, texture);
0208 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
0209 gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, texture.image);
0210 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
0211 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
0212 gl.bindTexture(gl.TEXTURE_2D, null);
0213 }
0214 }
0215
0216 function renderImages(code){
0217 //Renders the 3 views out of a 2D slice dataset
0218 var loadedImages = 0;
0219 var imageArray = [];
0220 var div = document.getElementById("text");
0221 div.innerHTML = "<p>Fetching Images, please wait</p>";
0222 if (code==1){
0223 for( var i = 0 , j = numImages; i < j ; i++ ){
0224 imageArray[i] = new Image();
0225
0226 str = "data/"+path;
0227
0228 if (i<10)
0229 str += "000";
0230 else if (i<100)
0231 str += "00";
0232 else if (i<1000)
0233 str += "0";
0234 else { }
0235
0236 imageArray[i].src = str + i+".png";
0237
0238 imageArray[i].onload = function(){
0239 loadedImages++;
0240 if (loadedImages == numImages){
0241 textureRender();
IV
0242 }
0243 }
0244 }
0245 }
0246 if (code==2){
0247 for (var i = 0 ; i < files.length; i++) {
0248 var file = files[i];
0249
0250 imageArray[i] = new Image();
0251 imageArray[i].onload = function(){
0252 loadedImages++;
0253 if (loadedImages == files.length){
0254 textureRender();
0255 }
0256 }
0257 imageArray[i].src = file;
0258
0259 var reader = new FileReader();
0260 reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; };
})(imageArray[i]);
0261 reader.readAsDataURL(file);
0262 }
0263 }
0264
0265 function textureRender(){
0266 var corImages = [];
0267 var sagImages = [];
0268 var transversalImages = [];
0269
0270 //Build temporarly 2D canvas element
0271 var div = document.getElementById("container");
0272 var can = document.createElement('canvas');
0273 can.setAttribute("width", max);
0274 can.setAttribute("height", max);
0275 can.setAttribute("id", "canvasDraw");
0276 div.appendChild(can);
0277
0278 render();
0279
0280 div.removeChild(can);
0281 div = null;
0282 can = null;
0283
0284 function render(){
0285 var canvasCoronal = document.getElementById("canvasDraw");
0286 var ctx = canvasCoronal.getContext('2d');
0287 var canvas = document.getElementById("canvasCoronal");
0288 var canvas2 = document.getElementById("canvasSagittal");
0289 var canvas3 = document.getElementById("canvasTransversal");
0290
0291
0292 try {
0293 gl1 = canvas.getContext("experimental-webgl");
0294 gl1.viewportWidth = canvas.width;
0295 gl1.viewportHeight = canvas.height;
0296 } catch (e) {
0297 }
0298 if (!gl1) {
0299 alert("Error: Browser does not support Webgl");
0300 }
0301
0302 try {
0303 gl2 = canvas2.getContext("experimental-webgl");
0304 gl2.viewportWidth = canvas2.width;
0305 gl2.viewportHeight = canvas2.height;
0306 } catch (e) {
0307 }
0308 if (!gl2) {
0309 alert("Error: Browser does not support Webgl");
0310 }
0311
0312 try {
0313 gl3 = canvas3.getContext("experimental-webgl");
0314 gl3.viewportWidth = canvas3.width;
0315 gl3.viewportHeight = canvas3.height;
0316 } catch (e) {
0317 }
0318 if (!gl3) {
0319 alert("Error: Browser does not support Webgl");
0320 }
0321
0322 for (var j = 0; j < max; j++){
V
0323 sagImages[j] = ctx.createImageData(max, max);
0324 }
0325
0326 for (var j = 0; j < max; j++){
0327 corImages[j] = ctx.createImageData(max, max);
0328 }
0329 var i=0;
0330
0331 document.getElementById("progressbar").style.display = "block";
0332 var div = document.getElementById("text");
0333 div.innerHTML = "<p>Rendering Images, please wait</p>";
0334 var el = document.getElementById("progressbar").firstChild;
0335
0336 function renderImg (progressBar) {
0337 (function () {
0338 var callee = arguments.callee;
0339 ctx.drawImage(imageArray[i],0,0);
0340 var orig_imdata = ctx.getImageData(0, 0, max, max);
0341 transversalImages[i] = orig_imdata;
0342 var orig_imdatadata = orig_imdata.data;
0343
0344 for (var j = 0; j < max; j++){
0345
0346 var step = max * 4;
0347 var idx = j*4;
0348 var idx2 = i*4;
0349
0350 var cor_temp = sagImages[j].data;
0351
0352 for (var k = 0; k < max; k++){
0353 for (var l = 0; l < 4; l++){
0354 cor_temp[idx2+(k*step)+l] = orig_imdatadata[idx+(k*step)+l]
0355 }
0356 }
0357 }
0358
0359 for (var j = 0; j < max; j++){
0360
0361 // start index of the pixels in the original array
0362 var idx = (j * max) * 4;
0363
0364 // start index of the pixels in the resulting array
0365 var idx2 = (i * max) * 4;
0366 var stop = idx + (max * 4);
0367
0368 var cor_temp = corImages[j].data;
0369 for (var k=idx, l = 0; k < stop; k++, l++){
0370 cor_temp[idx2+l] = orig_imdatadata[k];
0371 }
0372 }
0373
0374 i++
0375 progressBar(i, numImages);
0376 if (i < numImages) {
0377 (function(){
0378 setTimeout(function(){
0379 callee();
0380 }, 0);
0381 })();
0382 }
0383
0384 })();
0385 }
0386
0387 renderImg(function (value, total) {
0388 el.style.width = (100 * value / total) + "%";
0389 if (value >= total) {
0390 renderTex();
0391 }
0392 });
0393
0394 function renderTex(){
0395 for (var t=0; t < height; t++) {
0396 var texture = gl1.createTexture();
0397 var container = corImages[t];
0398 handleLoadedTexture(texture, container);
0399 corTexture.push(texture);
0400 }
0401
0402 function handleLoadedTexture(texture, container) {
0403 gl1.bindTexture(gl1.TEXTURE_2D, texture);
0404 gl1.pixelStorei(gl1.UNPACK_FLIP_Y_WEBGL, true);
VI
0405 gl1.texImage2D(gl1.TEXTURE_2D, 0, gl1.LUMINANCE, gl1.LUMINANCE,
gl1.UNSIGNED_BYTE, container);
0406 gl1.texParameteri(gl1.TEXTURE_2D, gl1.TEXTURE_MAG_FILTER, gl1.LINEAR);
0407 gl1.texParameteri(gl1.TEXTURE_2D, gl1.TEXTURE_MIN_FILTER, gl1.LINEAR);
0408 gl1.bindTexture(gl1.TEXTURE_2D, null);
0409 }
0410
0411 for (var t=0; t < width; t++) {
0412 var texture = gl2.createTexture();
0413 var container = sagImages[t];
0414 handleLoadedTexture2(texture, container);
0415 sagTexture.push(texture);
0416 }
0417
0418 function handleLoadedTexture2(texture, container) {
0419 gl2.bindTexture(gl2.TEXTURE_2D, texture);
0420 gl2.pixelStorei(gl2.UNPACK_FLIP_Y_WEBGL, true);
0421 gl2.texImage2D(gl2.TEXTURE_2D, 0, gl2.LUMINANCE, gl2.LUMINANCE,
gl2.UNSIGNED_BYTE, container);
0422 gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_MAG_FILTER, gl2.LINEAR);
0423 gl2.texParameteri(gl2.TEXTURE_2D, gl2.TEXTURE_MIN_FILTER, gl2.LINEAR);
0424 gl2.bindTexture(gl2.TEXTURE_2D, null);
0425 }
0426
0427 for (t=0; t < numImages; t++) {
0428 var texture = gl3.createTexture();
0429 var container = transversalImages[t];
0430 handleLoadedTexture3(texture, container);
0431 transTexture.push(texture);
0432 }
0433
0434 function handleLoadedTexture3(texture, container) {
0435 gl3.bindTexture(gl3.TEXTURE_2D, texture);
0436 gl3.pixelStorei(gl3.UNPACK_FLIP_Y_WEBGL, true);
0437 gl3.texImage2D(gl3.TEXTURE_2D, 0, gl3.LUMINANCE, gl3.LUMINANCE,
gl3.UNSIGNED_BYTE, container);
0438 gl3.texParameteri(gl3.TEXTURE_2D, gl3.TEXTURE_MAG_FILTER, gl3.LINEAR);
0439 gl3.texParameteri(gl3.TEXTURE_2D, gl3.TEXTURE_MIN_FILTER, gl3.LINEAR);
0440 gl3.bindTexture(gl3.TEXTURE_2D, null);
0441 }
0442 runCST();
0443 }
0444 }
0445 }
0446 }
0447
0448
0449 function initUI(){
0450 //Dynamically give the webgl canvas the correct height and width
0451 if(size == 1 )c_size = 128;
0452 if(size == 2 )c_size = max;
0453 if(size == 3 )c_size = 512;
0454 if(size == 4 )c_size = 1024;
0455 document.getElementById("canvasCoronal").setAttribute("width", c_size);
0456 document.getElementById("canvasCoronal").setAttribute("height", c_size);
0457
0458 document.getElementById("canvasSagittal").setAttribute("width", c_size);
0459 document.getElementById("canvasSagittal").setAttribute("height", c_size);
0460
0461 document.getElementById("canvasTransversal").setAttribute("width", c_size);
0462 document.getElementById("canvasTransversal").setAttribute("height", c_size);
0463
0464 document.getElementById("fullVolume").setAttribute("width", c_size);
0465 document.getElementById("fullVolume").setAttribute("height", c_size);
0466
0467 //Give the sliders the correct max values
0468 document.getElementById("graph_cor").setAttribute("max", (height-1));
0469 document.getElementById("graph_sag").setAttribute("max", (width-1));
0470 document.getElementById("graph_trans").setAttribute("max", (numImages-1));
0471
0472 //Adjust the the height of the slider in the css
0473 var css = new Array();
0474 if (document.styleSheets[0].cssRules){
0475 css = document.styleSheets[0].cssRules
0476 }
0477 else if (document.styleSheets[0].rules){
0478 css = document.styleSheets[0].rules
0479 }
0480 else return;
0481 css[css.length-1].style.height = (c_size-10)+'px';
0482
0483 //eventlisteners on slider change
VII
0484 var timer = null;
0485 $("#graph_cor").rangeinput({
0486 onSlide: function (event, step) {
0487 beeldC = step;
0488 coronalMove = (1-(step * stap));
0489 if ( timer != null ){
0490 clearTimeout(timer);
0491 }
0492 timer = setTimeout("updateCtoTex()", 500);
0493 },
0494
0495 change: function (event, value) {
0496 beeldC = value;
0497 coronalMove = (1-(value * stap));
0498 if ( timer != null ){
0499 clearTimeout(timer);
0500 }
0501 timer = setTimeout("updateCtoTex()", 500);
0502 }
0503 });
0504
0505 $("#graph_sag").rangeinput({
0506 onSlide: function (event, step) {
0507 beeldS = step;
0508 sagittalMove = (1-(step * stap));
0509 if ( timer != null ){
0510 clearTimeout(timer);
0511 }
0512 timer = setTimeout("updateStoTex()", 500);
0513 },
0514
0515 change: function (event, value) {
0516 beeldS = value;
0517 sagittalMove = (1-(value * stap));
0518 if ( timer != null ){
0519 clearTimeout(timer);
0520 }
0521 timer = setTimeout("updateStoTex()", 500);
0522 }
0523 });
0524
0525 $("#graph_trans").rangeinput({
0526 onSlide: function (event, step) {
0527 beeldT = step;
0528 transversalMove = (-1+(step * stap));
0529 if ( timer != null ){
0530 clearTimeout(timer);
0531 }
0532 timer = setTimeout("updateTtoTex()", 500);
0533 },
0534
0535 change: function(event, value) {
0536 beeldT = value;
0537 transversalMove = (-1+(value * stap));
0538 if ( timer != null ){
0539 clearTimeout(timer);
0540 }
0541 timer = setTimeout("updateTtoTex()", 500);
0542 }
0543
0544 });
0545
0546 $("#graph_volume").rangeinput({
0547 onSlide: function (event, step) {
0548 zoom = step;
0549 },
0550
0551 change: function(event, value) {
0552 zoom = value;
0553 }
0554 });
0555
0556 //Initialize the sliders
0557 apicor = $("#graph_cor").data("rangeinput");
0558 apisag = $("#graph_sag").data("rangeinput");
0559 apitrans = $("#graph_trans").data("rangeinput");
0560 apifull = $("#graph_volume").data("rangeinput");
0561 }
0562
0563 function reset(){
0564 coronalMove = 1.0;
0565 beeldC = 0;
VIII
0566 transversalMove = -1.0;
0567 beeldT = 0;
0568 sagittalMove = 1.0;
0569 beeldS = 0;
0570 zoom = -3.5;
0571
0572 setGraphicalInput(1);
0573 setGraphicalInput(2);
0574 setGraphicalInput(3);
0575 setGraphicalInput(4);
0576
0577 var t=setTimeout("updateCtoTex()",25);
0578 var t=setTimeout("updateStoTex()",25);
0579 var t=setTimeout("updateTtoTex()",25);
0580
0581 resetRotation = true;
0582 var t = setTimeout("resetreturn()",50);
0583 }
0584
0585 function resetreturn(){
0586 resetRotation = false;
0587 }
0588
0589 function scrollSpeed(code){
0590 if(code==1){
0591 cur_speedC++;
0592 if(cur_speedC > speedArr.length-1)cur_speedC=0;
0593 cor_speed = parseInt(speedArr[cur_speedC]);
0594 var but = document.getElementById("s1").innerHTML = "<span>Coronal:
"+speedArr[cur_speedC]+"x</span>";
0595 }
0596
0597 if(code==2){
0598 cur_speedS++;
0599 if(cur_speedS > speedArr.length-1)cur_speedS=0;
0600 sag_speed = parseInt(speedArr[cur_speedS]);
0601 var but = document.getElementById("s2").innerHTML = "<span>Sagittal:
"+speedArr[cur_speedS]+"x</span>";
0602 }
0603
0604 if(code==3){
0605 cur_speedT++;
0606 if(cur_speedT > speedArr.length-1)cur_speedT=0;
0607 trans_speed = parseInt(speedArr[cur_speedT]);
0608 var but = document.getElementById("s3").innerHTML = "<span>Transverse:
"+speedArr[cur_speedT]+"x</span>";
0609 }
0610 }
0611
0612 function setGraphicalInput(code){
0613 if(code==1)apicor.setValue(beeldC);
0614 if(code==2)apisag.setValue(beeldS);
0615 if(code==3)apitrans.setValue(beeldT);
0616 if(code==4)apifull.setValue(zoom);
0617 }
0618
0619 function runWebGL(webglCanvas, canvasNumb){
0620 //Runs a webgl instance on a given canvas element, canvasNumb sets the correct webgl mode
0621
0622 var canv = canvasNumb;
0623 var gl;
0624 var canvas = webglCanvas;
0625 var shaderProgram;
0626
0627 var mvMatrix = mat4.create();
0628 var pMatrix = mat4.create();
0629 var mvMatrixStack = [];
0630 var cubeMatrixStack = [];
0631 var rotationMatrixStack = [];
0632
0633 var cubeMatrix = mat4.create();
0634 mat4.identity(cubeMatrix);
0635 var rotationMatrix = mat4.create();
0636 mat4.identity(rotationMatrix);
0637
0638 var cubeVertexPositionBuffer;
0639 var cubeVertexTextureCoordBuffer;
0640 var cubeVertexIndexBuffer;
0641
0642 var cube2VertexPositionBuffer;
0643 var cube2VertexTextureCoordBuffer;
0644 var cube2VertexIndexBuffer;
IX
0645
0646 var cube3VertexPositionBuffer;
0647 var cube3VertexTextureCoordBuffer;
0648 var cube3VertexIndexBuffer;
0649
0650 var SquareVertexPositionBuffer;
0651 var SquareVertexTextureCoordBuffer;
0652 var SquareVertexIndexBuffer;
0653
0654 var Square2VertexPositionBuffer;
0655 var Square2VertexTextureCoordBuffer;
0656 var Square2VertexIndexBuffer;
0657
0658 var Square3VertexPositionBuffer;
0659 var Square3VertexTextureCoordBuffer;
0660 var Square3VertexIndexBuffer;
0661
0662 var currentlyPressedKeys = {};
0663 var left_mouse_is_down = false;
0664 var lastMouseX = null;
0665 var lastMouseY = null;
0666
0667 initGL(canvas);
0668 initShaders();
0669 initBuffers();
0670
0671 gl.clearColor(1.0, 1.0, 1.0, 1.0);
0672 gl.enable(gl.DEPTH_TEST);
0673
0674 canvas.onmousedown = handleMouseDown;
0675 document.onmouseup = handleMouseUp;
0676 document.onmousemove = handleMouseMove;
0677
0678 mouseScroll();
0679 mouseClick(canv);
0680
0681 tick();
0682
0683 function tick() {
0684 requestAnimFrame(tick);
0685 drawScene();
0686 }
0687
0688 function mouseScroll(){
0689 var timer = null;
0690 var wheelHandler = function(ev) {
0691 if(canv == 4){
0692 var ds = ((ev.detail || ev.wheelDelta) > 0) ? (-0.2) : (0.2);
0693 zoom += ds;
0694 conform(4);
0695 setGraphicalInput(4);
0696 ev.preventDefault();
0697 }
0698 if(canv == 1){
0699 var ch = ((ev.detail || ev.wheelDelta) > 0) ? -cor_speed : cor_speed;
0700 beeldC += ch;
0701 coronalMove -= (ch * stap);
0702 conform(1);
0703 ev.preventDefault();
0704 setGraphicalInput(1);
0705 if ( timer != null ){
0706 clearTimeout(timer);
0707 }
0708 timer = setTimeout("updateCtoTex()", 500);
0709 }
0710 if(canv == 2){
0711 var ch = ((ev.detail || ev.wheelDelta) > 0) ? -sag_speed : sag_speed;
0712 beeldS += ch;
0713 sagittalMove -= (ch * stap);
0714 conform(2);
0715 ev.preventDefault();
0716 setGraphicalInput(2);
0717 if ( timer != null ){
0718 clearTimeout(timer);
0719 }
0720 timer = setTimeout("updateStoTex()", 500);
0721 }
0722 if(canv == 3){
0723 var ch = ((ev.detail || ev.wheelDelta) > 0) ? -trans_speed : trans_speed;
0724 beeldT += ch;
0725 transversalMove += (ch * stap);
0726 conform(3);
X
0727 ev.preventDefault();
0728 setGraphicalInput(3);
0729 if ( timer != null ){
0730 clearTimeout(timer);
0731 }
0732 timer = setTimeout("updateTtoTex()", 500);
0733 }
0734 }
0735 canvas.addEventListener('DOMMouseScroll', wheelHandler, false);
0736 canvas.addEventListener('mousewheel', wheelHandler, false);
0737 }
0738
0739 function conform(code){
0740 if(code==1){
0741 if ( beeldC <= (-1.0) ){
0742 beeldC = 0;
0743 coronalMove = 1.0;
0744 }
0745 if ( beeldC > (height-1)){
0746 beeldC = height-1;
0747 coronalMove = (-1.0 * (sizeCubeC -1));
0748 }
0749 } else if (code==2){
0750 if ( beeldS <= (-1) ){
0751 beeldS = 0;
0752 sagittalMove = 1.0;
0753 }
0754 if ( beeldS > (width-1)){
0755 beeldS = width-1;
0756 sagittalMove = (-1.0 * (sizeCubeS -1));
0757 }
0758 } else if (code==3){
0759 if ( beeldT <= (-1) ){
0760 beeldT = 0;
0761 transversalMove = -1.0;
0762 }
0763 if ( beeldT > (numImages-1)){
0764 beeldT = numImages-1;
0765 transversalMove = (sizeCubeT-1);
0766 }
0767 } else {
0768 if (zoom >= -1){
0769 zoom = -1.0;
0770 }
0771 if (zoom <= -10.0){
0772 zoom = -10.0;
0773 }
0774 }
0775 }
0776
0777 function mouseClick(code){
0778 var timer = null;
0779 function ev_mouseClick (e) {
0780 if (code == 1){
0781 var x;
0782 var y;
0783 if (e.pageX || e.pageY) {
0784 x = e.pageX;
0785 y = e.pageY;
0786 }
0787 else {
0788 x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
0789 y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
0790 }
0791
0792 x -= Math.round($("#canvasCoronal").offset().left);
0793 y -= Math.round($("#canvasCoronal").offset().top);
0794
0795 if(size == 1)x=x*2,y=y*2;
0796 if(size == 3)x=Math.round(x/2),y=Math.round(y/2);
0797 if(size == 4)x=Math.round(x/4),y=Math.round(y/4);
0798
0799 if(x > (numImages-1)) x = numImages-1;
0800
0801 if(x < 0.1) x=0;
0802 if(y < 0.1) y=0;
0803
0804 transversalMove = (- 1 + (x * stap));
0805
0806 var corspec = (max - y);
0807 if (corspec > width ) corspec = width;
0808
XI
0809 sagittalMove = (- 1 + (corspec * stap));
0810
0811 beeldS = (corspec);
0812 beeldT = (x);
0813 if ( timer != null ){
0814 clearTimeout(timer);
0815 }
0816 timer = setTimeout("updateStoTex()", 500);
0817 timer = setTimeout("updateTtoTex()", 500);
0818
0819 setGraphicalInput(2);
0820 setGraphicalInput(3);
0821 }
0822
0823 if(code==2){
0824 var x;
0825 var y;
0826 if (e.pageX || e.pageY) {
0827 x = e.pageX;
0828 y = e.pageY;
0829 }
0830 else {
0831 x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
0832 y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
0833 }
0834
0835 x -= Math.round($("#canvasSagittal").offset().left);
0836 y -= Math.round($("#canvasSagittal").offset().top);
0837
0838 if(size == 1)x=x*2,y=y*2;
0839 if(size == 3)x=Math.round(x/2),y=Math.round(y/2);
0840 if(size == 4)x=Math.round(x/4),y=Math.round(y/4);
0841
0842 if(x > (numImages-1)) x = numImages-1;
0843 if(y > (height-1)) y = height-1;
0844 if(x < 0.1) x=0;
0845 if(y < 0.1) y=0;
0846
0847 coronalMove = (- 1 + (((height-1)-y) * stap));
0848 transversalMove= (- 1 + (x * stap));
0849
0850 beeldC = (y);
0851 beeldT = (x);
0852 if ( timer != null ){
0853 clearTimeout(timer);
0854 }
0855 timer = setTimeout("updateCtoTex()", 500);
0856 timer = setTimeout("updateTtoTex()", 500);
0857
0858 setGraphicalInput(1);
0859 setGraphicalInput(3);
0860 }
0861
0862 if(code ==3 ){
0863 var x;
0864 var y;
0865 if (e.pageX || e.pageY) {
0866 x = e.pageX;
0867 y = e.pageY;
0868 }
0869 else {
0870 x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
0871 y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
0872 }
0873 x -= Math.round($("#canvasTransversal").offset().left);
0874 y -= Math.round($("#canvasTransversal").offset().top);
0875
0876 if(size == 1)x=x*2,y=y*2;
0877 if(size == 3)x=Math.round(x/2),y=Math.round(y/2);
0878 if(size == 4)x=Math.round(x/4),y=Math.round(y/4);
0879
0880 if (x > (width-1)) x = width-1;
0881 if (y > (height-1)) y = height-1;
0882 if (x < 0.1) x = 0;
0883 if (y < 0.1) y = 0;
0884
0885 coronalMove = (1 - (y * stap));
0886 sagittalMove = (1 - (x * stap));
0887
0888 beeldS = (x);
0889 beeldC = (y);
0890
XII
0891 if ( timer != null ){
0892 clearTimeout(timer);
0893 }
0894 timer = setTimeout("updateStoTex()", 500);
0895 timer = setTimeout("updateCtoTex()", 500);
0896
0897 setGraphicalInput(2);
0898 setGraphicalInput(1);
0899 }
0900 }
0901 canvas.addEventListener('mousedown', ev_mouseClick, false);
0902 }
0903
0904 function initGL(canvas) {
0905 try {
0906 gl = canvas.getContext("experimental-webgl");
0907 gl.viewportWidth = canvas.width;
0908 gl.viewportHeight = canvas.height;
0909 } catch (e) {
0910 }
0911 if (!gl) {
0912 alert("Could not initialise WebGL, sorry :-(");
0913 }
0914 }
0915
0916 function getShader(gl, id) {
0917 var shaderScript = document.getElementById(id);
0918 if (!shaderScript) {
0919 return null;
0920 }
0921
0922 var str = "";
0923 var k = shaderScript.firstChild;
0924 while (k) {
0925 if (k.nodeType == 3) {
0926 str += k.textContent;
0927 }
0928 k = k.nextSibling;
0929 }
0930
0931 var shader;
0932 if (shaderScript.type == "x-shader/x-fragment") {
0933 shader = gl.createShader(gl.FRAGMENT_SHADER);
0934 } else if (shaderScript.type == "x-shader/x-vertex") {
0935 shader = gl.createShader(gl.VERTEX_SHADER);
0936 } else {
0937 return null;
0938 }
0939
0940 gl.shaderSource(shader, str);
0941 gl.compileShader(shader);
0942
0943 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
0944 alert(gl.getShaderInfoLog(shader));
0945 return null;
0946 }
0947
0948 return shader;
0949 }
0950
0951
0952 function initShaders() {
0953 var fragmentShader = getShader(gl, "shader-fs");
0954 var vertexShader = getShader(gl, "shader-vs");
0955
0956 shaderProgram = gl.createProgram();
0957 gl.attachShader(shaderProgram, vertexShader);
0958 gl.attachShader(shaderProgram, fragmentShader);
0959 gl.linkProgram(shaderProgram);
0960
0961 if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
0962 alert("Could not initialise shaders");
0963 }
0964
0965 gl.useProgram(shaderProgram);
0966
0967 shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
0968 gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
0969
0970 shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
0971 gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
0972
XIII
0973 shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
0974 shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
0975 shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
0976 }
0977
0978 function mvPushMatrix() {
0979 var copy = mat4.create();
0980 mat4.set(mvMatrix, copy);
0981 mvMatrixStack.push(copy);
0982 }
0983 function cubePushMatrix() {
0984 var copy = mat4.create();
0985 mat4.set(cubeMatrix, copy);
0986 cubeMatrixStack.push(copy);
0987 }
0988
0989 function mvPopMatrix() {
0990 if (mvMatrixStack.length == 0) {
0991 throw "Invalid popMatrix!";
0992 }
0993 mvMatrix = mvMatrixStack.pop();
0994 }
0995 function cubePopMatrix() {
0996 if (cubeMatrixStack.length == 0) {
0997 throw "Invalid popMatrix!";
0998 }
0999 cubeMatrix = cubeMatrixStack.pop();
1000 }
1001
1002 function setMatrixUniforms() {
1003 gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
1004 gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
1005 }
1006
1007 function degToRad(degrees) {
1008 return degrees * Math.PI / 180;
1009 }
1010
1011 function handleMouseDown(event) {
1012 left_mouse_is_down = (event.which == 1);
1013 lastMouseX = event.clientX;
1014 lastMouseY = event.clientY;
1015 }
1016
1017 function handleMouseUp(event) {
1018 if (event.which == 1)
1019 {
1020 left_mouse_is_down = false;
1021 }
1022 }
1023
1024 function handleMouseMove(event) {
1025 if (!left_mouse_is_down)
1026 {
1027 return;
1028 }
1029 var newX = event.clientX;
1030 var newY = event.clientY;
1031
1032 var deltaX = newX - lastMouseX
1033 var newRotationMatrix = mat4.create();
1034 mat4.identity(newRotationMatrix);
1035 mat4.rotate(newRotationMatrix, degToRad(deltaX / 10), [0, 1, 0]);
1036
1037 var deltaY = newY - lastMouseY;
1038 mat4.rotate(newRotationMatrix, degToRad(deltaY / 10), [1, 0, 0]);
1039
1040 mat4.multiply(newRotationMatrix, rotationMatrix, rotationMatrix);
1041
1042 lastMouseX = newX
1043 lastMouseY = newY;
1044 }
1045
1046 function setGraphicalInput(code){
1047 if(code==1)apicor.setValue(beeldC);
1048 if(code==2)apisag.setValue(beeldS);
1049 if(code==3)apitrans.setValue(beeldT);
1050 if(code==4)apifull.setValue(zoom);
1051 }
1052
1053 function initBuffers() {
1054 if (canv == 4){
XIV
1055 //CUBE PLANE SAGITTAL
1056 cubeVertexPositionBuffer = gl.createBuffer();
1057 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
1058 vertices = [
1059 -1.0, -1.0, 0.0,
1060 1.0, -1.0, 0.0,
1061 1.0, 1.0, 0.0,
1062 -1.0, 1.0, 0.0,
1063 ];
1064 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
1065 cubeVertexPositionBuffer.itemSize = 3;
1066 cubeVertexPositionBuffer.numItems = 4;
1067
1068 cubeVertexTextureCoordBuffer = gl.createBuffer();
1069 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
1070 var textureCoords = [
1071 0.0, 0.0,
1072 1.0, 0.0,
1073 1.0, 1.0,
1074 0.0, 1.0,
1075 ];
1076 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
1077 cubeVertexTextureCoordBuffer.itemSize = 2;
1078 cubeVertexTextureCoordBuffer.numItems = 4;
1079
1080 cubeVertexIndexBuffer = gl.createBuffer();
1081 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
1082 var cubeVertexIndices = [
1083 0, 1, 2, 0, 2, 3,
1084 ];
1085 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices),
gl.STATIC_DRAW);
1086 cubeVertexIndexBuffer.itemSize = 1;
1087 cubeVertexIndexBuffer.numItems = 6;
1088
1089 //CUBE PLANE TRANSVERSE
1090 cube3VertexPositionBuffer = gl.createBuffer();
1091 gl.bindBuffer(gl.ARRAY_BUFFER, cube3VertexPositionBuffer);
1092 vertices = [
1093 0.0, -1.0, -1.0,
1094 0.0, 1.0, -1.0,
1095 0.0, 1.0, 1.0,
1096 0.0, -1.0, 1.0,
1097 ];
1098 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
1099 cube3VertexPositionBuffer.itemSize = 3;
1100 cube3VertexPositionBuffer.numItems = 4;
1101
1102 cube3VertexTextureCoordBuffer = gl.createBuffer();
1103 gl.bindBuffer(gl.ARRAY_BUFFER, cube3VertexTextureCoordBuffer);
1104 var textureCoords = [
1105 1.0, 0.0,
1106 1.0, 1.0,
1107 0.0, 1.0,
1108 0.0, 0.0,
1109 ];
1110 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
1111 cube3VertexTextureCoordBuffer.itemSize = 2;
1112 cube3VertexTextureCoordBuffer.numItems = 4;
1113
1114 cube3VertexIndexBuffer = gl.createBuffer();
1115 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cube3VertexIndexBuffer);
1116 var cube3VertexIndices = [
1117 0, 1, 2, 0, 2, 3,
1118 ];
1119 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cube3VertexIndices), gl.STATIC_DRAW);
1120 cube3VertexIndexBuffer.itemSize = 1;
1121 cube3VertexIndexBuffer.numItems = 6;
1122
1123 //CUBE PLANE CORONAL
1124 cube2VertexPositionBuffer = gl.createBuffer();
1125 gl.bindBuffer(gl.ARRAY_BUFFER, cube2VertexPositionBuffer);
1126 vertices = [
1127 // Tranversaal
1128 -1.0, 0.0, -1.0,
1129 -1.0, 0.0, 1.0,
1130 1.0, 0.0, 1.0,
1131 1.0, 0.0, -1.0,
1132
1133 ];
1134 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
1135 cube2VertexPositionBuffer.itemSize = 3;
XV
1136 cube2VertexPositionBuffer.numItems = 4;
1137
1138 cube2VertexTextureCoordBuffer = gl.createBuffer();
1139 gl.bindBuffer(gl.ARRAY_BUFFER, cube2VertexTextureCoordBuffer);
1140 var textureCoords = [
1141 0.0, 1.0,
1142 0.0, 0.0,
1143 1.0, 0.0,
1144 1.0, 1.0,
1145 ];
1146 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
1147 cube2VertexTextureCoordBuffer.itemSize = 2;
1148 cube2VertexTextureCoordBuffer.numItems = 4;
1149
1150 cube2VertexIndexBuffer = gl.createBuffer();
1151 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cube2VertexIndexBuffer);
1152 var cube2VertexIndices = [
1153 0, 1, 2, 0, 2, 3,
1154 ];
1155 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cube2VertexIndices), gl.STATIC_DRAW);
1156 cube2VertexIndexBuffer.itemSize = 1;
1157 cube2VertexIndexBuffer.numItems = 6;
1158 }
1159 if( canv == 1){
1160 //SQUARE 1 - Coronal
1161 squareVertexPositionBuffer = gl.createBuffer();
1162 gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
1163 vertices = [
1164 -1.0, -1.0, 0.0,
1165 1.0, -1.0, 0.0,
1166 1.0, 1.0, 0.0,
1167 -1.0, 1.0, 0.0,
1168 ];
1169 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
1170 squareVertexPositionBuffer.itemSize = 3;
1171 squareVertexPositionBuffer.numItems = 4;
1172
1173 squareVertexTextureCoordBuffer = gl.createBuffer();
1174 gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexTextureCoordBuffer);
1175 var textureCoords = [
1176 0.0, 1.0,
1177 0.0, 0.0,
1178 1.0, 0.0,
1179 1.0, 1.0,
1180 ];
1181 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
1182 squareVertexTextureCoordBuffer.itemSize = 2;
1183 squareVertexTextureCoordBuffer.numItems = 4;
1184
1185 squareVertexIndexBuffer = gl.createBuffer();
1186 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareVertexIndexBuffer);
1187 var squareVertexIndices = [
1188 0, 1, 2, 0, 2, 3,
1189 ];
1190 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(squareVertexIndices),
gl.STATIC_DRAW);
1191 squareVertexIndexBuffer.itemSize = 1;
1192 squareVertexIndexBuffer.numItems = 6;
1193 }
1194 if (canv == 2){
1195 //SQUARE 2 - Sagittal
1196 square2VertexPositionBuffer = gl.createBuffer();
1197 gl.bindBuffer(gl.ARRAY_BUFFER, square2VertexPositionBuffer);
1198 vertices = [
1199 -1.0, -1.0, 0.0,
1200 1.0, -1.0, 0.0,
1201 1.0, 1.0, 0.0,
1202 -1.0, 1.0, 0.0,
1203 ];
1204 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
1205 square2VertexPositionBuffer.itemSize = 3;
1206 square2VertexPositionBuffer.numItems = 4;
1207
1208 square2VertexTextureCoordBuffer = gl.createBuffer();
1209 gl.bindBuffer(gl.ARRAY_BUFFER, square2VertexTextureCoordBuffer);
1210 var textureCoords = [
1211 0.0, 0.0,
1212 1.0, 0.0,
1213 1.0, 1.0,
1214 0.0, 1.0,
1215 ];
1216 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
XVI
1217 square2VertexTextureCoordBuffer.itemSize = 2;
1218 square2VertexTextureCoordBuffer.numItems = 4;
1219
1220 square2VertexIndexBuffer = gl.createBuffer();
1221 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, square2VertexIndexBuffer);
1222 var square2VertexIndices = [
1223 0, 1, 2, 0, 2, 3,
1224 ];
1225 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(square2VertexIndices),
gl.STATIC_DRAW);
1226 square2VertexIndexBuffer.itemSize = 1;
1227 square2VertexIndexBuffer.numItems = 6;
1228 }
1229
1230 if (canv == 3){
1231 //SQUARE 3 - Transverse
1232 square3VertexPositionBuffer = gl.createBuffer();
1233 gl.bindBuffer(gl.ARRAY_BUFFER, square3VertexPositionBuffer);
1234 vertices = [
1235 -1.0, -1.0, 0.0,
1236 1.0, -1.0, 0.0,
1237 1.0, 1.0, 0.0,
1238 -1.0, 1.0, 0.0,
1239 ];
1240 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
1241 square3VertexPositionBuffer.itemSize = 3;
1242 square3VertexPositionBuffer.numItems = 4;
1243
1244 square3VertexTextureCoordBuffer = gl.createBuffer();
1245 gl.bindBuffer(gl.ARRAY_BUFFER, square3VertexTextureCoordBuffer);
1246 var textureCoords = [
1247 0.0, 0.0,
1248 1.0, 0.0,
1249 1.0, 1.0,
1250 0.0, 1.0,
1251 ];
1252 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
1253 square3VertexTextureCoordBuffer.itemSize = 2;
1254 square3VertexTextureCoordBuffer.numItems = 4;
1255
1256 square3VertexIndexBuffer = gl.createBuffer();
1257 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, square3VertexIndexBuffer);
1258 var square3VertexIndices = [
1259 0, 1, 2, 0, 2, 3,
1260 ];
1261 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(square3VertexIndices),
gl.STATIC_DRAW);
1262 square3VertexIndexBuffer.itemSize = 1;
1263 square3VertexIndexBuffer.numItems = 6;
1264 }
1265 }
1266
1267 function drawScene() {
1268 gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
1269 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
1270
1271 mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
1272
1273 mat4.identity(mvMatrix);
1274 mat4.identity(cubeMatrix);
1275
1276 if(resetRotation) mat4.identity(rotationMatrix);
1277
1278 if(canv == 4){
1279 //DRAWING SAGITTAL
1280
1281 mvPushMatrix();
1282 cubePushMatrix();
1283
1284 mat4.translate(mvMatrix, [0.0,0.0, zoom]);
1285 mat4.multiply(cubeMatrix, rotationMatrix);
1286 mat4.translate(cubeMatrix, [0, 0, sagittalMove]);
1287 mat4.multiply(mvMatrix, cubeMatrix);
1288
1289 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
1290 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
1291
1292 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
1293 gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,
cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
1294
XVII
1295 gl.activeTexture(gl.TEXTURE0);
1296 gl.bindTexture(gl.TEXTURE_2D, sagTex);
1297 gl.uniform1i(shaderProgram.samplerUniform, 0);
1298
1299 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
1300 setMatrixUniforms();
1301 gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
1302
1303 cubePopMatrix();
1304 mvPopMatrix();
1305
1306
1307 //DRAWING TRANSVERSAL
1308
1309 mvPushMatrix();
1310 cubePushMatrix();
1311
1312 mat4.translate(mvMatrix, [0.0,0.0, zoom]);
1313 mat4.multiply(cubeMatrix, rotationMatrix);
1314 mat4.translate(cubeMatrix, [transversalMove, 0, 0]);
1315 mat4.multiply(mvMatrix, cubeMatrix);
1316
1317 gl.bindBuffer(gl.ARRAY_BUFFER, cube3VertexPositionBuffer);
1318 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
cube3VertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
1319
1320 gl.bindBuffer(gl.ARRAY_BUFFER, cube3VertexTextureCoordBuffer);
1321 gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,
cube3VertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
1322
1323
1324 gl.activeTexture(gl.TEXTURE0);
1325 gl.bindTexture(gl.TEXTURE_2D, transTex);
1326 gl.uniform1i(shaderProgram.samplerUniform, 0);
1327
1328 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cube3VertexIndexBuffer);
1329 setMatrixUniforms();
1330 gl.drawElements(gl.TRIANGLES, cube3VertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
1331
1332 cubePopMatrix();
1333 mvPopMatrix();
1334
1335
1336 //DRAWING CORONAL
1337
1338 mvPushMatrix();
1339 cubePushMatrix();
1340
1341 mat4.translate(mvMatrix, [0.0,0.0, zoom]);
1342 mat4.multiply(cubeMatrix, rotationMatrix);
1343 mat4.translate(cubeMatrix, [0, coronalMove, 0]);
1344 mat4.multiply(mvMatrix, cubeMatrix);
1345
1346 gl.bindBuffer(gl.ARRAY_BUFFER, cube2VertexPositionBuffer);
1347 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
cube2VertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
1348
1349 gl.bindBuffer(gl.ARRAY_BUFFER, cube2VertexTextureCoordBuffer);
1350 gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,
cube2VertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
1351
1352 gl.activeTexture(gl.TEXTURE0);
1353 gl.bindTexture(gl.TEXTURE_2D, corTex);
1354 gl.uniform1i(shaderProgram.samplerUniform, 0);
1355
1356 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cube2VertexIndexBuffer);
1357 setMatrixUniforms();
1358 gl.drawElements(gl.TRIANGLES, cube2VertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
1359
1360 cubePopMatrix();
1361 mvPopMatrix();
1362 } else if ( canv == 1 ){
1363 mvPushMatrix();
1364 mat4.translate(mvMatrix, [0.0,0.0, -2.42]);
1365
1366 gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
1367 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
1368
1369 gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexTextureCoordBuffer);
1370 gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,
squareVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
XVIII
1371
1372 gl.activeTexture(gl.TEXTURE0);
1373 gl.bindTexture(gl.TEXTURE_2D, corTexture[beeldC]);
1374 gl.uniform1i(shaderProgram.samplerUniform, 0);
1375
1376 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareVertexIndexBuffer);
1377 setMatrixUniforms();
1378 gl.drawElements(gl.TRIANGLES, squareVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT,
0);
1379
1380 mvPopMatrix();
1381
1382 }else if ( canv == 2 ){
1383 mvPushMatrix();
1384 mat4.translate(mvMatrix, [0.0,0.0, -2.42]);
1385
1386 gl.bindBuffer(gl.ARRAY_BUFFER, square2VertexPositionBuffer);
1387 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
square2VertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
1388
1389 gl.bindBuffer(gl.ARRAY_BUFFER, square2VertexTextureCoordBuffer);
1390 gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,
square2VertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
1391
1392 gl.activeTexture(gl.TEXTURE0);
1393 gl.bindTexture(gl.TEXTURE_2D, sagTexture[beeldS]);
1394 gl.uniform1i(shaderProgram.samplerUniform, 0);
1395
1396 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, square2VertexIndexBuffer);
1397 setMatrixUniforms();
1398 gl.drawElements(gl.TRIANGLES, square2VertexIndexBuffer.numItems, gl.UNSIGNED_SHORT,
0);
1399
1400 mvPopMatrix();
1401
1402 }else if ( canv == 3 ){
1403 mvPushMatrix();
1404 mat4.translate(mvMatrix, [0.0,0.0, -2.42]);
1405
1406 gl.bindBuffer(gl.ARRAY_BUFFER, square3VertexPositionBuffer);
1407 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
square3VertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
1408
1409 gl.bindBuffer(gl.ARRAY_BUFFER, square3VertexTextureCoordBuffer);
1410 gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,
square3VertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
1411
1412 gl.activeTexture(gl.TEXTURE0);
1413 gl.bindTexture(gl.TEXTURE_2D, transTexture[beeldT]);
1414 gl.uniform1i(shaderProgram.samplerUniform, 0);
1415
1416 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, square3VertexIndexBuffer);
1417 setMatrixUniforms();
1418 gl.drawElements(gl.TRIANGLES, square3VertexIndexBuffer.numItems, gl.UNSIGNED_SHORT,
0);
1419
1420 mvPopMatrix();
1421
1422 }
1423 }
1424 }
XIX
Bijlage B
Figuur 9 - Vier canvas elementen
Figuur 10 - Error buffer/texture sharing
Figuur 11 - Fout zonder caching (bron: Eigen verwerking)
XX
Figuur 12 OpenGL es 2.0 pijplijn (bron: (14))
Figuur 13 - Input scherm
XXI
Figuur 14 - Klikken in coronaal canvas
Figuur 15 - Geheugengebruik Firefox 4
XXII
Referenties
1. World Wide Web Consortium, (W3C). W3C Open Source Software. W3C. [Online] 5 5, 2011.
http://www.w3.org/Status.html.
2. World Wide Web Consortium (W3C). HTML5 W3C Working Draft 05 April 2011. A vocabulary and associated
APIs for HTML and XHTML. [Online] 4 5, 2011. http://www.w3.org/TR/html5/.
3. Microsoft. get Silverlight. Silverlight License. [Online] http://www.microsoft.com/getsilverlight/get-
started/install/license.aspx.
4. Adobe. Adobe Licensing. [Online] 2011. http://www.adobe.com/licensing/.
5. World Wide Web Consortium (W3C). HTML5 differences from HTML4. W3C. [Online] 4 28, 2011.
http://dev.w3.org/html5/html4-differences/.
6. Word Wide Web Consortium (W3C). The Canvas Element. [Online] 2011. http://www.w3.org/TR/html5/the-
canvas-element.html#the-canvas-element.
7. Dive into HTML 5. Canvas coordiantes. [Online] 2011. http://diveintohtml5.org/canvas.html.
8. WHATWG. The Canvas Element. [Online] 2011. http://www.whatwg.org/specs/web-apps/current-
work/multipage/the-canvas-element.html.
9. —. WHATWG. Drag and Drop. [Online] 2011. http://www.whatwg.org/specs/web-apps/current-work/#dnd.
10. Khronos. WebGL - OpenGL ES 2.0 for the Web. Khronos. [Online] 2011. http://www.khronos.org/webgl/.
11. —. WebGL Specification 1.0. Khronos. [Online] 2 10, 2011.
https://www.khronos.org/registry/webgl/specs/1.0/.
12. Mozilla. Platform / GFX / HardwareAcceleration. Mozilla Wiki. [Online]
13. Chromium. Unleashing GPU acceleration on the web. The Chromium blog. [Online] 09 14, 2010.
http://blog.chromium.org/2010/09/unleashing-gpu-acceleration-on-web.html.
14. Munshi, Aaftab, Ginsburg, Dan and Shreiner, Dave. OpenGL ES 2.0 - Programming Guide. Massachusetts :
Addison-Wesley, 2009. 978-0-321-50279-7.
15. Wright, Richard S., et al. OpenGL Superbible 5th Edition. s.l. : Addison-Wesley, 2011. 978-0-32-171261-5.
16. Flanagan, David. Javascript - The Definitive Guide. s.l. : O'reilly, 2011. 978-0-596-80552-4.
17. Khronos. Webgl Wiki. WebGL and OpenGL Differences. [Online]
http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences.
XXIII
18. Lecomte, Julien. Running CPU intensive Javascript Computations in a web browser. [Online] 2007.
http://www.julienlecomte.net/blog/2007/10/28/.
19. Thomas, Giles. Learning WebGL. Lessons. [Online] 2011. http://learningwebgl.com.
20. Lehtonen, Sakari. Fraktal thinking. [Online] http://lgo900.wordpress.com/.
21. About.com. The browser Object Model. About.com. [Online]
http://javascript.about.com/od/browserobjectmodel/a/bom10.htm.
22. World Wide Web Consortium (W3C). HTML5 7.7 Drag and Drop. HTML5. [Online] W3C, 2011.
http://www.w3.org/TR/html5/dnd.html.
23. —. File API. W3C. [Online] 2011. http://www.w3.org/TR/file-upload/.
24. Mozilla. Using XMLHttpRequest. MDN Docs. [Online] 2011.
https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest.
25. —. Using files from web applications. MDN Docs. [Online] 2011.
https://developer.mozilla.org/en/using_files_from_web_applications.
26. Quirksmode. Change style sheet. Quirksmode. [Online] http://www.quirksmode.org/dom/changess.html.
27. Mozilla. event.detail. MDN docs. [Online] 2011. https://developer.mozilla.org/en/DOM/event.detail.
28. Quirksmode. event properties. Quirksmode. [Online] 2011.
http://www.quirksmode.org/js/events_properties.html.
Matthias Cornet
Webapplicatie voor interactieve 3D-beeldweergave
Academiejaar 2010-2011Faculteit Ingenieurswetenschappen en ArchitectuurVoorzitter: prof. dr. ir. Herwig BruneelVakgroep Telecommunicatie en Informatieverwerking
Master in de toegepaste informaticaMasterproef ingediend tot het behalen van de academische graad van
Begeleiders: Dirk Van Haerenborgh, ir. Johan De BockPromotor: prof. dr. ir. Wilfried Philips