Äskettäisessä artikkelissaan ApeeScapen blogista, ammattitaitoinen tutkija Charles Cook kirjoitti tieteellinen laskenta avoimen lähdekoodin työkaluilla . Hänen opetusohjelmassaan on tärkeä näkökohta avoimen lähdekoodin työkaluista ja roolista, joka niillä voi olla tietojen käsittelyssä ja tulosten hankkimisessa.
Mutta heti kun olemme ratkaisseet kaikki nämä monimutkaiset differentiaaliyhtälöt, tulee esiin uusi ongelma. Kuinka ymmärrämme ja tulkitsemme näistä simulaatioista tulevan valtavan määrän tietoja? Kuinka visualisoimme potentiaalisia gigatavuja tietoja, kuten tietoja, joissa on miljoonia ruudukkopisteitä suuressa simulaatiossa?
Työssään samanlaisten ongelmien parissa Pro gradu tutkielma , Otin yhteyttä Visualization Toolkitiin tai VTK: han - tehokkaaseen grafiikkakirjastoon, joka on erikoistunut tietojen visualisointiin.
Tässä opetusohjelmassa annan nopean esittelyn VTK: sta ja sen putkistoarkkitehtuurista ja jatkan keskustelemaan tosielämän 3D-visualisointiesimerkistä juoksupyörän pumpun simuloidusta nesteestä. Lopuksi luetellaan kirjaston vahvuudet ja kohtaamani heikot kohdat.
Avoimen lähdekoodin kirjasto VTK sisältää vankan käsittely- ja renderointiputken, jossa on monia hienostuneita visualisointialgoritmeja. Sen ominaisuudet eivät kuitenkaan pysähdy tähän, koska ajan myötä myös kuvien ja verkkojen käsittelyalgoritmit on lisätty. Käytän nykyisessä projektissani hammaslääketieteen tutkimusyrityksen kanssa VTK: ta verkkopohjaisiin käsittelytehtäviin a Qt -pohjainen, CAD-tyyppinen sovellus. VTK-tapaustutkimukset näytä laaja valikoima sopivia sovelluksia.
VTK: n arkkitehtuuri pyörii tehokkaan putkikonseptin ympärillä. Tämän käsitteen peruspiirteet on esitetty tässä:
vtkConeSource
luo 3D-kartion ja vtkSTLReader
lukee *.stl
3D-geometriatiedostot.vtkCutter
leikkaa algoritmeissa edellisen objektin lähdön implisiittisen funktion, esimerkiksi tason, avulla. Kaikki VTK: n mukana tulevat käsittelyalgoritmit on toteutettu suodattimina ja ne voidaan ketjuttaa vapaasti yhteen.Tyypillinen VTK-renderöintiputki alkaa yhdestä tai useammasta lähteestä, käsittelee ne käyttämällä erilaisia suodattimia useiksi lähtöobjekteiksi, jotka sitten renderoidaan erikseen kartoittajien ja toimijoiden avulla. Tämän konseptin voima on päivitysmekanismi. Jos suodattimien tai lähteiden asetuksia muutetaan, kaikki riippuvat suodattimet, kartoittimet, näyttelijät ja renderointi-ikkunat päivitetään automaattisesti. Jos toisaalta putkilinjan alapuolella oleva esine tarvitsee tietoja tehtäviensä suorittamiseksi, se voi helposti hankkia sen.
Lisäksi OpenGL: n kaltaisia renderointijärjestelmiä ei tarvitse käsitellä suoraan. VTK kapseloi kaikki matalan tason tehtävät alustasta ja (osittain) renderointijärjestelmästä riippumattomalla tavalla; kehittäjä toimii paljon korkeammalla tasolla.
Tarkastellaan datan visualisointiesimerkkiä, joka käyttää IEEE Visualization Contest 2011 -tulostimen nestevirtausta pyörivässä juoksupyörän pumpussa. Tiedot itse ovat seurausta laskennallisesta nestedynamiikan simulaatiosta, aivan kuten Charles Cookin artikkelissa kuvattu.
Esitetyn pumpun pakatut simulaatiotiedot ovat kooltaan yli 30 Gt. Se sisältää useita osia ja useita aikavaiheita, joten suuri koko. Tässä oppaassa pelataan yhdessä näiden aikavaiheiden roottoriosan kanssa, jonka pakattu koko on noin 150 Mt.
Valitsemani kieli VTK: n käytössä on C ++, mutta on olemassa kartoituksia useille muille kielille, kuten Tcl / Tk, Java ja Python. Jos kohde on vain yhden tietojoukon visualisointi, koodia ei tarvitse kirjoittaa ollenkaan, vaan sitä voidaan käyttää Paraview , graafinen käyttöliittymä suurimmalle osalle VTK: n toiminnoista.
Pienen roottorin tietojoukon yllä olevasta 30 Gt: n tietojoukosta avaamalla yhden aikavaiheen Paraview-näytössä ja purkamalla roottorin osan erilliseen tiedostoon. Se on strukturoimaton ruudukkotiedosto, eli 3D-tilavuus, joka koostuu pisteistä ja 3D-soluista, kuten heksahedrat, tetraederit ja niin edelleen. Jokaisella 3D-pisteellä on siihen liittyvät arvot. Joskus soluihin liittyy myös arvoja, mutta ei tässä tapauksessa. Tämä koulutus keskittyy paineeseen ja nopeuteen pisteissä ja yrittää visualisoida ne 3D-kontekstissaan.
Pakattu tiedostokoko on noin 150 Mt ja muistin koko on noin 280 Mt ladattuna VTK: lla. Käsittelemällä sen VTK: ssa, datajoukko välimuistissa on useita kertoja VTK-putkessa ja saavutamme nopeasti 2 Gt: n muistirajan 32-bittisille ohjelmille. Muistia voi säästää käytettäessä VTK: ta, mutta pitämällä sen yksinkertaisena kootaan ja suoritetaan esimerkki 64-bittisenä.
Kiitokset : Tietojoukko on saatavana Clausthalin yliopiston soveltavan mekaniikan instituutin kautta Saksassa (Dipl. Wirtsch.-Ing. Andreas Lucius).
Mitä saavutamme käyttämällä VTK: ta työkaluna, on alla olevassa kuvassa esitetty visualisointi. 3D-kontekstina tietojoukon ääriviivat esitetään osittain läpinäkyvällä lankakehyshahmonnuksella. Tietojoukon vasenta osaa käytetään sitten paineen näyttämiseen pintojen yksinkertaista värikoodausta käyttämällä. (Ohitamme tämän esimerkin monimutkaisemman volyymirenderöinnin). Nopeuskentän visualisoimiseksi datajoukon oikea osa täytetään virtaviivaistaa , jotka on värikoodattu niiden nopeuden suuruuden mukaan. Tämä visualisointivalinta ei ole teknisesti ihanteellinen, mutta halusin pitää VTK-koodin mahdollisimman yksinkertaisena. Lisäksi tällä esimerkillä on syy olla osa visualisointihaastetta, ts. Paljon pyörteitä virtauksessa.
Keskustelen VTK-koodista askel askeleelta ja näytän, kuinka renderöintilähtö näyttäisi jokaisessa vaiheessa. Koko lähdekoodi voidaan ladata koulutuksen lopussa.
Aloitetaan sisällyttämällä kaikki tarvitsemamme VTK: lta ja avaamalla päätoiminto.
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int main(int argc, char** argv) {
Seuraavaksi asetamme renderöijän ja renderöinti-ikkunan tulosten näyttämiseksi. Asetamme taustavärin ja renderöinnin ikkunan koon.
// Setup the renderer vtkNew renderer; renderer->SetBackground(0.9, 0.9, 0.9); // Setup the render window vtkNew renWin; renWin->AddRenderer(renderer.Get()); renWin->SetSize(500, 500);
Tällä koodilla voimme jo näyttää staattisen renderointi-ikkunan. Sen sijaan päätämme lisätä vtkRenderWindowInteractor
kiertääksesi, zoomaamalla ja panoroimalla kohtausta vuorovaikutteisesti.
// Setup the render window interactor vtkNew interact; vtkNew style; interact->SetRenderWindow(renWin.Get()); interact->SetInteractorStyle(style.Get());
Nyt meillä on käynnissä oleva esimerkki, jossa näkyy harmaa, tyhjä renderointi-ikkuna.
Seuraavaksi lataamme tietojoukon käyttämällä yhtä monista VTK: n mukana tulevista lukijoista.
// Read the file vtkSmartPointer pumpReader = vtkSmartPointer::New(); pumpReader->SetFileName('rotor.vtu');
Lyhyt retki VTK-muistinhallintaan : VTK käyttää kätevää automaattista muistinhallintakonseptia, joka pyörii referenssilaskennan ympärillä. Eroaa useimmista muista toteutuksista, mutta viitemäärä pidetään itse VTK-objekteissa älykkään osoitinluokan sijaan. Tällä on se etu, että referenssimäärää voidaan lisätä, vaikka VTK-objekti siirrettäisiinkin raakaosoittimena. Hallitut VTK-objektit voidaan luoda kahdella tavalla. vtkNew
ja vtkSmartPointer::New()
, pääasiallisena erona on, että a vtkSmartPointer
on implisiittinen cast-kykenevä raakaosoittimeen T*
ja voidaan palauttaa funktiosta. vtkNew
meidän on soitettava .Get()
saada raaka osoitin ja voimme palauttaa sen vain käärimällä se vtkSmartPointer
Esimerkissämme emme koskaan palaa funktioista ja kaikki objektit elävät koko ajan, joten käytämme lyhyttä vtkNew
paitsi ylläolevaa poikkeusta esittelytarkoituksiin.
Tässä vaiheessa tiedostosta ei ole vielä luettu mitään. Meidän tai suodattimen alapuolella olevan suodattimen on soitettava Update()
jotta tiedoston lukeminen tapahtuisi. Yleensä on paras tapa antaa VTK-luokkien hoitaa päivitykset itse. Joskus haluamme kuitenkin käyttää suodattimen tulosta suoraan, esimerkiksi saadaksemme paineen alueen tässä tietojoukossa. Sitten meidän on soitettava Update()
käsin. (Emme menetä suorituskykyä soittamalla Update()
useita kertoja, koska tulokset tallennetaan välimuistiin.)
// Get the pressure range pumpReader->Update(); double pressureRange[2]; pumpReader->GetOutput()->GetPointData()->GetArray('Pressure')->GetRange(pressureRange);
Seuraavaksi meidän on purettava tietojoukon vasen puolisko käyttämällä vtkClipDataSet
. Tämän saavuttamiseksi määritämme ensin a vtkPlane
joka määrittelee jakautumisen. Sitten näemme ensimmäistä kertaa, kuinka VTK-putki on kytketty yhteen: successor->SetInputConnection(predecessor->GetOutputPort())
. Aina kun pyydämme päivitystä clipperLeft
tämä yhteys varmistaa nyt, että kaikki edelliset suodattimet ovat myös ajan tasalla.
// Clip the left part from the input vtkNew planeLeft; planeLeft->SetOrigin(0.0, 0.0, 0.0); planeLeft->SetNormal(-1.0, 0.0, 0.0); vtkNew clipperLeft; clipperLeft->SetInputConnection(pumpReader->GetOutputPort()); clipperLeft->SetClipFunction(planeLeft.Get());
Lopuksi luomme ensimmäiset näyttelijät ja kartoittajat näyttämään vasemman puoliskon lankakehyshahmonnuksen. Huomaa, että kartoitin on kytketty suodattimeensa samalla tavalla kuin suodattimet toisiinsa. Suurimman osan ajasta renderöijä laukaisee kaikkien toimijoiden, kartoittajien ja niiden alla olevien suodatinketjujen päivitykset!
Ainoa rivi, joka ei ole itsestään selvä, on luultavasti leftWireMapper->ScalarVisibilityOff();
- se kieltää lankakehyksen värin painearvoilla, jotka asetetaan tällä hetkellä aktiiviseksi matriisiksi.
// Create the wireframe representation for the left part vtkNew leftWireMapper; leftWireMapper->SetInputConnection(clipperLeft->GetOutputPort()); leftWireMapper->ScalarVisibilityOff(); vtkNew leftWireActor; leftWireActor->SetMapper(leftWireMapper.Get()); leftWireActor->GetProperty()->SetRepresentationToWireframe(); leftWireActor->GetProperty()->SetColor(0.8, 0.8, 0.8); leftWireActor->GetProperty()->SetLineWidth(0.5); leftWireActor->GetProperty()->SetOpacity(0.8); renderer->AddActor(leftWireActor.Get());
Tässä vaiheessa renderöinti-ikkuna näyttää lopulta jotain, ts. Vasemman osan lankakehyksen.
Oikean osan lankakehys renderöidään samalla tavalla vaihtamalla (vasta luodun) tason normaalia vtkClipDataSet
päinvastaiseen suuntaan ja muuttamalla (vasta luodun) kartoittajan ja näyttelijän väriä ja peittävyyttä hieman. Huomaa, että tässä VTK-putkijohto jakautuu kahteen suuntaan (oikea ja vasen) samasta syötetiedostosta.
// Clip the right part from the input vtkNew planeRight; planeRight->SetOrigin(0.0, 0.0, 0.0); planeRight->SetNormal(1.0, 0.0, 0.0); vtkNew clipperRight; clipperRight->SetInputConnection(pumpReader->GetOutputPort()); clipperRight->SetClipFunction(planeRight.Get()); // Create the wireframe representation for the right part vtkNew rightWireMapper; rightWireMapper->SetInputConnection(clipperRight->GetOutputPort()); rightWireMapper->ScalarVisibilityOff(); vtkNew rightWireActor; rightWireActor->SetMapper(rightWireMapper.Get()); rightWireActor->GetProperty()->SetRepresentationToWireframe(); rightWireActor->GetProperty()->SetColor(0.2, 0.2, 0.2); rightWireActor->GetProperty()->SetLineWidth(0.5); rightWireActor->GetProperty()->SetOpacity(0.1); renderer->AddActor(rightWireActor.Get());
Lähtöikkuna näyttää nyt molemmat lankakehysosat odotetusti.
Nyt olemme valmiita visualisoimaan hyödyllisiä tietoja! Paineen visualisoinnin lisäämiseksi vasempaan osaan meidän ei tarvitse tehdä paljon. Luomme uuden kartan ja yhdistämme sen clipperLeft
samoin, mutta tällä kertaa värjäämme paineryhmällä. Täällä on myös vihdoin käytössä pressureRange
olemme johtaneet edellä.
// Create the pressure representation for the left part vtkNew pressureColorMapper; pressureColorMapper->SetInputConnection(clipperLeft->GetOutputPort()); pressureColorMapper->SelectColorArray('Pressure'); pressureColorMapper->SetScalarRange(pressureRange); vtkNew pressureColorActor; pressureColorActor->SetMapper(pressureColorMapper.Get()); pressureColorActor->GetProperty()->SetOpacity(0.5); renderer->AddActor(pressureColorActor.Get());
Tulos näyttää nyt alla olevalta kuvalta. Paine keskellä on hyvin matala, imee materiaalia pumppuun. Sitten tämä materiaali kuljetetaan ulkopuolelle, mikä saa nopeasti paineen. (Tietysti pitäisi olla värikartta-selite, jossa on todelliset arvot, mutta jätin sen pois, jotta esimerkki olisi lyhyempi.)
Nyt hankalampi osa alkaa. Haluamme piirtää nopeuden virtaviivoja oikeaan osaan. Suoraviivat syntyvät integroimalla vektorikenttään lähdekohdista. Vektorikenttä on jo osa tietoaineistoa 'Velocities' -vektoriryhmän muodossa. Joten meidän on luotava vain lähdepisteet. vtkPointSource
tuottaa satunnaispisteiden pallon. Luomme 1500 lähdepistettä, koska suurin osa niistä ei ole missään tapauksessa tietojoukossa ja virtajäljitin jättää ne huomioimatta.
// Create the source points for the streamlines vtkNew pointSource; pointSource->SetCenter(0.0, 0.0, 0.015); pointSource->SetRadius(0.2); pointSource->SetDistributionToUniform(); pointSource->SetNumberOfPoints(1500);
Seuraavaksi luomme streamtracerin ja asetamme sen tuloyhteydet. 'Odota, useita yhteyksiä? ”, saatat sanoa. Kyllä - tämä on ensimmäinen VTK-suodatin, jossa on useita tuloja. Normaalia tuloliitäntää käytetään vektorikentässä ja lähdeliitäntää siemenpisteissä. Koska 'Nopeudet' on 'aktiivinen' vektoriryhmä clipperRight
: ssa, meidän ei tarvitse määrittää sitä tässä nimenomaisesti. Lopuksi määrittelemme, että integrointi tulisi suorittaa molempiin suuntiin siemenpisteistä, ja asetamme integrointimenetelmäksi Runge-Kutta-4.5 .
vtkNew tracer; tracer->SetInputConnection(clipperRight->GetOutputPort()); tracer->SetSourceConnection(pointSource->GetOutputPort()); tracer->SetIntegrationDirectionToBoth(); tracer->SetIntegratorTypeToRungeKutta45();
Seuraava ongelma on virtaviivojen värittäminen nopeuden suuruuden mukaan. Koska vektorien suuruuksille ei ole taulukkoa, laskemme suuruudet yksinkertaisesti uudeksi skalaariryhmäksi. Kuten olet arvannut, myös tässä tehtävässä on VTK-suodatin: vtkArrayCalculator
. Se vie tietojoukon ja antaa sen muuttumattomana, mutta lisää tarkalleen yhden taulukon, joka lasketaan yhdestä tai useammasta olemassa olevasta. Konfiguroimme tämän matriisilaskimen ottamaan “Velocity” -vektorin suuruuden ja antamaan sen nimellä “MagVelocity”. Lopuksi soitamme Update()
uudelleen manuaalisesti uuden taulukon alueen johtamiseksi.
// Compute the velocity magnitudes and create the ribbons vtkNew magCalc; magCalc->SetInputConnection(tracer->GetOutputPort()); magCalc->AddVectorArrayName('Velocity'); magCalc->SetResultArrayName('MagVelocity'); magCalc->SetFunction('mag(Velocity)'); magCalc->Update(); double magVelocityRange[2]; magCalc->GetOutput()->GetPointData()->GetArray('MagVelocity')->GetRange(magVelocityRange);
vtkStreamTracer
tuottaa suoraan viivat ja vtkArrayCalculator
välittää ne muuttumattomina. Siksi voimme vain näyttää magCalc
-lähdön suoraan uuden kartoittajan ja näyttelijän avulla.
Sen sijaan tässä koulutuksessa päätämme tehdä tulosta hieman mukavammaksi näyttämällä sen sijaan nauhoja. vtkRibbonFilter
generoi 2D-solut näyttämään nauhoja kaikille syötteensä polylineille.
// Create and render the ribbons vtkNew ribbonFilter; ribbonFilter->SetInputConnection(magCalc->GetOutputPort()); ribbonFilter->SetWidth(0.0005); vtkNew streamlineMapper; streamlineMapper->SetInputConnection(ribbonFilter->GetOutputPort()); streamlineMapper->SelectColorArray('MagVelocity'); streamlineMapper->SetScalarRange(magVelocityRange); vtkNew streamlineActor; streamlineActor->SetMapper(streamlineMapper.Get()); renderer->AddActor(streamlineActor.Get());
Se, mikä vielä puuttuu, ja jota tarvitaan tosiasiallisesti myös välirenderöintien tuottamiseen, ovat viisi viimeistä riviä, jotka todella tekevät näkymän ja alustavat vuorovaikuttajan.
// Render and show interactive window renWin->Render(); interact->Initialize(); interact->Start(); return 0; }
Lopuksi pääsemme valmiiseen visualisointiin, jonka esitän vielä kerran täällä:
Yllä olevan visualisoinnin täydellinen lähdekoodi löytyy tässä .
Lopetan tämän artikkelin luettelolla VTK-puitteiden henkilökohtaisista eduista ja haitoista.
Sillä : Aktiivinen kehitys : VTK: ta kehitetään aktiivisesti useilta avustajilta, lähinnä tutkimusyhteisöltä. Tämä tarkoittaa, että joitain huippuluokan algoritmeja on saatavana, monia 3D-formaatteja voidaan tuoda ja viedä, vikoja korjataan aktiivisesti ja ongelmilla on yleensä valmis ratkaisu keskustelupalstoilla.
Kanssa : Luotettavuus : Monien algoritmien yhdistäminen eri tekijöiltä VTK: n avoimen putkisuunnittelun kanssa voi kuitenkin johtaa ongelmiin epätavallisilla suodatinyhdistelmillä. Minun on täytynyt mennä VTK-lähdekoodiin muutaman kerran selvittääkseen, miksi monimutkainen suodatinketjuni ei tuota toivottuja tuloksia. Suosittelen vahvasti VTK: n asentamista tavalla, joka sallii virheenkorjauksen.
Sillä : Ohjelmistoarkkitehtuuri : VTK: n putkisuunnittelu ja yleinen arkkitehtuuri tuntuu hyvin harkitulta ja on ilo työskennellä. Muutama koodirivi voi tuottaa uskomattomia tuloksia. Sisäänrakennetut tietorakenteet on helppo ymmärtää ja käyttää.
Kanssa : Mikroarkkitehtuuri : Jotkut mikroarkkitehtuuriset suunnittelupäätökset välttävät ymmärrystäni. Vakiokorjausta ei ole lainkaan, taulukot välitetään tuloina ja lähtöinä ilman selkeää eroa. Lievensin tätä omille algoritmeilleni luopumalla suorituskyvystä ja käyttämällä omaa kääreä vtkMath
joka käyttää mukautettuja 3D-tyyppejä, kuten typedef std::array Pnt3d;
.
Sillä : Mikrodokumentaatio : Kaikkien luokkien ja suodattimien Doxygen-dokumentaatio on laaja ja käyttökelpoinen, wikissä olevat esimerkit ja testitapaukset ovat myös suuri apu suodattimien käytön ymmärtämisessä.
Kanssa : Makrodokumentaatio : Verkossa on useita hyviä oppaita ja esittelyjä VTK: lle. Sikäli kuin tiedän, ei ole suurta viiteasiakirjaa, joka selittäisi, kuinka erityisiä asioita tehdään. Jos haluat tehdä jotain uutta, odota etsivän, miten se tehdään jonkin aikaa. Lisäksi tehtävää varten on vaikea löytää tiettyä suodatinta. Kun olet löytänyt sen, Doxygen-dokumentaatio yleensä riittää. Hyvä tapa tutustua VTK-kehykseen on ladata ja kokeilla Paraviewia.
Sillä : Implisiittinen rinnakkaistuki : Jos lähteet voidaan jakaa useaan osaan, jotka voidaan käsitellä itsenäisesti, rinnakkaistaminen on yhtä helppoa kuin erillisen suodatinketjun luominen kuhunkin yksittäistä osaa käsittelevään ketjuun. Useimmat suuret visualisointiongelmat kuuluvat yleensä tähän luokkaan.
Kanssa : Ei nimenomaista rinnakkaistukea : Jos sinua ei siunata suurilla, jaettavissa olevilla ongelmilla, mutta haluat käyttää useita ytimiä, olet yksin. Sinun on selvitettävä, mitkä luokat ovat langattomia, tai jopa palata takaisin kokeiluvirheellä tai lukemalla lähde. Olen kerran löytänyt rinnakkaisongelman VTK-suodattimelle, joka käytti staattista globaalia muuttujaa soittaakseen C-kirjastoon.
Sillä : Buildsystem CMake : Monialustainen meta-build-järjestelmä CMake on kehittänyt myös Kitware (VTK: n valmistajat) ja sitä käytetään monissa Kitwaren ulkopuolisissa projekteissa. Se integroituu erittäin hyvin VTK: n kanssa ja tekee useiden alustojen koontijärjestelmän perustamisesta vähemmän tuskallista.
Sillä : Alustan riippumattomuus, lisenssi ja pitkäikäisyys : VTK on alustasta riippumaton, ja sen käyttöoikeus on a erittäin salliva BSD-tyylinen lisenssi . Lisäksi ammattitaitoista tukea on tarjolla niille tärkeille hankkeille, jotka sitä tarvitsevat. Kitwarea tukevat monet tutkimusyksiköt ja muut yritykset, ja se tulee olemaan jonkin aikaa.
Kaiken kaikkiaan VTK on paras tietojen visualisointityökalu sellaisiin ongelmiin, joita rakastan. Jos olet koskaan törmännyt projektiin, joka vaatii visualisointia, verkkokäsittelyä, kuvankäsittelyä tai vastaavia tehtäviä, yritä käynnistää Paraview syöttöesimerkillä ja arvioida, voisiko VTK olla sinulle työkalu.