การสร้างภาพข้อมูล 3 มิติด้วยเครื่องมือโอเพ่นซอร์ส: บทช่วยสอนโดยใช้ VTK
เผยแพร่แล้ว: 2022-03-11ในบทความล่าสุดของเขาในบล็อกของ Toptal นักวิทยาศาสตร์ด้านข้อมูลที่มีฝีมือ Charles Cook เขียนเกี่ยวกับการคำนวณทางวิทยาศาสตร์ด้วยเครื่องมือโอเพ่นซอร์ส บทช่วยสอนของเขาเป็นจุดสำคัญเกี่ยวกับเครื่องมือโอเพนซอร์สและบทบาทที่พวกเขาสามารถใช้ในการประมวลผลข้อมูลและการรับผลลัพธ์ได้อย่างง่ายดาย
แต่ทันทีที่เราแก้สมการเชิงอนุพันธ์เชิงซ้อนเหล่านี้หมด ปัญหาก็ปรากฏขึ้นอีก เราจะเข้าใจและตีความข้อมูลจำนวนมหาศาลที่ออกมาจากการจำลองเหล่านี้ได้อย่างไร เราจะเห็นภาพข้อมูลที่เป็นไปได้ในระดับกิกะไบต์ เช่น ข้อมูลที่มีจุดกริดหลายล้านจุดภายในการจำลองขนาดใหญ่ได้อย่างไร
ระหว่างที่ฉันทำงานเกี่ยวกับปัญหาที่คล้ายคลึงกันสำหรับวิทยานิพนธ์ระดับปริญญาโทของฉัน ฉันได้ติดต่อกับ Visualization Toolkit หรือ VTK ซึ่งเป็นไลบรารีกราฟิกที่ทรงพลังซึ่งเชี่ยวชาญด้านการสร้างภาพข้อมูลโดยเฉพาะ
ในบทช่วยสอนนี้ ฉันจะแนะนำอย่างรวดเร็วเกี่ยวกับ VTK และสถาปัตยกรรมไปป์ไลน์ของมัน และพูดถึงตัวอย่างการสร้างภาพ 3 มิติในชีวิตจริงโดยใช้ข้อมูลจากของไหลจำลองในปั๊มใบพัด สุดท้าย ฉันจะแสดงรายการจุดแข็งของห้องสมุด รวมถึงจุดอ่อนที่ฉันพบ
การแสดงข้อมูลและไปป์ไลน์ VTK
ไลบรารีโอเพนซอร์ส VTK มีขั้นตอนการประมวลผลและการเรนเดอร์ที่มั่นคงพร้อมอัลกอริธึมการแสดงภาพที่ซับซ้อนจำนวนมาก อย่างไรก็ตาม ความสามารถดังกล่าวไม่ได้หยุดอยู่แค่นั้น เนื่องจากมีการเพิ่มอัลกอริธึมการประมวลผลภาพและเมชเมื่อเวลาผ่านไปด้วย ในโครงการปัจจุบันของฉันกับบริษัทวิจัยด้านทันตกรรม ฉันใช้ VTK สำหรับงานประมวลผลแบบตาข่ายภายในแอปพลิเคชันที่เหมือน CAD แบบ Qt กรณีศึกษา VTK แสดงการใช้งานที่เหมาะสมที่หลากหลาย
สถาปัตยกรรมของ VTK หมุนรอบแนวคิดไปป์ไลน์ที่ทรงพลัง โครงร่างพื้นฐานของแนวคิดนี้แสดงไว้ที่นี่:
- แหล่งที่มา อยู่ที่จุดเริ่มต้นของไปป์ไลน์และสร้าง "บางสิ่งที่ว่างเปล่า" ตัวอย่างเช่น
vtkConeSource
สร้างกรวย 3 มิติ และvtkSTLReader
จะอ่านไฟล์เรขาคณิต 3 มิติ*.stl
- ตัวกรอง แปลงเอาต์พุตของแหล่งที่มาหรือตัวกรองอื่นๆ ให้เป็นสิ่งใหม่ ตัวอย่างเช่น
vtkCutter
ตัดเอาต์พุตของอ็อบเจ็กต์ก่อนหน้าในอัลกอริธึมโดยใช้ฟังก์ชันโดยปริยาย เช่น ระนาบ อัลกอริธึมการประมวลผลทั้งหมดที่มาพร้อมกับ VTK ถูกนำมาใช้เป็นตัวกรองและสามารถเชื่อมโยงเข้าด้วยกันได้อย่างอิสระ - Mappers แปลงข้อมูลเป็นกราฟิกดั้งเดิม ตัวอย่างเช่น สามารถใช้เพื่อระบุตารางค้นหาสำหรับระบายสีข้อมูลทางวิทยาศาสตร์ เป็นวิธีที่เป็นนามธรรมในการระบุสิ่งที่จะแสดง
- นักแสดง เป็นตัวแทนของวัตถุ (เรขาคณิตพร้อมคุณสมบัติการแสดงผล) ภายในฉาก มีการระบุสิ่งต่างๆ เช่น สี ความทึบ การแรเงา หรือการวางแนวไว้ที่นี่
- ในที่สุด Renderers & Windows ก็อธิบายการเรนเดอร์บนหน้าจอด้วยวิธีที่ไม่ขึ้นกับแพลตฟอร์ม
ไปป์ไลน์การเรนเดอร์ VTK ทั่วไปเริ่มต้นด้วยแหล่งที่มาอย่างน้อยหนึ่งแหล่ง ประมวลผลโดยใช้ตัวกรองต่างๆ ลงในอ็อบเจ็กต์เอาต์พุตหลายรายการ จากนั้นจึงแสดงผลแยกกันโดยใช้ตัวแมปและตัวแสดง พลังที่อยู่เบื้องหลังแนวคิดนี้คือกลไกการอัปเดต หากการตั้งค่าของตัวกรองหรือแหล่งที่มามีการเปลี่ยนแปลง ตัวกรองที่เกี่ยวข้อง ผู้ทำแผนที่ นักแสดง และหน้าต่างการเรนเดอร์ทั้งหมดจะได้รับการอัปเดตโดยอัตโนมัติ ในทางกลับกัน หากอ็อบเจ็กต์ที่อยู่ไกลออกไปในไปป์ไลน์ต้องการข้อมูลเพื่อทำงาน วัตถุนั้นก็สามารถได้รับมาโดยง่าย
นอกจากนี้ ไม่จำเป็นต้องจัดการกับระบบการเรนเดอร์อย่าง OpenGL โดยตรง VTK สรุปงานระดับต่ำทั้งหมดในรูปแบบแพลตฟอร์มและ (บางส่วน) การแสดงผลที่ไม่ขึ้นกับระบบ นักพัฒนาทำงานในระดับที่สูงขึ้นมาก
ตัวอย่างโค้ดพร้อมชุดข้อมูลปั๊มโรเตอร์
มาดูตัวอย่างการสร้างภาพข้อมูลโดยใช้ชุดข้อมูลของการไหลของของไหลในปั๊มใบพัดแบบหมุนจาก IEEE Visualization Contest 2011 ข้อมูลดังกล่าวเป็นผลมาจากการจำลองไดนามิกของไหลเชิงคำนวณ เช่นเดียวกับที่อธิบายไว้ในบทความของ Charles Cook
ข้อมูลการจำลองแบบซิปของปั๊มที่แนะนำนั้นมีขนาดมากกว่า 30 GB ประกอบด้วยหลายส่วนและหลายขั้นตอน จึงมีขนาดใหญ่ ในคู่มือนี้ เราจะลองใช้ส่วนโรเตอร์ของหนึ่งในขั้นตอนเหล่านี้ ซึ่งมีขนาดบีบอัดประมาณ 150 MB
ภาษาที่ฉันเลือกใช้ VTK คือ C++ แต่มีการแมปสำหรับภาษาอื่นๆ อีกหลายภาษา เช่น Tcl/Tk, Java และ Python หากเป้าหมายเป็นเพียงการแสดงภาพชุดข้อมูลชุดเดียว ก็ไม่จำเป็นต้องเขียนโค้ดเลย และสามารถใช้ Paraview ซึ่งเป็นส่วนหน้าแบบกราฟิกสำหรับฟังก์ชันส่วนใหญ่ของ VTK ได้
ชุดข้อมูลและเหตุใดจึงจำเป็นต้องใช้ 64-บิต
ฉันแยกชุดข้อมูลโรเตอร์จากชุดข้อมูล 30 GB ที่ให้ไว้ด้านบน โดยเปิดขั้นตอนเดียวใน Paraview และแยกส่วนโรเตอร์ออกเป็นไฟล์แยกต่างหาก เป็นไฟล์กริดที่ไม่มีโครงสร้าง เช่น โวลุ่ม 3D ที่ประกอบด้วยจุดและเซลล์ 3 มิติ เช่น hexahedra, tetrahedra และอื่นๆ จุด 3 มิติแต่ละจุดมีค่าที่เกี่ยวข้อง บางครั้งเซลล์ก็มีค่าที่เกี่ยวข้องเช่นกัน แต่ไม่ใช่ในกรณีนี้ การฝึกอบรมนี้จะเน้นที่ความกดดันและความเร็ว ณ จุดต่างๆ และพยายามนึกภาพสิ่งเหล่านี้ในบริบท 3 มิติ
ขนาดไฟล์บีบอัดประมาณ 150 MB และขนาดในหน่วยความจำประมาณ 280 MB เมื่อโหลดด้วย VTK อย่างไรก็ตาม ด้วยการประมวลผลใน VTK ชุดข้อมูลจะถูกแคชหลายครั้งภายในไปป์ไลน์ VTK และเราถึงขีดจำกัดหน่วยความจำ 2 GB อย่างรวดเร็วสำหรับโปรแกรม 32 บิต มีวิธีบันทึกหน่วยความจำเมื่อใช้ VTK แต่เพื่อให้ง่าย เราจะรวบรวมและเรียกใช้ตัวอย่างใน 64 บิต
กิตติกรรมประกาศ : ชุดข้อมูลนี้จัดทำขึ้นโดยได้รับความอนุเคราะห์จาก Institute of Applied Mechanics, Clausthal University, Germany (Dipl. Wirtsch.-Ing. Andreas Lucius)
เป้าหมาย
สิ่งที่เราจะทำได้สำเร็จโดยใช้ VTK เป็นเครื่องมือคือการแสดงภาพที่แสดงในภาพด้านล่าง ในบริบท 3 มิติ โครงร่างของชุดข้อมูลจะแสดงโดยใช้การเรนเดอร์เฟรมแบบโปร่งใสบางส่วน จากนั้นใช้ส่วนด้านซ้ายของชุดข้อมูลเพื่อแสดงแรงกดโดยใช้รหัสสีอย่างง่ายของพื้นผิว (เราจะข้ามการแสดงปริมาณที่ซับซ้อนมากขึ้นสำหรับตัวอย่างนี้) เพื่อให้เห็นภาพสนามความเร็ว ส่วนด้านขวาของชุดข้อมูลจะเต็มไปด้วยความคล่องตัว ซึ่งกำหนดรหัสสีตามขนาดของความเร็ว ตัวเลือกการสร้างภาพข้อมูลนี้ไม่เหมาะในทางเทคนิค แต่ฉันต้องการเก็บโค้ด VTK ให้เรียบง่ายที่สุด นอกจากนี้ ยังมีเหตุผลที่ตัวอย่างนี้เป็นส่วนหนึ่งของความท้าทายในการมองเห็น กล่าวคือ มีความปั่นป่วนมากมายในการไหล
เป็นขั้นเป็นตอน
ฉันจะพูดถึงโค้ด VTK ทีละขั้นตอนโดยแสดงให้เห็นว่าเอาต์พุตการแสดงผลจะมีลักษณะอย่างไรในแต่ละขั้นตอน สามารถดาวน์โหลดซอร์สโค้ดแบบเต็มได้เมื่อสิ้นสุดการฝึกอบรม
เริ่มต้นด้วยการรวมทุกอย่างที่เราต้องการจาก VTK และเปิดฟังก์ชันหลัก
#include <vtkActor.h> #include <vtkArrayCalculator.h> #include <vtkCamera.h> #include <vtkClipDataSet.h> #include <vtkCutter.h> #include <vtkDataSetMapper.h> #include <vtkInteractorStyleTrackballCamera.h> #include <vtkLookupTable.h> #include <vtkNew.h> #include <vtkPlane.h> #include <vtkPointData.h> #include <vtkPointSource.h> #include <vtkPolyDataMapper.h> #include <vtkProperty.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkRibbonFilter.h> #include <vtkStreamTracer.h> #include <vtkSmartPointer.h> #include <vtkUnstructuredGrid.h> #include <vtkXMLUnstructuredGridReader.h> int main(int argc, char** argv) {
ต่อไป เราตั้งค่าตัวแสดงภาพและหน้าต่างการเรนเดอร์เพื่อแสดงผลลัพธ์ของเรา เราตั้งค่าสีพื้นหลังและขนาดหน้าต่างแสดงผล
// Setup the renderer vtkNew<vtkRenderer> renderer; renderer->SetBackground(0.9, 0.9, 0.9); // Setup the render window vtkNew<vtkRenderWindow> renWin; renWin->AddRenderer(renderer.Get()); renWin->SetSize(500, 500);
ด้วยรหัสนี้ เราสามารถแสดงหน้าต่างการแสดงผลแบบคงที่ได้แล้ว แต่เราเลือกที่จะเพิ่ม vtkRenderWindowInteractor
เพื่อหมุน ซูม และเลื่อนฉากแบบโต้ตอบ
// Setup the render window interactor vtkNew<vtkRenderWindowInteractor> interact; vtkNew<vtkInteractorStyleTrackballCamera> style; interact->SetRenderWindow(renWin.Get()); interact->SetInteractorStyle(style.Get());
ตอนนี้เรามีตัวอย่างที่แสดงหน้าต่างการเรนเดอร์ว่างเปล่าสีเทา
ต่อไป เราโหลดชุดข้อมูลโดยใช้โปรแกรมอ่านที่มาพร้อมกับ VTK ตัวใดตัวหนึ่ง
// Read the file vtkSmartPointer<vtkXMLUnstructuredGridReader> pumpReader = vtkSmartPointer<vtkXMLUnstructuredGridReader>::New(); pumpReader->SetFileName("rotor.vtu");
การเดินทางระยะสั้นสู่การจัดการหน่วยความจำ VTK : VTK ใช้แนวคิดการจัดการหน่วยความจำอัตโนมัติที่สะดวกสบายซึ่งหมุนเวียนไปรอบ ๆ การนับอ้างอิง อย่างไรก็ตาม ต่างจากการใช้งานอื่นๆ ส่วนใหญ่ จำนวนอ้างอิงจะถูกเก็บไว้ในอ็อบเจ็กต์ VTK เอง แทนที่จะเป็นคลาสพอยน์เตอร์อัจฉริยะ สิ่งนี้มีประโยชน์ที่สามารถเพิ่มจำนวนการอ้างอิงได้ แม้ว่าอ็อบเจ็กต์ VTK จะถูกส่งผ่านเป็นตัวชี้แบบดิบ มีสองวิธีหลักในการสร้างอ็อบเจ็กต์ VTK ที่มีการจัดการ vtkNew<T>
และ vtkSmartPointer<T>::New()
โดยมีความแตกต่างที่สำคัญคือ vtkSmartPointer<T>
สามารถส่งโดยปริยายไปยังตัวชี้แบบดิบ T*
และสามารถส่งคืนได้จากฟังก์ชัน สำหรับอินสแตนซ์ของ vtkNew<T>
เราจะต้องเรียก .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<vtkPlane> planeLeft; planeLeft->SetOrigin(0.0, 0.0, 0.0); planeLeft->SetNormal(-1.0, 0.0, 0.0); vtkNew<vtkClipDataSet> clipperLeft; clipperLeft->SetInputConnection(pumpReader->GetOutputPort()); clipperLeft->SetClipFunction(planeLeft.Get());
สุดท้าย เราสร้างนักแสดงและผู้ทำแผนที่กลุ่มแรกของเราเพื่อแสดงการเรนเดอร์โครงลวดของครึ่งซ้าย โปรดสังเกตว่าผู้ทำแผนที่เชื่อมต่อกับตัวกรองในลักษณะเดียวกับตัวกรองที่เชื่อมต่อกัน โดยส่วนใหญ่แล้ว ตัวแสดงภาพจะกระตุ้นให้มีการอัพเดทของนักแสดง ผู้ทำแผนที่ และตัวกรองที่เกี่ยวข้องทั้งหมด!
บรรทัดเดียวที่ไม่อธิบายตัวเองน่าจะเป็น leftWireMapper->ScalarVisibilityOff();
- ห้ามมิให้สีของโครงลวดด้วยค่าแรงกด ซึ่งกำหนดเป็นอาร์เรย์ที่ใช้งานอยู่ในปัจจุบัน
// Create the wireframe representation for the left part vtkNew<vtkDataSetMapper> leftWireMapper; leftWireMapper->SetInputConnection(clipperLeft->GetOutputPort()); leftWireMapper->ScalarVisibilityOff(); vtkNew<vtkActor> 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());
ณ จุดนี้ ในที่สุดหน้าต่างการเรนเดอร์ก็แสดงบางอย่าง กล่าวคือ โครงร่างสำหรับส่วนด้านซ้าย

การเรนเดอร์โครงลวดสำหรับส่วนที่ถูกต้องนั้นถูกสร้างขึ้นในลักษณะเดียวกัน โดยเปลี่ยนระนาบปกติของ vtkClipDataSet
(ที่สร้างขึ้นใหม่) ไปในทิศทางตรงกันข้ามและเปลี่ยนสีและความทึบเล็กน้อยของผู้ทำแผนที่และนักแสดง (ที่สร้างขึ้นใหม่) โปรดสังเกตว่าที่นี่ไปป์ไลน์ VTK ของเราแบ่งออกเป็นสองทิศทาง (ขวาและซ้าย) จากชุดข้อมูลอินพุตเดียวกัน
// Clip the right part from the input vtkNew<vtkPlane> planeRight; planeRight->SetOrigin(0.0, 0.0, 0.0); planeRight->SetNormal(1.0, 0.0, 0.0); vtkNew<vtkClipDataSet> clipperRight; clipperRight->SetInputConnection(pumpReader->GetOutputPort()); clipperRight->SetClipFunction(planeRight.Get()); // Create the wireframe representation for the right part vtkNew<vtkDataSetMapper> rightWireMapper; rightWireMapper->SetInputConnection(clipperRight->GetOutputPort()); rightWireMapper->ScalarVisibilityOff(); vtkNew<vtkActor> 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());
หน้าต่างแสดงผลจะแสดงส่วนโครงลวดทั้งสองตามที่คาดไว้
ตอนนี้เราพร้อมที่จะเห็นภาพข้อมูลที่เป็นประโยชน์แล้ว! สำหรับการเพิ่มการแสดงภาพแรงดันในส่วนด้านซ้าย เราไม่ต้องทำอะไรมาก เราสร้าง mapper ใหม่และเชื่อมต่อกับ clipperLeft
เช่นกัน แต่คราวนี้เราระบายสีตามอาร์เรย์ความดัน มันยังอยู่ที่นี่ ซึ่งในที่สุดเราก็ใช้ช่วง pressureRange
ที่เราได้รับข้างต้น
// Create the pressure representation for the left part vtkNew<vtkDataSetMapper> pressureColorMapper; pressureColorMapper->SetInputConnection(clipperLeft->GetOutputPort()); pressureColorMapper->SelectColorArray("Pressure"); pressureColorMapper->SetScalarRange(pressureRange); vtkNew<vtkActor> pressureColorActor; pressureColorActor->SetMapper(pressureColorMapper.Get()); pressureColorActor->GetProperty()->SetOpacity(0.5); renderer->AddActor(pressureColorActor.Get());
ผลลัพธ์ตอนนี้ดูเหมือนภาพที่แสดงด้านล่าง แรงดันตรงกลางต่ำมากดูดวัสดุเข้าไปในปั๊ม จากนั้นวัสดุนี้จะถูกส่งออกไปด้านนอก และรับแรงกดอย่างรวดเร็ว (แน่นอนว่าควรมีคำอธิบายแผนที่สีพร้อมค่าจริง แต่ฉันปล่อยทิ้งไว้เพื่อให้ตัวอย่างสั้นลง)
ตอนนี้ส่วนที่ยากขึ้นเริ่มต้นขึ้น เราต้องการวาดเส้นความเร็วในส่วนที่ถูกต้อง Streamlines ถูกสร้างขึ้นโดยการผสานรวมภายในฟิลด์เวกเตอร์จากจุดต้นทาง ฟิลด์เวกเตอร์เป็นส่วนหนึ่งของชุดข้อมูลในรูปแบบของเวกเตอร์อาเรย์ “Velocities” แล้ว ดังนั้นเราต้องสร้างจุดต้นทางเท่านั้น vtkPointSource
สร้างทรงกลมของจุดสุ่ม เราจะสร้างจุดต้นทาง 1500 จุด เนื่องจากส่วนใหญ่จะไม่อยู่ในชุดข้อมูลอยู่แล้ว และจะถูกละเว้นโดยตัวติดตามสตรีม
// Create the source points for the streamlines vtkNew<vtkPointSource> pointSource; pointSource->SetCenter(0.0, 0.0, 0.015); pointSource->SetRadius(0.2); pointSource->SetDistributionToUniform(); pointSource->SetNumberOfPoints(1500);
ต่อไปเราจะสร้าง streamtracer และตั้งค่าการเชื่อมต่ออินพุต “เดี๋ยวก่อน การเชื่อมต่อ หลายจุด ?” คุณอาจจะพูด ใช่ นี่คือตัวกรอง VTK ตัวแรกที่มีอินพุตหลายตัวที่เราพบ การเชื่อมต่ออินพุตปกติใช้สำหรับฟิลด์เวกเตอร์ และการเชื่อมต่อต้นทางใช้สำหรับจุดเมล็ด เนื่องจาก “Velocities” เป็นอาร์เรย์เวกเตอร์ “active” ใน clipperRight
เราจึงไม่จำเป็นต้องระบุให้ชัดเจนที่นี่ สุดท้ายเราระบุว่าควรทำการรวมทั้งสองทิศทางจากจุดเมล็ดและตั้งค่าวิธีการรวมเป็น Runge-Kutta-4.5
vtkNew<vtkStreamTracer> tracer; tracer->SetInputConnection(clipperRight->GetOutputPort()); tracer->SetSourceConnection(pointSource->GetOutputPort()); tracer->SetIntegrationDirectionToBoth(); tracer->SetIntegratorTypeToRungeKutta45();
ปัญหาต่อไปของเราคือ การระบายสีคล่องตัวตามขนาดความเร็ว เนื่องจากไม่มีอาร์เรย์สำหรับขนาดของเวกเตอร์ เราก็แค่คำนวณขนาดให้เป็นอาร์เรย์สเกลาร์ใหม่ ตามที่คุณเดา มีตัวกรอง VTK สำหรับงานนี้เช่นกัน: vtkArrayCalculator
ใช้ชุดข้อมูลและเอาต์พุตไม่เปลี่ยนแปลง แต่เพิ่มเพียงหนึ่งอาร์เรย์ที่คำนวณจากอาร์เรย์ที่มีอยู่อย่างน้อยหนึ่งรายการ เรากำหนดค่าเครื่องคำนวณอาร์เรย์นี้เพื่อใช้ขนาดของเวกเตอร์ "Velocity" และส่งออกเป็น "MagVelocity" สุดท้าย เราเรียก Update()
ด้วยตนเองอีกครั้ง เพื่อหาช่วงของอาร์เรย์ใหม่
// Compute the velocity magnitudes and create the ribbons vtkNew<vtkArrayCalculator> 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<vtkRibbonFilter> ribbonFilter; ribbonFilter->SetInputConnection(magCalc->GetOutputPort()); ribbonFilter->SetWidth(0.0005); vtkNew<vtkPolyDataMapper> streamlineMapper; streamlineMapper->SetInputConnection(ribbonFilter->GetOutputPort()); streamlineMapper->SelectColorArray("MagVelocity"); streamlineMapper->SetScalarRange(magVelocityRange); vtkNew<vtkActor> streamlineActor; streamlineActor->SetMapper(streamlineMapper.Get()); renderer->AddActor(streamlineActor.Get());
สิ่งที่ขาดหายไปในขณะนี้ และจำเป็นจริงๆ ในการสร้างการเรนเดอร์ระดับกลางเช่นกัน คือห้าบรรทัดสุดท้ายในการเรนเดอร์ฉากและเริ่มต้นตัวโต้ตอบ
// Render and show interactive window renWin->Render(); interact->Initialize(); interact->Start(); return 0; }
ในที่สุด เราก็มาถึงการสร้างภาพข้อมูลเสร็จแล้ว ซึ่งผมจะนำเสนออีกครั้งที่นี่:
สามารถดูซอร์สโค้ดแบบเต็มสำหรับการแสดงภาพด้านบนได้ที่นี่
ความดีความเลวและความน่าเกลียด
ฉันจะปิดบทความนี้พร้อมรายการข้อดีและข้อเสียส่วนตัวของกรอบงาน VTK
Pro : Active development : VTK อยู่ระหว่างการพัฒนาเชิงรุกจากผู้ร่วมให้ข้อมูลหลายราย ส่วนใหญ่มาจากภายในชุมชนการวิจัย ซึ่งหมายความว่ามีอัลกอริธึมล้ำสมัยอยู่บ้าง สามารถนำเข้าและส่งออกรูปแบบ 3 มิติจำนวนมาก ข้อบกพร่องได้รับการแก้ไขอย่างแข็งขัน และปัญหามักจะมีวิธีแก้ปัญหาสำเร็จรูปในกระดานสนทนา
ข้อ เสีย : ความน่าเชื่อถือ : การรวมอัลกอริธึมจำนวนมากจากผู้ร่วมให้ข้อมูลต่างกันกับการออกแบบไปป์ไลน์แบบเปิดของ VTK อาจนำไปสู่ปัญหากับการรวมตัวกรองที่ผิดปกติ ฉันต้องเข้าไปที่ซอร์สโค้ด VTK สองสามครั้งเพื่อหาสาเหตุที่ห่วงโซ่ตัวกรองที่ซับซ้อนของฉันไม่ได้ผลลัพธ์ที่ต้องการ ฉันขอแนะนำอย่างยิ่งให้ตั้งค่า VTK ในลักษณะที่อนุญาตให้มีการดีบัก
Pro : สถาปัตยกรรมซอฟต์แวร์ : การออกแบบไปป์ไลน์และสถาปัตยกรรมทั่วไปของ VTK นั้นได้รับการพิจารณามาเป็นอย่างดีและยินดีที่ได้ร่วมงานด้วย โค้ดไม่กี่บรรทัดสามารถสร้างผลลัพธ์ที่น่าอัศจรรย์ได้ โครงสร้างข้อมูลในตัวนั้นง่ายต่อการเข้าใจและใช้งาน
Con : Micro Architecture : การตัดสินใจออกแบบสถาปัตยกรรมขนาดเล็กบางอย่างทำให้ฉันไม่เข้าใจ ความถูกต้องของ Const นั้นแทบจะไม่มีเลย อาร์เรย์จะถูกส่งต่อเป็นอินพุตและเอาต์พุตโดยไม่มีความแตกต่างที่ชัดเจน ฉันบรรเทาสิ่งนี้สำหรับอัลกอริธึมของฉันเองโดยเลิกใช้ประสิทธิภาพและใช้ wrapper ของฉันเองสำหรับ
vtkMath
ซึ่งใช้ประเภท 3D ที่กำหนดเอง เช่นtypedef std::array<double, 3> Pnt3d;
.Pro : Micro Documentation : เอกสาร Doxygen ของคลาสและตัวกรองทั้งหมดนั้นกว้างขวางและใช้งานได้ ตัวอย่างและกรณีทดสอบบน wiki ยังช่วยให้เข้าใจวิธีการใช้ตัวกรองได้ดี
Con : Macro Documentation : มีบทช่วยสอนดีๆ มากมายสำหรับและแนะนำ VTK บนเว็บ อย่างไรก็ตาม เท่าที่ฉันรู้ ไม่มีเอกสารอ้างอิงขนาดใหญ่ที่อธิบายวิธีการทำสิ่งที่เฉพาะเจาะจง หากคุณต้องการทำอะไรใหม่ๆ ให้คาดหวังที่จะค้นหาวิธีการทำสักระยะหนึ่ง นอกจากนี้ยังเป็นการยากที่จะค้นหาตัวกรองเฉพาะสำหรับงาน เมื่อคุณพบแล้ว เอกสาร Doxygen มักจะเพียงพอ วิธีที่ดีในการสำรวจเฟรมเวิร์ก VTK คือการดาวน์โหลดและทดลองกับ Paraview
Pro : Implicit Parallelization Support : หากแหล่งที่มาของคุณสามารถแบ่งออกเป็นหลายส่วนที่สามารถประมวลผลได้อย่างอิสระ Parallelization นั้นง่ายพอๆ กับการสร้างห่วงโซ่ตัวกรองแยกกันภายในแต่ละเธรดที่ประมวลผลส่วนเดียว ปัญหาการสร้างภาพข้อมูลขนาดใหญ่ส่วนใหญ่มักจัดอยู่ในหมวดหมู่นี้
Con : No Explicit Parallelization Support : หากคุณไม่ได้รับความสุขจากปัญหาที่ใหญ่และแบ่งได้ แต่คุณต้องการใช้หลายคอร์ คุณต้องจัดการเอง คุณจะต้องค้นหาว่าคลาสใดปลอดภัยต่อเธรด หรือแม้แต่เข้าร่วมใหม่โดยการลองผิดลองถูกหรือโดยการอ่านแหล่งที่มา ครั้งหนึ่งฉันเคยติดตามปัญหาการทำให้ขนานกันกับตัวกรอง VTK ที่ใช้ตัวแปรโกลบอลแบบคงที่เพื่อเรียกไลบรารี C บางตัว
Pro : Buildsystem CMake : CMake ระบบ meta-build-system แบบหลายแพลตฟอร์มได้รับการพัฒนาโดย Kitware (ผู้ผลิต VTK) และใช้ในหลายโครงการนอก Kitware มันรวมเข้ากับ VTK ได้อย่างลงตัวและทำให้การตั้งค่าระบบบิลด์สำหรับหลายแพลตฟอร์มเจ็บปวดน้อยกว่ามาก
Pro : Platform Independence, License, and Longevity : VTK เป็นแพลตฟอร์มที่เป็นอิสระจากกล่อง และได้รับอนุญาตภายใต้ใบอนุญาตแบบ BSD ที่อนุญาตอย่างมาก นอกจากนี้ยังมีการสนับสนุนอย่างมืออาชีพสำหรับโครงการสำคัญเหล่านั้นที่ต้องการ Kitware ได้รับการสนับสนุนจากหน่วยงานวิจัยและบริษัทอื่นๆ หลายแห่ง และจะเปิดให้บริการในระยะเวลาหนึ่ง
คำสุดท้าย
โดยรวมแล้ว VTK เป็นเครื่องมือสร้างภาพข้อมูลที่ดีที่สุดสำหรับปัญหาประเภทต่างๆ ที่ฉันชอบ หากคุณเคยเจอโปรเจ็กต์ที่ต้องใช้การแสดงภาพ การประมวลผลแบบตาข่าย การประมวลผลภาพ หรืองานที่คล้ายกัน ให้ลองใช้ Paraview ด้วยตัวอย่างอินพุตและประเมินว่า VTK สามารถเป็นเครื่องมือสำหรับคุณได้หรือไม่