В скорошната си статия в блога на ApeeScape, квалифициран учен за данни Чарлз Кук пише за научни изчисления с инструменти с отворен код . Неговият урок прави важен момент относно инструментите с отворен код и ролята, която те могат да играят при лесното обработване на данни и получаването на резултати.
Но щом решим всички тези сложни диференциални уравнения, възникват други проблеми. Как да разберем и интерпретираме огромните количества данни, излизащи от тези симулации? Как да визуализираме потенциални гигабайта данни, като например данни с милиони мрежови точки в рамките на голяма симулация?
По време на работата ми по подобни проблеми за моя Магистърска теза , Влязох в контакт с Visualization Toolkit или VTK - мощна графична библиотека, специализирана за визуализация на данни.
В този урок ще дам кратко въведение в VTK и неговата архитектура на тръбопровода и ще продължа да обсъждам реалния пример за 3D визуализация, използвайки данни от симулиран флуид в работната помпа. Накрая ще изброя силните страни на библиотеката, както и слабите места, които срещнах.
Библиотеката с отворен код VTK съдържа солиден конвейер за обработка и изобразяване с много усъвършенствани алгоритми за визуализация. Възможностите му обаче не спират дотук, тъй като с течение на времето са добавени и алгоритми за обработка на изображения и мрежи. В настоящия си проект с компания за стоматологични изследвания използвам VTK за мрежови задачи за обработка в рамките на Qt базирано на CAD-подобно приложение. The VTK казуси показват широката гама от подходящи приложения.
Архитектурата на VTK се върти около мощна концепция на тръбопровода. Основното очертание на тази концепция е показано тук:
vtkConeSource
създава 3D конус и vtkSTLReader
чете *.stl
3D геометрични файлове.vtkCutter
изрязва изхода на предишния обект в алгоритмите, използвайки неявна функция, например равнина. Всички алгоритми за обработка, които се доставят с VTK, са внедрени като филтри и могат да бъдат свободно свързани помежду си.Типичният конвейер за рендиране на VTK започва с един или повече източници, обработва ги с помощта на различни филтри в няколко изходни обекта, които след това се изобразяват отделно с помощта на картографи и актьори. Силата зад тази концепция е механизмът за актуализация. Ако настройките на филтрите или източниците се променят, всички зависими филтри, картографи, актьори и прозорци за рендиране се актуализират автоматично. Ако, от друга страна, обект по-надолу по тръбопровода се нуждае от информация, за да изпълнява задачите си, той може лесно да я получи.
Освен това няма нужда да се занимавате директно със системи за рендиране като OpenGL. VTK капсулира всички задачи от ниско ниво по независим от платформата и (частично) визуализиране начин; разработчикът работи на много по-високо ниво.
Нека да разгледаме пример за визуализация на данни, използващ набор от данни за потока на флуида във въртяща се помпа на работното колело от IEEE Visualization Contest 2011. Самите данни са резултат от изчислителна симулация на динамика на течността, подобно на тази, описана в статията на Чарлз Кук.
Данните за симулация с цип на представената помпа са с размер над 30 GB. Той съдържа множество части и множество времеви стъпки, следователно големият размер. В това ръководство ще си поиграем с роторната част на един от тези времеви стъпки, която има компресиран размер от около 150 MB.
полуконтролирано обучение дълбоко учене
Избраният от мен език за използване на VTK е C ++, но има съпоставяния за няколко други езика като Tcl / Tk, Java и Python. Ако целта е само визуализация на един набор от данни, изобщо не е необходимо да се пише код и вместо това може да се използва Paraview , графичен интерфейс за по-голямата част от функционалността на VTK.
Извадих роторния набор от данни от 30 GB набор от данни, предоставен по-горе, като отворих една стъпка от време в Paraview и извадих роторната част в отделен файл. Това е неструктуриран мрежов файл, т.е. 3D обем, състоящ се от точки и 3D клетки, като хексаедри, тетраедри и т.н. Всяка от 3D точките има свързани стойности. Понякога клетките също имат свързани стойности, но не и в този случай. Това обучение ще се концентрира върху натиска и скоростта в точките и ще се опита да ги визуализира в техния 3D контекст.
Размерът на компресирания файл е около 150 MB, а размерът в паметта е около 280 MB, когато се зарежда с VTK. Чрез обработката му във VTK наборът от данни се кешира няколко пъти в рамките на VTK тръбопровода и бързо достигаме ограничението от 2 GB памет за 32-битови програми. Има начини да спестите памет при използване на VTK, но за да бъде опростено, просто ще компилираме и стартираме примера в 64bit.
c corp срещу корпоративно данъчно облагане
Благодарности : Наборът от данни се предоставя с любезното съдействие на Института по приложна механика, Университет Клаустал, Германия (Dipl. Wirtsch.-Ing. Andreas Lucius).
Това, което ще постигнем, използвайки VTK като инструмент, е визуализацията, показана на изображението по-долу. Като 3D контекст контурът на набора от данни е показан с помощта на частично прозрачно изобразяване на каркасни рамки. След това лявата част от набора от данни се използва за показване на налягането, като се използва просто цветно кодиране на повърхностите. (Ще пропуснем по-сложното изобразяване на тома за този пример). За да визуализирате полето на скоростта, дясната част от набора от данни е изпълнена с рационализиране , които са цветно кодирани от величината на тяхната скорост. Този избор на визуализация технически не е идеален, но исках да запазя VTK кода възможно най-опростен. Освен това има причина този пример да бъде част от предизвикателство за визуализация, т.е. много турбулентност в потока.
Ще обсъдя VTK кода стъпка по стъпка, показвайки как би изглеждал изходът за рендиране на всеки етап. Пълният изходен код може да бъде изтеглен в края на обучението.
Нека започнем, като включим всичко, от което се нуждаем от VTK, и отворим основната функция.
#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) {
След това настройваме визуализатора и прозореца за изобразяване, за да покажем нашите резултати. Задаваме цвета на фона и размера на прозореца за изобразяване.
// 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);
С този код вече бихме могли да покажем статичен прозорец за рендиране. Вместо това избираме да добавим vtkRenderWindowInteractor
за интерактивно завъртане, мащабиране и панорамиране на сцената.
// Setup the render window interactor vtkNew interact; vtkNew style; interact->SetRenderWindow(renWin.Get()); interact->SetInteractorStyle(style.Get());
Сега имаме работещ пример, показващ сив, празен прозорец за рендиране.
След това зареждаме набора от данни, използвайки един от многото четци, които се доставят с VTK.
// Read the file vtkSmartPointer pumpReader = vtkSmartPointer::New(); pumpReader->SetFileName('rotor.vtu');
Кратка екскурзия в управлението на паметта VTK : VTK използва удобна концепция за автоматично управление на паметта, въртяща се около преброяването на референции. За разлика от повечето други реализации, броят на референциите се съхранява в самите обекти на VTK, вместо в класа на интелигентния указател. Това има предимството, че броят на референциите може да бъде увеличен, дори ако VTK обектът се предава като суров указател. Има два основни начина за създаване на управлявани VTK обекти. vtkNew
и vtkSmartPointer::New()
, като основната разлика е, че a vtkSmartPointer
е имплицитно приложимо към суровия указател T*
и може да бъде върнато от функция. За случаи на vtkNew
ще трябва да се обадим .Get()
за да получим суров указател и можем да го върнем само като го увием в vtkSmartPointer
. В рамките на нашия пример ние никога не се връщаме от функции и всички обекти живеят през цялото време, затова ще използваме краткото vtkNew
, само с горното изключение за демонстрационни цели.
Към този момент от файла все още не е прочетено нищо. Ние или филтър по-надолу по веригата ще трябва да извикаме Update()
за да се случи четенето на файла. Обикновено най-добрият подход е да оставите VTK класовете да се справят сами с актуализациите. Понякога обаче искаме да получим директен достъп до резултата от даден филтър, например за да получим обхвата на наляганията в този набор от данни. След това трябва да се обадим Update()
ръчно. (Не губим производителност, като извикваме Update()
няколко пъти, тъй като резултатите се кешират.)
// Get the pressure range pumpReader->Update(); double pressureRange[2]; pumpReader->GetOutput()->GetPointData()->GetArray('Pressure')->GetRange(pressureRange);
След това трябва да извлечем лявата половина от набора от данни, като използваме vtkClipDataSet
За да постигнем това, първо дефинираме vtkPlane
което определя разделението. След това ще видим за първи път как VTK тръбопроводът е свързан заедно: successor->SetInputConnection(predecessor->GetOutputPort())
Винаги, когато поискаме актуализация от clipperLeft
тази връзка вече ще гарантира, че всички предходни филтри също са актуални.
// 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());
И накрая, ние създаваме нашите първи актьори и картографи, за да покажем каркасното изобразяване на лявата половина. Забележете, че картографът е свързан към своя филтър точно по същия начин като филтрите един към друг. По-голямата част от времето самият визуализатор задейства актуализациите на всички участници, картографи и основните филтърни вериги!
Единственият ред, който не е обяснителен, може би е leftWireMapper->ScalarVisibilityOff();
- забранява оцветяването на телената рамка чрез стойности на налягането, които са зададени като активния в момента масив.
// 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());
В този момент прозорецът за рендиране най-накрая показва нещо, т.е. каркаса за лявата част.
колко шрифта има
Рендерирането на каркасна рамка за дясната част се създава по подобен начин, чрез превключване на равнината нормална на a (новосъздадена) vtkClipDataSet
в обратна посока и леко променящ цвета и непрозрачността на (новосъздадения) картограф и актьор. Забележете, че тук нашият VTK тръбопровод се разделя в две посоки (вдясно и вляво) от един и същ набор от данни.
// 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());
Изходният прозорец сега показва и двете части на каркаса, както се очаква.
Сега сме готови да визуализираме някои полезни данни! За да добавим визуализацията на налягането в лявата част, не е нужно да правим много. Създаваме нов картограф и го свързваме с clipperLeft
също, но този път оцветяваме чрез масива под налягане. Също така тук най-накрая използваме pressureRange
извлекли сме по-горе.
// 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());
Резултатът сега изглежда като изображението, показано по-долу. Налягането в средата е много ниско, всмуква материал в помпата. След това този материал се транспортира навън, бързо набирайки налягане. (Разбира се, трябва да има легенда за цветна карта с действителните стойности, но я оставих, за да запазя примера по-кратък.)
Сега започва по-сложната част. Искаме да нарисуваме скоростни потоци в дясната част. Потоците се генерират чрез интегриране в векторно поле от изходни точки. Векторното поле вече е част от набора от данни под формата на векторния масив „Скорости“. Така че трябва само да генерираме изходните точки. vtkPointSource
генерира сфера от случайни точки. Ще генерираме 1500 източници, тъй като повечето от тях така или иначе няма да се намират в набора от данни и ще бъдат игнорирани от трасиращия поток.
// 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);
След това създаваме streamtracer и задаваме неговите входни връзки. 'Изчакайте, многократни връзки? ”, може да се каже. Да - това е първият VTK филтър с множество входове, които срещаме. Нормалната входна връзка се използва за векторното поле, а връзката източник се използва за началните точки. Тъй като „Velocities“ е „активният“ векторен масив в clipperRight
, не е необходимо да го посочваме тук изрично. Накрая уточняваме, че интегрирането трябва да се извършва в двете посоки от началните точки и да зададем метода на интеграция на Рунге-Кута-4.5 .
vtkNew tracer; tracer->SetInputConnection(clipperRight->GetOutputPort()); tracer->SetSourceConnection(pointSource->GetOutputPort()); tracer->SetIntegrationDirectionToBoth(); tracer->SetIntegratorTypeToRungeKutta45();
Следващият ни проблем е оцветяването на потоците с величина на скоростта. Тъй като няма масив за величините на векторите, ние просто ще изчислим величините в нов скаларен масив. Както се досещате, за тази задача има и VTK филтър: vtkArrayCalculator
. Той взема набор от данни и го извежда непроменен, но добавя точно един масив, който се изчислява от един или повече от съществуващите. Конфигурираме този калкулатор на масиви да приема величината на вектора „Скорост“ и да го извежда като „MagVelocity“. Накрая извикваме Update()
отново ръчно, за да се извлече обхватът на новия масив.
// 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
директно извежда полилинии и vtkArrayCalculator
предава ги непроменени. Следователно можем просто да покажем изхода на magCalc
директно с помощта на нов картограф и актьор.
Вместо това, в това обучение ние избираме да направим изхода малко по-хубав, като показваме ленти вместо това. vtkRibbonFilter
генерира 2D клетки за показване на панделки за всички полилинии от неговия вход.
// 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());
Това, което сега все още липсва и всъщност е необходимо, за да се получат и междинните визуализации, са последните пет реда за рендиране на сцената и инициализиране на интерактора.
// Render and show interactive window renWin->Render(); interact->Initialize(); interact->Start(); return 0; }
Накрая стигаме до завършената визуализация, която ще представя още веднъж тук:
получаване и преобразуване (захранване на запитване)
Пълният изходен код за горната визуализация може да бъде намерен тук .
Ще затворя тази статия със списък с моите лични плюсове и минуси на рамката VTK.
За : Активно развитие : VTK е в процес на активно развитие от няколко участници, главно от изследователската общност. Това означава, че са налични някои авангардни алгоритми, много 3D-формати могат да бъдат импортирани и експортирани, грешките са активно отстранени и проблемите обикновено имат готово решение в дискусионните дъски.
С : Надеждност : Свързването на много алгоритми от различни сътрудници с отворения дизайн на тръбопровода на VTK обаче може да доведе до проблеми с необичайни комбинации от филтри. Трябваше да вляза в изходния код на VTK няколко пъти, за да разбера защо моята сложна филтърна верига не дава желаните резултати. Силно препоръчвам да настроите VTK по начин, който позволява отстраняване на грешки.
За : Софтуерна архитектура : Дизайнът на тръбопровода и общата архитектура на VTK изглежда добре обмислен и е удоволствие да се работи с него. Няколко реда на кода могат да доведат до невероятни резултати. Вградените структури от данни са лесни за разбиране и използване.
С : Микро архитектура : Някои решения за микроархитектурен дизайн избягват моето разбиране. Конст-коректността почти не съществува, масивите се предават като входове и изходи без ясна разлика. Облекчих това за собствените си алгоритми, като се отказах от някаква производителност и използвах собствената си обвивка за vtkMath
който използва персонализирани 3D типове като typedef std::array Pnt3d;
.
За : Микродокументация : Документовата документация за всички класове и филтри е обширна и използваема, примерите и тестовите случаи в wiki също са чудесна помощ за разбиране на начина на използване на филтрите.
С : Макро документация : В мрежата има няколко добри урока за и въведения за VTK. Доколкото знам обаче, няма голяма справочна документация, която да обяснява как се правят конкретни неща. Ако искате да направите нещо ново, очаквайте да потърсите как да го направите известно време. Освен това е трудно да се намери конкретният филтър за дадена задача. След като го намерите обаче, документацията за кислорода обикновено е достатъчна. Един добър начин за изследване на VTK рамката е да изтеглите и експериментирате с Paraview.
За : Имплицитна подкрепа за паралелизиране : Ако вашите източници могат да бъдат разделени на няколко части, които могат да бъдат обработени независимо, успоредяването е толкова просто, колкото създаването на отделна верига от филтри във всяка нишка, която обработва една част. Повечето големи проблеми с визуализацията обикновено попадат в тази категория.
С : Няма изрична поддръжка за паралелизиране : Ако не сте благословени с големи, делими проблеми, но искате да използвате множество ядра, вие сте сами. Ще трябва да разберете кои класове са безопасни за нишки или дори да се включите отново чрез проби и грешки или като прочетете източника. Веднъж проследих проблема с паралелизацията до VTK филтър, който използва статична глобална променлива, за да извика някаква C библиотека.
За : Buildsystem CMake : Мултиплатформената мета-система за изграждане CMake също се разработва от Kitware (създателите на VTK) и се използва в много проекти извън Kitware. Той се интегрира много добре с VTK и прави създаването на система за изграждане на множество платформи много по-малко болезнено.
За : Независимост на платформата, лиценз и дълголетие : VTK е независима от платформата и е лицензирана под много разрешителен лиценз в стил BSD . Освен това се предлага професионална подкрепа за онези важни проекти, които изискват това. Kitware е подкрепен от много изследователски организации и други компании и ще съществува известно време.
Като цяло VTK е най-добрият инструмент за визуализация на данни за видовете проблеми, които обичам. Ако някога попаднете на проект, който изисква визуализация, обработка на мрежи, обработка на изображения или подобни задачи, опитайте да задействате Paraview с входен пример и преценете дали VTK може да бъде инструментът за вас.