VTK

معمارية تطبيقات المصادر المفتوحة (المجلد الأول)VTK

معمارية تطبيقات المصادر المفتوحة (المجلد الأول) VTK

Berk Geveci and Will Schroeder

إذا استمتعت بهذه الكتب، فقد يعجبك أيضًا التصميم البرمجي بالأمثلة في Python والتصميم البرمجي بالأمثلة في JavaScript.

إنّ حزمة أدوات التصوير المرئي (Visualization Toolkit، أو VTK) هي نظام برمجي واسع الاستخدام في معالجة البيانات وتصويرها. ويُستخدَم في الحوسبة العلمية، وتحليل الصور الطبية، والهندسة الحاسوبية، والعرض (rendering)، ومعالجة الصور، والمعلوماتية. وفي هذا الفصل نقدّم عرضًا موجزًا لـ VTK، بما في ذلك بعض أنماط التصميم الأساسية (design patterns) التي جعلت منه نظامًا ناجحًا.

لفهم نظام برمجي فهمًا حقيقيًا، من الضروري ألّا نفهم المشكلة التي يحلّها فحسب، بل أيضًا الثقافة الخاصة التي نشأ فيها. وفي حالة VTK، طُوِّر البرنامج ظاهريًا بوصفه نظام عرض ثلاثي الأبعاد للبيانات العلمية. لكنّ السياق الثقافي الذي نشأ فيه يضيف قصّة خلفية جوهرية إلى هذا المسعى، ويساعد في تفسير سبب تصميم البرنامج وإطلاقه على النحو الذي كان عليه.

في الوقت الذي أُطِّلع فيه على VTK وكُتب، كان مؤلِّفوه الأوائل (Will Schroeder وKen Martin وBill Lorensen) باحثين في GE Corporate R&D. وقد كنا نستثمر كثيرًا في نظام سابق يُعرف بـ LYMB، وهو بيئة شبيهة بـ Smalltalk نُفِّذت بلغة البرمجة C. وفيما كان نظامًا رائعًا لحينه، ظلّينا كباحثين نواجه باستمرار عقبتين رئيسيتين عند محاولة الترويج لعملنا: 1) مسائل الملكية الفكرية (IP)، و2) برمجيات مملوكة غير قياسية. وكانت مسائل الملكية الفكرية عقبةً لأنّ محاولة توزيع البرنامج خارج GE صارت شبه مستحيلة بمجرّد تدخّل محامي الشركة. وثانيًا، حتى لو كنّا ننشر البرنامج داخل GE، تردّد كثير من عملائنا في تعلّم نظام مملوك غير قياسي، لأنّ الجهد المبذول لإتقانه لا ينتقل مع الموظّفة عند مغادرتها الشركة، كما أنه لا يتمتّع بدعم واسع من مجموعة أدوات قياسية. وهكذا فإنّ الدافع الأساسي لـ VTK في النهاية كان تطوير معيار مفتوح، أو منصّة تعاون (collaboration platform) ننتقل عبرها التقنية إلى عملائنا بسهولة. ولهذا فإنّ اختيار رخصة مفتوحة المصدر لـ VTK كان على الأرجح أهمّ قرار تصميم اتخذناه.

وكان اختيار رخصة متساهلة (permissive) غير متبادلية (non-reciprocal) في النهاية (أي BSD لا GPL) قرارًا نموذجيًا اتخذه المؤلّفون إذا نظرنا إليه في الأثر الرجعي، لأنه مكّن في النهاية من ظهور الأعمال القائمة على الخدمات والاستشارات التي تحوّلت إلى Kitware. وفي الوقت الذي اتّخذنا فيه هذا القرار، كنّا مهتمّين أساسًا لتقليل العوائق أمام التعاون مع الأكاديميين ومختبرات البحث والجهات التجارية. وقد اكتشّفنا منذ ذلك الحين أنّ كثيرًا من المؤسسات تتجنّب الرخص المتبادلية بسبب الفوضى المحتملة التي قد تثيرها. وفي الواقع نودّ أن نجادل بأنّ الرخص المتبادلية تبطئ قبول البرمجيات مفتوحة المصدر إلى حدٍّ كبير، لكنّ هذه مسألة تُحال إلى وقت آخر. والمقصود هنا هو: أحد القرارات التصميمية الكبرى الواجب اتّخاذه بالنسبة إلى أيّ نظام برمجي هو اختيار رخصة حقوق النشر. ومن المهمّ مراجعة أهداف المشروع ثمّ معالجة مسائل الملكية الفكرية على النحو المناسب.

24.1. ما هو VTK؟

صُوِّر VTK في الأصل بوصفه نظامًا لتصوير البيانات العلمية. ويظنّ كثيرون ممّن هم خارج هذا المجال، على نحو ساذج، أنّ التصوير (visualization) هو نوع معيّن من العرض الهندسي: فحص الأجسام الافتراضية والتفاعل معها. وفيما كان هذا جزءًا من التصوير فعلًا، فإنّ تصوير البيانات على نحو عام يشمل العملية الكاملة لتحويل البيانات إلى مُدخَلات حسّية، عادةً صورًا، لكنّه يشمل كذلك أشكال اللمس والسمع وغيرها. ولا تتكوّن صيغ البيانات من البنى الهندسية والطبولوجية فحسب، بما في ذلك تجريدات مثل الشبكات (meshes) أو التحليلات المكانية المعقّدة، بل أيضًا من سمات البنية الأساسية مثل السميات (scalars) (مثلًا، الحرارة أو الضغط)، والمتجهات (vectors) (مثلًا، السرعة)، والموتّرات (tensors) (مثلًا، الإجهاد والانفعال)، إضافةً إلى سمات العرض مثل أعمدة السطح (surface normals) وإحداثيات النسيج (texture coordinates).

لاحظ أنّ البيانات الممثِّلة لمعلومات مكانية-زمانية تُعدّ عمومًا جزءًا من التصوير العلمي. لكنّ هناك صيغ بيانات أكثر تجريدًا مثل الديموغرافيا التسويقية (marketing demographics) وصفحات الويب والمستندات وغيرها من المعلومات، التي لا يمكن تمثيلها إلا عبر علاقات تجريدية (أي غير مكانية زمانية) مثل المستندات غير المهيكلة والجداول والرسوم البيانية والأشجار. وتُعالَج هذه البيانات التجريدية عادةً بطرق تصوير المعلومات (information visualization). وبمساعدة المجتمع، أصبح VTK قادرًا الآن على كلٍّ من التصوير العلمي وتصوير المعلومات.

وبصفتهم نظام تصوير، فإنّ دور VTK هو أخذ البيانات بهذه الصيغ وتحويلها في النهاية إلى صيغ يستطيع الجهاز الحسيّ البشري فهمها. ومن ثمّ فإنّ أحد المتطلبات الأساسية لـ VTK هو قدرته على إنشاء خطوط أنابيب لتدفّق البيانات (data flow pipelines) قادرة على استيعاب البيانات ومعالجتها وتمثيلها ثمّ عرضها في النهاية. ومن هنا تُصمَّم الحزمة حتمًا بوصفها نظامًا مرنًا، ويعكس تصميمها ذلك على مستويات عديدة. فمثلًا، صمّمنا VTK عن قصد بوصفه حزمة أدوات ذات مكوّنات قابلة للاستبدال والتبادل يمكن دمجها لمعالجة مجموعة واسعة ومتنوّعة من البيانات.

24.2. الخصائص المعمارية

قبل التعمّق كثيرًا في الخصائص المعمارية المحدّدة لـ VTK، هناك مفاهيم عليا ذات تأثير كبير على تطوير النظام واستخدامه. ومن بينها مرفق التغليف الهجين (hybrid wrapper) الخاص بـ VTK. يولّد هذا المرفق تلقائيًا روابط لغوية (language bindings) إلى Python وJava وTcl من تنفيذ VTK المكتوب بـ C++ (وقد كان من الممكن إضافة لغات أخرى وقد أُضيفت فعلًا). وسيعمل معظم المطوّرين ذوي القدرات العالية بـ C++. وقد يستخدم مطوّرو المستخدمين والتطبيقات C++ أيضًا، لكنّ اللغات المُفسَّرة المذكورة أعلاه هي المفضّلة عادةً. وتجمع هذه البيئة الهجينة بين أفضل عالمَين: خوارزميات عالية الأداء مكثّفة الحوسبة، ومرونةٍ عند النمذجة الأولية (prototyping) أو تطوير التطبيقات. وفي الواقع لقي هذا الأسلوب في الحوسبة متعددة اللغات قبولًا واسعًا في مجتمع الحوسبة العلمية، وكثيرًا ما يستخدمون VTK بوصفه قالبًا لتطوير برمجياتهم الخاصة.

وفيما يخصّ عملية تطوير البرمجيات، تبنّى VTK أداة CMake لضبط عملية البناء، وCDash/CTest للاختبار، وCPack للنشر عبر المنصات. وبالفعل يمكن ترجمة VTK على أيّ حاسوب تقريبًا، بما في ذلك الحواسيب فائقة الأداء (supercomputers) التي غالبًا ما تكون بيئات تطوير بدائية إلى حدٍّ مؤسف. وإلى جانب ذلك، تكمل صفحات الويب والويكي وقوائم البريد (للمستخدمين والمطوّرين) ومرافق توليد التوثيق (أي Doxygen) ومتتبّع أخطاء (Mantis) أدوات التطوير.

24.2.1. الخصائص الأساسية

بما أنّ VTK نظام موجَّه نحو الكائنات، فإنّ الوصول إلى أعضاء بيانات الأصناف ونسخها محكوم في VTK بدقّة. وعلى العموم، يكون جميع أعضاء البيانات إما محميًّا (protected) أو خاصًّا (private). والوصول إليها يتم عبر طرقي Set وGet، مع تنويعات خاصة للبيانات المنطقية (Boolean) والبيانات النمطية (modal) والسلاسل النصية والمتجهات. وتُنشأ كثير من هذه الطرق فعليًا بإدراج ماكروهات (macros) في ملفات ترويسة الأصناف. فمثلًا:

vtkSetMacro(Tolerance,double);
vtkGetMacro(Tolerance,double);

تصبح عند توسيعها:

virtual void SetTolerance(double);
virtual double GetTolerance();

هناك أسباب كثيرة لاستخدام هذه الماكروهات تتجاوز وضوح الشيفرة فحسب. ففي VTK هناك أعضاء بيانات مهمّون يتحكّمون في التنقيح (debugging)، وفي تحديث زمن تعديل الكائن (MTime)، وفي إدارة العدّ بالمراجع على النحو السليم. وتُحَرّك هذه الماكروهات هذه البيانات على النحو الصحيح، ويُنصح بشدّة باستخدامها. فمثلًا، يقع في VTK خطأ خبيث على وجه الخصوص عندما لا يُدار MTime الخاص بالكائن على النحو السليم. وعندئذٍ قد لا تُنفَّذ الشيفرة عندما ينبغي لها التنفيذ، أو قد تُنفَّذ أكثر من اللازم.

ومن نقاط قوّة VTK قدرته على تمثيل البيانات وإدارتها بطريقة مبسّطة نسبيًا. فعادةً تُستخدَم مصفوفات بيانات متنوّعة من أنواع محدّدة (مثل vtkFloatArray) لتمثيل مقاطع متّصلة من المعلومات. فمثلًا، تُمثَّل قائمة من ثلاث نقاط XYZ بمصفوفة vtkFloatArray من تسعة مدخلات (x,y,z, x,y,z، إلخ). وهناك مفهوم الـ tuple في هذه المصفوفات، فالنقطة ثلاثية الأبعاد ثلاثية (3-tuple)، في حين تُمثَّل مصفوفة موتّر متماثلة 3×3 بستّية (6-tuple) (حيث يمكن تحقيق وفر في المساحة بفضل التناظر). وقد أُخذ هذا التصميم عن قصد، لأنّه في الحوسبة العلمية من الشائع التفاعل مع أنظمة تعالج المصفوفات (مثل Fortran)، وتخصيص الذاكرة وتحريرها على شكل كتل متّصلة كبيرة أكثر كفاءة بكثير. وإضافةً إلى ذلك، فإنّ الاتصال وعمليات التسلسل (serialization) وأداء الإدخال/الإخراج (IO) تكون عمومًا أكثر كفاءة بكثير مع البيانات المتّصلة. وتمثّل مصفوفات البيانات الأساسية هذه (من الأنواع المختلفة) الجزء الأكبر من بيانات VTK، ولديها مجموعة متنوّعة من طرق الراحة لإدراج المعلومات والوصول إليها، بما في ذلك طرق للوصول السريع، وطرق تخصّص الذاكرة تلقائيًا عند الحاجة إليها عند إضافة المزيد من البيانات. ومصفوفات البيانات هي مشتقّات من الصنف المجرّد vtkDataArray، ما يعني إمكان استخدام طرق افتراضية عامة (generic virtual methods) لتبسيط البرمجة. غير أنّه، سعيًا وراء بلوغ أداء أعلى، تُستخدَم دوال ساكنة معمَّمة على قوالب (templated) تتبدّل بحسب النوع، مع وصول لاحق ومباشر إلى مصفوفات البيانات المتّصلة.

وعلى العموم، فإنّ قوالب C++ غير ظاهرة في واجهة الصنف العامة (public class API)؛ وإن كانت تُستخدَم على نطاق واسع لأسباب الأداء. وينطبق الأمر نفسه على STL: فنحن نستخدم عادةً نمط تصميم PIMPL[1] لإخفاء تعقيدات تنفيذ قالب عن المستخدم أو مطوّر التطبيق. وقد أفادنا هذا الأمر بصورة خاصة حين يأتي تغليف الكود داخل شيفرة مُفسَّرة كما وُصفت سابقًا. وتجنّب تعقيدات القوالب في الواجهة العامة يعني أنّ تنفيذ VTK، من منظور مطوّر التطبيق، خالٍ في معظمه من تعقيدات اختيار أنواع البيانات. وبالطبع، في الداخل يقود تنفيذ الشيفرة نوع البيانات، الذي يُحدَّد عادةً وقت التشغيل عند الوصول إلى البيانات.

ويتساءل بعض المستخدمين لماذا يستخدم VTK العدّ بالمراجع (reference counting) لإدارة الذاكرة بدلًا من نهج أكثر ملاءمة للمستخدم مثل جمع القمامة (garbage collection). والإجابة الأساسية أنّ VTK يحتاج إلى تحكّم كامل في متى تُحذف البيانات، لأنّ أحجام البيانات قد تكون هائلة. فمثلًا، حجم وحدة من بيانات البايتات بحجم 1000×1000×1000 يعادل غيغابايت واحد. وليس من الفكرة الجيدة ترك هذه البيانات متناثرة بينما يقرّر جامع القمامة ما إذا كان وقت تحريرها قد حان أم لا. وفي VTK تمتلك معظم الأصناف (مشتقّات vtkObject) القدرة المدمجة على العدّ بالمراجع. ويحتوي كل كائن على عدّاد مراجع يُهيَّأ إلى واحد لحظة إنشاء الكائن. ومع كل تسجيل لاستخدام الكائن، يزداد عدّاد المراجع بمقدار واحد. وعلى نحو مماثل، عند إلغاء تسجيل استخدام الكائن (أو ما يعادله، حذف الكائن)، ينقص عدّاد المراجع بمقدار واحد. وفي النهاية ينخفض عدّاد مراجع الكائن إلى الصفر، وعندئذٍ يفني الكائن ذاتيًّا. ويبدو المثال النموذجي على النحو التالي:

vtkCamera *camera = vtkCamera::New();   //reference count is 1
camera->Register(this);                 //reference count is 2
camera->Unregister(this);               //reference count is 1
renderer->SetActiveCamera(camera);      //reference count is 2
renderer->Delete();                     //ref count is 1 when renderer is deleted
camera->Delete();                       //camera self destructs

وهناك سبب آخر مهمّ يجعل العدّ بالمراجع مهمًّا لـ VTK— فهو يوفّر القدرة على نسخ البيانات بكفاءة. فمثلًا، تخيّل كائن بيانات D1 يتكوّن من عدد من مصفوفات البيانات: النقاط، والمضلّعات، والألوان، والسميات، وإحداثيات النسيج. ثمّ تخيّل معالجة هذه البيانات لإنشاء كائن بيانات جديد D2 مطابق للأول مع إضافة بيانات متجهات (موضوعة على النقاط). ومن الأساليب المهدِرة للذاكرة نسخ D1 نسخًا كاملًا (عميقًا) لإنشاء D2، ثمّ إضافة مصفوفة بيانات المتجهات الجديدة إلى D2. وبدلًا من ذلك، ننشئ D2 فارغًا ثمّ نمرّر مصفوفات D1 إلى D2 (نسخ سطحي)، مع استخدام العدّ بالمراجع لتتبّع ملكية البيانات، ثمّ نضيف في النهاية مصفوفة المتجهات الجديدة إلى D2. ويتجنّب الأسلوب الثاني نسخ البيانات، وهو — كما argue سابقًا — أمر جوهري لنظام تصوير جيّد. وكما سنرى لاحقًا في هذا الفصل، يؤدّي خط أنابيب معالجة البيانات هذا النوع من العمليات بصورة روتينية، أي نسخ البيانات من مُدخَل الخوارزمية إلى مُخرَجها، ومن ثمّ فإنّ العدّ بالمراجع أساسي لـ VTK.

وبالطبع، هناك بعض المشاكل المتداولة في العدّ بالمراجع. فقد توجد أحيانًا دورات مراجع (reference cycles)، حيث تشير الكائنات في الدورة إلى بعضها بعضًا في تهيؤ داعم متبادل. وفي هذه الحالة يلزم تدخّل ذكي، أو—in حالة VTK— تُستخدَم المرافق الخاصة المنفَّذة في vtkGarbageCollector لإدارة الكائنات المتنوّشة في الدورات. وحين يُحدَّد صنف من هذا النوع (وهو أمر متوقَّع أثناء التطوير)، يُسجِّل الصنف نفسه لدى جامع القمامة ويُعِرِّف طُرُقه الخاصة Register وUnRegister. ثمّ يتولّى أسلوب حذف الكائن اللاحق (أو إلغاء التسجيل) تحليلًا طوبولوجيًا لشبكة العدّ بالمراجع المحلية، بحثًا عن جزر منفصلة من كائنات تشير إلى بعضها بعضًا. ثمّ يحذف جامع القمامة هذه الجزر.

معظم عمليات الإنشاء في VTK تتم عبر مصنع كائنات (object factory) منفَّذ بوصفه عضوًا ساكنًا (static class member). وتظهر الصياغة النموذجية على النحو التالي:

vtkLight *a = vtkLight::New();

والأهمّ هنا أن نلاحظ أنّ ما يُنشَأ فعليًا قد لا يكون vtkLight، وقد يكون مشتقًّا من vtkLight (مثل vtkOpenGLLight). وهناك مجموعة متنوّعة من الدوافع وراء مصنع الكائنات، أهمّها قابلية نقل التطبيقات (portability) واستقلالية الأجهزة (device independence). فمثلًا، في المثال السابق ننشئ مصدر ضوء (light) في مشهد معروض. في تطبيق معيّن على منصّة معيّنة، قد تُنتج vtkLight::New ضوء OpenGL، لكنّ على منصّات أخرى يحتمل وجود مكتبات عرض أخرى أو طرائق أخرى لإنشاء ضوء في نظام الرسوميات. وأيُّ صنف مشتقّ يجب إنشاؤه بالضبط هو دالّة لمعلومات النظام وقت التشغيل. وفي الأيام الأولى لـ VTK كان هناك كمٌّ هائل من الخيارات بما فيها gl وPHIGS وStarbase وXGL وOpenGL. وبعد أن أختفت معظمها، ظهرت مناهج جديدة منها DirectX والمقاربات المعتمدة على وحدات معالجة الرسوميات (GPU). ومع مرور الوقت، لم تحتج التطبيقات المكتوبة بـ VTK إلى تغيير إذ اشتقّ المطوّرون مشتقّات جديدة خاصة بالأجهزة من vtkLight ومن أصناف العرض الأخرى لدعم التقنيات المتطوّرة. ومن الاستخدامات المهمّة الأخرى لمصنع الكائنات تمكين الاستبدال وقت التشغيل بنسخ محسَّنة أداءً. فمثلًا، قد يُستبدل vtkImageFFT بصنف يتعامل مع عتاد مخصَّص لغرض معيّن أو مع مكتبة عدديّة.

24.2.2. تمثيل البيانات

ومن نقاط قوّة VTK قدرته على تمثيل صيغ معقّدة من البيانات. وتمتدّ هذه الصيغ من الجداول البسيطة إلى البنى المعقّدة مثل شبكات العناصر المحدودة (finite element meshes). وجميع صيغ البيانات هذه هي مشتقّات من vtkDataObject كما هو مبيّن في الشكل 24.1 (لاحظ أنّه مخطّط وراثة جزئي للأصناف الكثيرة لكائنات البيانات).

أصناف كائنات البيانات

الشكل 24.1: أصناف كائنات البيانات

ومن أهمّ خصائص vtkDataObject أنّه قابل للمعالجة داخل خط أنابيب التصوير (القسم الفرعي التالي). ومن بين الأصناف الكثيرة المعروضة، هناك عدد قليل فقط يُستخدم عادةً في معظم تطبيقات العالم الحقيقي. وتُستخدم vtkDataSet ومشتقّاتها للتصوير العلمي (الشكل 24.2). فمثلًا، تُستخدم vtkPolyData لتمثيل الشبكات المضلّعية، وvtkUnstructuredGrid لتمثيل الشبكات، وvtkImageData لتمثيل بيانات البكسلات والفوكسلات ثنائية وثلاثية الأبعاد.

أصناف مجموعات البيانات

الشكل 24.2: أصناف مجموعات البيانات

24.2.3. معمارية خط الأنابيب

يتكوّن VTK من عدّة نظم فرعية رئيسية. ومن المرجّح أن يكون النظام الفرعي الأكثر ارتباطًا بحزم التصوير هو معمارية تدفّق البيانات/خط الأنابيب (data flow/pipeline architecture). ومفهوميًا، تتكوّن معمارية خط الأنابيب من ثلاث أصناف أساسية من الكائنات: كائنات لتمثيل البيانات (وهي vtkDataObject التي ناقشناها أعلاه)، وكائنات لمعالجة كائنات البيانات أو تحويلها أو ترشيحها أو تخطيطها من صيغة إلى أخرى (vtkAlgorithm)؛ وكائنات لتنفيذ خط الأنابيب (vtkExecutive) تتحكّم في رسم بياني متّصل من كائنات بيانات وكائنات معالجة متناوبة (أي خط الأنابيب). ويوضّح الشكل 24.3 خط أنابيب نموذجيًا.

خط أنابيب نموذجي

الشكل 24.3: خط أنابيب نموذجي

وفيما يبدو الأمر بسيطًا مفهوميًا، فإنّ تنفيذ معمارية خط الأنابيب فعليًا تحدٍّ. وأحد الأسباب أنّ تمثيل البيانات قد يكون معقّدًا. فمثلًا، تتكوّن بعض مجموعات البيانات من تسلسلات هرمية أو تجميعات للبيانات، لذا فإنّ التنفيذ عبر البيانات يتطلّب تكرارًا أو تعاودًا غير تافه. ولزيادة الأمور تعقيدًا، يتطلّب المعالجة المتوازية (سواءً بالذاكرة المشتركة أو بالمقاربات القابلة للتوسّع والموزّعة) تقسيم البيانات إلى قطع، حيث قد يتعيّب تداخل هذه القطع بعضها في بعض من أجل حساب معلومات الحدود على نحو متّسق، مثل المشتقّات.

كما تُدخل كائنات الخوارزميات تعقيدها الخاص. فبعض الخوارزميات قد تأخذ عدة مُدخَلات و/أو تنتج عدة مُخرجات من أنواع مختلفة. ويمكن لبعضها العمل محليًا على البيانات (مثل حساب مركز خلية)، بينما يحتاج بعضها الآخر إلى معلومات عامّة، مثلًا لحساب مدرّج تكراري (histogram). وفي جميع الحالات، تتعامل الخوارزميات مع مُدخَلاتها بوصفها غير قابلة للتغيير؛ فالخوارزميات تقرأ مُدخلاتها فحسب لإنتاج مُخرجاتها. والسبب في ذلك أنّ البيانات قد تكون متاحة كمدخل لعدّة خوارزميات، وليس من الفكرة الجيدة أن تدهس خوارزمية واحدة على مُدخل خوارزمية أخرى.

وأخيرًا، قد يكون المنفِّذ معقّدًا بحسب تفاصيل استراتيجية التنفيذ. ففي بعض الحالات قد نرغب في تخزين النتائج الوسيطة مؤقتًا بين المرشّحات. وهذا يقلّل مقدار إعادة الحساب التي يجب أداؤها إذا تغيّر شيء في خط الأنابيب. وعلى الجانب الآخر، قد تكون مجموعات بيانات التصوير ضخمة، وعندئذٍ قد نرغب في تحرير البيانات عندما لم تعد لازمة للحساب. وأخيرًا، هناك استراتيجيات تنفيذ معقّدة، مثل المعالجة متعدّدة الدقّة (multi-resolution) للبيانات، تتطلّب أن يعمل خط الأنابيب بأسلوب تكراري.

ولتوضيح بعض هذه المفاهيم وشرح معمارية خط الأنابيب أكثر، إليك مثال C++ التالي:

vtkPExodusIIReader *reader = vtkPExodusIIReader::New();
reader->SetFileName("exampleFile.exo");

vtkContourFilter *cont = vtkContourFilter::New();
cont->SetInputConnection(reader->GetOutputPort());
cont->SetNumberOfContours(1);
cont->SetValue(0, 200);

vtkQuadricDecimation *deci = vtkQuadricDecimation::New();
deci->SetInputConnection(cont->GetOutputPort());
deci->SetTargetReduction( 0.75 );

vtkXMLPolyDataWriter *writer = vtkXMLPolyDataWriter::New();
writer->SetInputConnection(deci->GetOuputPort());
writer->SetFileName("outputFile.vtp");
writer->Write();

في هذا المثال، يقرأ كائن قارئ ملف بيانات يصف شبكة غير مهيكلة كبيرة (أو شبكة mesh). ويُنتج المرشّح التالي سطحًا متساوي القيمة (isosurface) من الشبكة. ويقلّص مرشّح vtkQuadricDecimation حجم السطح متساوي القيمة، وهو مجموعة بيانات مضلّعية، عبر تنقيحه (أي تقليل عدد المثلثات التي تمثّل خط تساوي القيمة). وأخيرًا، بعد التنقيح تُكتب مجموعة البيانات الجديدة الأصغر حجمًا مرة أخرى إلى القرص. ويحدث التنفيذ الفعلي لخط الأنابيب عند استدعاء الكاتب لطريقة Write (أي عند الطلب على البيانات).

وكما يوضّح هذا المثال، فإنّ آلية تنفيذ خط الأنابيب في VTK قائمة على الطلب (demand-driven). فحين يحتاج طرف نهاية (sink) مثل كاتب أو مُخطِّط (كائن تصوير البيانات) إلى بيانات، فإنّه يطلبها من مُدخله. فإذا كان المرشّح المُدخل يملك البيانات المناسبة بالفعل، فإنّه ببساطة يعيد التحكّم بالتنفيذ إلى طرف النهاية. لكنّ إذا لم تكن لدى المُدخل البيانات المناسبة، فإنه يحتاج إلى حسابها، وعليه أن يطلبها من مُدخله أولًا. وهكذا يستمرّ هذا المسار عبر خط الأنابيب في اتجاه المصبّ (upstream) حتى بلوغ مرشّح أو مصدر يملك «البيانات المناسبة»، أو بداية خط الأنابيب، وعندئذٍ تُنفَّذ المرشّحات بالترتيب الصحيح وتتدفّق البيانات إلى النقطة التي طُلبت منها داخل خط الأنابيب.

وتجدر هنا أن نوضّح ما تعنيه «البيانات المناسبة». فبشكل افتراضي، بعد تنفيذ مصدر أو مرشّح من VTK، تُخزَّن مخرجاته مؤقتًا في خط الأنابيب تفاديًا للتنفيذ غير الضروري مستقبلًا. ويجري ذلك لتقليل الحساب و/أو الإدخال/الإخراج على حساب الذاكرة، وهو سلوك قابل للتهيئة. ولا يخزّن خط الأنابيب كائنات البيانات فحسب بل أيضًا البيانات الوصفية (metadata) عن الظروف التي تولّدت تحت هذه البيانات. وتشمل هذه البيانات الوصفية ختمًا زمنيًّا (أي ComputeTime) يلتقط لحظة حساب كائن البيانات. وبذلك، في أبسط الحالات، تكون «البيانات المناسبة» هي البيانات التي حُسبت بعد تعديل جميع كائنات خط الأنابيب الواقعة قبلها في اتجاه المصبّ. ومن الأسهل توضيح هذا السلوك بالنظر في الأمثلة التالية. فلنضف ما يلي إلى نهاية برنامج VTK السابق:

vtkXMLPolyDataWriter *writer2 = vtkXMLPolyDataWriter::New();
writer2->SetInputConnection(deci->GetOuputPort());
writer2->SetFileName("outputFile2.vtp");
writer2->Write();

وكما شُرح سابقًا، فإنّ الاستدعاء الأول writer->Write يؤدّي إلى تنفيذ خط الأنابيب بأكمله. وعندما تُستدعى writer2->Write()، يُدرك خط الأنابيب أنّ الخرج المخزَّن مؤقتًا لمرشّح التنقيح محدَّث، إذ يقارن الختم الزمني للمخزن المؤقت بزمن تعديل مرشّح التنقيح ومرشّح المستويات والقارئ. ومن ثمّ لا يلزم أن ينتشر طلب البيانات أبعد من writer2. والآن، لننظر في التغيير التالي.

cont->SetValue(0, 400);

vtkXMLPolyDataWriter *writer2 = vtkXMLPolyDataWriter::New();
writer2->SetInputConnection(deci->GetOuputPort());
writer2->SetFileName("outputFile2.vtp");
writer2->Write();

الآن سيُدرك منفّذ خط الأنابيب أنّ مرشّح المستويات قد عُدِّل بعد آخر تنفيذ لمخرجات مرشّح المستويات ومرشّح التنقيح. وبذلك يكون المخزن المؤقت لهذين المرشّحين قديمًا ويجب إعادة تنفيذهما. لكنّ القارئ لم يُعدَّل قبل مرشّح المستويات، ومن ثمّ فإنّ مخزنه المؤقت صالح، وبالتالي لا يلزم إعادة تنفيذ القارئ.

والسيناريو الموصوف هنا هو أبسط مثال على خط أنابيب قائم على الطلب. أمّا خط أنابيب VTK فأكثر تعقيدًا بكثير. فحين يحتاج مرشّح أو طرف نهاية إلى بيانات، يمكنه تقديم معلومات إضافية لطلب مجموعات بيانات محدّدة. فمثلًا، يمكن للمرشّح إجراء تحليل خارج الذاكرة الأساسية (out-of-core) عبر بثّ قطع البيانات. فلنغيّر مثالنا السابق لتوضيح ذلك.

vtkXMLPolyDataWriter *writer = vtkXMLPolyDataWriter::New();
writer->SetInputConnection(deci->GetOuputPort());
writer->SetNumberOfPieces(2);

writer->SetWritePiece(0);
writer->SetFileName("outputFile0.vtp");
writer->Write();

writer->SetWritePiece(1);
writer->SetFileName("outputFile1.vtp");
writer->Write();

هنا يطلب الكاتب من خط الأنابيب في اتجاه المصبّ أن يحمّل البيانات ويعالجها على شكل قطعتين، تُبثّ كلٌّ منهما باستقلال. ولعلك لاحظت أنّ منطق التنفيذ البسيط الموصوف سابقًا لن يعمل هنا. وبموجبه، عند استدعاء دالّة Write للمرة الثانية، يجب ألّا يعيد خط الأنابيب التنفيذ لأنّ شيئًا في اتجاه المصبّ لم يتغيّر. ولهذا، ولمعالجة هذه الحالة الأكثر تعقيدًا، يضيف المنفّذون منطقًا إضافيًا للتعامل مع طلبات القطع من هذا النوع. ويتكوّن تنفيذ خط أنابيب VTK فعليًا من عدّة مرّات (passes). وحساب كائنات البيانات هو في الواقع آخر مرّة. والمرّة التي تسبقها هي مرّة الطلب. وهنا يمكن لأطراف النهاية والمرشّحات أن تخبر ما في اتجاه المصبّ بما تريده من الحساب القادم. في المثال أعلاه، يبلّغ الكاتب مُدخله بأنه يريد القطعة 0 من 2. وينتشر هذا الطلب في الواقع حتى يصل إلى القارئ. وحين يُنفَّذ خط الأنابيب، يعرف القارئ عندئذٍ أنّ عليه قراءة مجموعة جزئية من البيانات. وإضافةً إلى ذلك، تُخزَّن معلومات عن القطعة التي يقابلها البيانات المخزَّنة مؤقتًا في البيانات الوصفية للكائن. وفي المرة التالية التي يطلب فيها مرشّح بيانات من مُدخله، تُقارَن هذه البيانات الوصفية بالطلب الحالي. وبذلك، في هذا المثال، سيعيد خط الأنابيب التنفيذ من أجل معالجة طلب قطعة مختلفة.

وهناك عدّة أنواع أخرى من الطلبات يمكن للمرشّح تقديمها. وتشمل طلبات خطوة زمنية معيّنة، أو امتدادًا منظّمًا معيّنًا (structured extent)، أو عدد طبقات الشبح (ghost layers) (أي طبقات الحدود لحساب معلومات الحيّز المجاور). وإضافةً إلى ذلك، أثناء مرّة الطلب، يُسمح لكل مرشّح بتعديل الطلبات القادمة من اتجاه المصبّ. فمثلًا، يمكن لمرشّح عاجز عن البثّ (مثل مرشّح الخطوط الانسيابية) تجاهل طلب القطعة وطلب البيانات كاملةً.

24.2.4. النظام الفرعي للعرض

للوهلة الأولى يبدو VTK ذا نموذج عرض موجَّه نحو الكائنات بسيط، بأصناف تقابل المكوّنات التي يتكوّن منها مشهد ثلاثي الأبعاد. فمثلًا، تمثّل vtkActors كائنات يعرضها vtkRenderer بالاشتراك مع vtkCamera، مع احتمال وجود عدّة vtkRenderers داخل vtkRenderWindow واحد. ويضاء المشهد بمصدر ضوء واحد أو أكثر vtkLights. ويُتحكَّم في موضع كل vtkActor عبر vtkTransform، ويُحدَّد مظهر الممثّل عبر vtkProperty. وأخيرًا، يحدّد vtkMapper التمثيل الهندسي للممثّل. وتلعب المُخطِّطات (mappers) دورًا مهمًّا في VTK؛ فهي تنهي خط أنابيب معالجة البيانات، فضلًا عن كونها واجهةً إلى نظام العرض. ولننظر في هذا المثال الذي نُنقِّح فيه البيانات ونكتب النتيجة إلى ملف، ثمّ نُصوِّر النتيجة ونتفاعل معها باستخدام مُخطِّط:

vtkOBJReader *reader = vtkOBJReader::New();
reader->SetFileName("exampleFile.obj");

vtkTriangleFilter *tri = vtkTriangleFilter::New();
tri->SetInputConnection(reader->GetOutputPort());

vtkQuadricDecimation *deci = vtkQuadricDecimation::New();
deci->SetInputConnection(tri->GetOutputPort());
deci->SetTargetReduction( 0.75 );

vtkPolyDataMapper *mapper = vtkPolyDataMapper::New();
mapper->SetInputConnection(deci->GetOutputPort());

vtkActor *actor = vtkActor::New();
actor->SetMapper(mapper);

vtkRenderer *renderer = vtkRenderer::New();
renderer->AddActor(actor);

vtkRenderWindow *renWin = vtkRenderWindow::New();
renWin->AddRenderer(renderer);

vtkRenderWindowInteractor *interactor = vtkRenderWindowInteractor::New();
interactor->SetRenderWindow(renWin);

renWin->Render();

هنا أُنشئ ممثّل واحد ومُصيِّر (renderer) ونافذة عرض واحدة، مع إضافة مُخطِّط يصل خط الأنابيب بنظام العرض. ولاحظ أيضًا إضافة vtkRenderWindowInteractor، التي تلتقط نسخه منها أحداث الفأرة ولوحة المفاتيح وتترجمها إلى تحكّم بالكاميرا أو إلى إجراءات أخرى. وتُعرَّف عملية الترجمة هذه عبر vtkInteractorStyle (وسنعود إلى هذا أدناه). وبشكل افتراضي، تُضبط قيم ونسخ كثيرة في الكواليس. فمثلًا، يُنشأ تحويل هوية (identity transform)، فضلًا عن مصدر ضوء افتراضي واحد (في موضع الرأس) وخاصية افتراضية.

ومع مرور الوقت، أصبح نموذج الكائنات هذا أكثر تطوّرًا. وقد جاء قدر كبير من التعقيد من تطوير أصناف مشتقّة تتخصّص في جانب من جوانب عملية العرض. وأصبحت vtkActors الآن تخصّصات من vtkProp (كما تمامًا مجسَّمٌ يُعثر عليه على خشبة المسرح)، وهناك مجموعة كاملة من هذه الأدوات (props) لعرض الرسوميات والنصوص الطبقة العلوية ثنائية الأبعاد، والأجسام ثلاثية الأبعاد المتخصّصة، وحتى لدعم تقنيات العرض المتقدّمة مثل عرض الحجم (volume rendering) أو تطبيقات وحدات معالجة الرسوميات (انظر الشكل 24.4).

وبالمثل، مع نموّ نموذج البيانات الذي يدعمه VTK، نمت أيضًا المُخطِّطات المختلفة التي تربط البيانات بنظام العرض. والمجال الآخر ذو التوسّع الكبير هو التسلسل الهرمي للتحويلات. فما كان في الأصل مصفوفة تحويل خطّية بسيطة 4×4، أصبح تسلسلًا هرميًّا قويًّا يدعم التحويلات غير الخطّية بما في ذلك تحويل الشريحة الرفيعة الصفيحية (thin-plate spline). فمثلًا، كان vtkPolyDataMapper الأصلي له مشتقّات خاصة بالأجهزة (مثل vtkOpenGLPolyDataMapper). وفي السنوات الأخيرة استُبدل بخط رسوميات متطوّر يُشار إليه بـ«خط الرسّام» (the "painter" pipeline) المبيّن في الشكل 24.4.

أصناف العرض

الشكل 24.4: أصناف العرض

ويدعم تصميم الرسّام مجموعة متنوّعة من تقنيات عرض البيانات التي يمكن دمجها لتوفير مؤثّرات عرض خاصة. وهذه القدرة تتفوق بكثير على vtkPolyDataMapper البسيط الذي نُفِّذ أوّلًا عام 1994.

ومن الجوانب المهمّة الأخرى في نظام التصوير نظام الاختيار. ففي VTK هناك تسلسل هرمي من المنتقيات (pickers)، تُصنَّف تقريبًا إلى كائنات تختار vtkProps بالاعتماد على طرائق قائمة على العتاد مقارنةً بالطرائق البرمجية (مثل صبّ الأشعة ray-casting)؛ فضلًا عن كائنات تقدّم مستويات مختلفة من المعلومات بعد عملية الانتقاء. فمثلًا، تقدّم بعض المنتقيات موقعًا فقط في فضاء العالم XYZ دون تحديد أيّ vtkProp اختارته؛ بينما تقدّم أخرى، إضافةً إلى vtkProp المختار، نقطةً أو خلية بعينها تشكّل الشبكة التي تعرّف هندسة الأداة.

24.2.5. الأحداث والتفاعل

التفاعل مع البيانات جزء أساسي من التصوير. وفي VTK يتم ذلك بطرق متنوّعة. وفي أبسط مستوياته، يستطيع المستخدمون مراقبة الأحداث والاستجابة لها على النحو المناسب عبر الأوامر (نمط تصميم الأمر/المراقب). وتحتفظ جميع مشتقّات vtkObject بقائمة من المراقبين الذين يسجّلون أنفسهم لدى الكائن. وأثناء التسجيل، يوضّح المراقبون أيّ الأحداث تحديدًا يهمّهم، مع إضافة أمر مرتبط يُستدعى إذا وقع الحدث ومتى وقع. ولمعرفة كيفية عمل ذلك، إليك المثال التالي الذي يراقب فيه مرشّح (وهو هنا مرشّح تنقيح مضلّعات) ثلاثة أحداث هي StartEvent وProgressEvent وEndEvent. وتُستدعى هذه الأحداث حين يبدأ المرشّح في التنفيذ، ودوريًا أثناء التنفيذ، ثمّ عند اكتمال التنفيذ. وفيما يلي يتضمّن صنف vtkCommand طريقة Execute تطبع المعلومات المناسبة قياسًا بالزمن الذي استغرقه تنفيذ الخوارزمية:

class vtkProgressCommand : public vtkCommand
{
  public:
    static vtkProgressCommand *New() { return new vtkProgressCommand; }
    virtual void Execute(vtkObject *caller, unsigned long, void *callData)
    {
      double progress = *(static_cast<double*>(callData));
      std::cout << "Progress at " << progress<< std::endl;
    }
};

vtkCommand* pobserver = vtkProgressCommand::New();

vtkDecimatePro *deci = vtkDecimatePro::New();
deci->SetInputConnection( byu->GetOutputPort() );
deci->SetTargetReduction( 0.75 );
deci->AddObserver( vtkCommand::ProgressEvent, pobserver );

وفيما كان هذا شكلًا أوّليًّا من أشكال التفاعل، إلّا أنّه عنصر أساسي في كثير من التطبيقات التي تستخدم VTK. فمثلًا، يمكن تحويل الشيفرة البسيطة أعلاه بسهولة إلى شريط تقدّم في واجهة المستخدم الرسومية وعرضه وإدارته. وهذا النظام الفرعي للأمر/المراقب محوري أيضًا في عناصر الواجهة ثلاثية الأبعاد في VTK، وهي كائنات تفاعل متطوّرة للاستعلام عن البيانات ومعالجتها وتحريرها، ووصفها يأتي أدناه.

بالإشارة إلى المثال أعلاه، من المهمّ ملاحظة أنّ أحداث VTK معرَّفة مسبقًا، لكنّ هناك بابًا خلفيًّا للأحداث التي يعرّفها المستخدم. ويحدّد صنف vtkCommand مجموعة الأحداث المُعدَّدة (مثل vtkCommand::ProgressEvent في المثال أعلاه)، فضلًا عن حدث المستخدم. وUserEvent، وهو مجرّد قيمة صحيحة، يُستخدَم عادةً كقيمة بداية (offset) داخل مجموعة أحداث التطبيق التي يعرّفها المستخدم. فمثلًا، قد تشير vtkCommand::UserEvent+100 إلى حدث معيّن خارج مجموعة أحداث VTK المعرّفة.

ومن منظور المستخدم، يبدو عنصر واجهة VTK بوصفه ممثّلًا في مشهد، إلا أنّ المستخدم يتفاعل معه بالتلاعب بالمقبضات (handles) أو بسمات هندسية أخرى (التلاعب بالمقبضات والسمات الهندسية يعتمد على وظيفة الانتقاء الموصوفة سابقًا). والتفاعل مع هذا العنصر بديهي إلى حدٍّ معقول: يمسك المستخدم المقبضات الكروية ويحرّكها، أو يمسك الخط ويحرّكه. غير أنّه في الكواليس تُصدر أحداث (مثل InteractionEvent)، والتطبيق المبرمج على النحو السليم يستطيع مراقبة هذه الأحداث ثمّ اتّخاذ الإجراء المناسب. فمثلًا، كثيرًا ما يستجيب لـ vtkCommand::InteractionEvent على النحو التالي:

vtkLW2Callback *myCallback = vtkLW2Callback::New();
  myCallback->PolyData = seeds;    // streamlines seed points, updated on interaction
  myCallback->Actor = streamline;  // streamline actor, made visible on interaction

vtkLineWidget2 *lineWidget = vtkLineWidget2::New();
  lineWidget->SetInteractor(iren);
  lineWidget->SetRepresentation(rep);
  lineWidget->AddObserver(vtkCommand::InteractionEvent,myCallback);

وتُنشأ عناصر واجهة VTK فعليًا باستخدام كائنين: مشتقّ من vtkInteractorObserver ومشتقّ من vtkProp. ويكتفّي vtkInteractorObserver بمراقبة تفاعل المستخدم في نافذة العرض (أي أحداث الفأرة ولوحة المفاتيح) ومعالجتها. أمّا مشتقّات vtkProp (أي الممثّلات) فيكتفّي vtkInteractorObserver بالتلاعب بها. وعادةً يتكوّن هذا التلاعب من تعديل هندسة vtkProp بما في ذلك إبراز المقبضات، وتغيير مظهر المؤشّر، و/أو تحويل البيانات. وبالطبع، تتطلّب تفاصيل كل عنصر كتابة مشتقّات تتحكّم في دقائق سلوكه، وهناك أكثر من 50 عنصرًا مختلفًا في النظام حاليًا.

24.2.6. ملخّص المكتبات

VTK حزمة أدوات برمجية ضخمة. ويتكوّن النظام حاليًا من نحو 1.5 مليون سطر من الشيفرة (بما في ذلك التعليقات، دون أن يشمل ذلك البرمجيات المغلِّفة المولَّدة آليًّا)، ومن نحو 1000 صنف C++. ولإدارة تعقيد النظام وتقليل زمن البناء والربط، قُسِّم النظام إلى عشرات الأدلة الفرعية. ويعرض الجدول 24.1 هذه الأدلة الفرعية، مع ملخّص موجز يصف القدرات التي توفّرها المكتبة.

Common أصناف VTK الأساسية
Filtering أصناف تُستخدم لإدارة تدفّق بيانات خط الأنابيب
Rendering العرض، والانتقاء، وعرض الصور، والتفاعل
VolumeRendering تقنيات عرض الحجم
Graphics معالجة الهندسة ثلاثية الأبعاد
GenericFiltering المعالجة غير الخطّية للهندسة ثلاثية الأبعاد
Imaging خط أنابيب معالجة الصور
Hybrid أصناف تتطلّب كلٌّ من وظيفة الرسوميات ووظيفة معالجة الصور
Widgets تفاعل متطوّر
IO إدخال وإخراج VTK
Infovis تصوير المعلومات
Parallel المعالجة المتوازية (المتحكّمات والمُتواصلات)
Wrapping دعم تغليف Tcl وPython وJava
Examples أمثلة واسعة موثّقة جيّدًا

الجدول 24.1: الأدلة الفرعية في VTK

24.3. نظرة إلى الوراء وإلى الأمام

كان VTK نظامًا ناجحًا إلى حدٍّ هائل. ومع أنّ أول سطر من الشيفرة كُتب عام 1993، فإنّ VTK ما زال في وقت كتابة هذا الكتاب ينمو بقوة، بل إنّ وتيرة التطوير تتزايد إن أمكن.[2] وفي هذا القسم نتناول بعض الدروس المستفادة والتحدّيات المستقبلية.

24.3.1. إدارة النموّ

ومن أكثر جوانب مغامرة VTK إثارةً للدهشة طول عمر المشروع. وتعود وتيرة التطوير إلى عدّة أسباب رئيسية:

  • الخوارزميات والقدرات الجديدة مستمرّة في الإضافة. فمثلًا، النظام الفرعي للمعلوماتية (Titan، الذي طوّرته أساسًا مختبرات سانديا الوطنية (Sandia National Labs) وKitware) إضافةٌ جوهرية حديثة. كما يجري إضافة أصناف إضافية للمخطّطات البيانية والعرض، فضلًا عن قدرات لأنواع جديدة من مجموعات البيانات العلمية. ومن الإضافات المهمّة الأخرى عناصر واجهة التفاعل ثلاثية الأبعاد. وأخيرًا، يقود التطوّر المستمرّ للعرض والمعالجة المعتمدَين على وحدات معالجة الرسوميات (GPU) قدرات جديدة في VTK.
  • التعرّض المتنامي لـ VTK واستخدامه عملية ذاتية الاستدامة تُضيف إلى المجتمع مزيدًا من المستخدمين والمطوّرين. فمثلًا، ParaView هو تطبيق التصوير العلمي الأشهر المبني على VTK، ويحظى بتقدير عالٍ في مجتمع الحوسبة عالية الأداء. و3D Slicer منصّة حوسبة حيوية طبية رئيسية مبنية إلى حدٍّ بعيد على VTK، وتتلقّى ملايين الدولارات سنويًا في التمويل.
  • تستمرّ عملية تطوير VTK في التطوّر. وفي السنوات الأخيرة دُمجت أدوات عملية البرمجيات CMake وCDash وCTest وCPack في بيئة بناء VTK. ثمّ نُقل مستودع شيفرة VTK مؤخرًا إلى Git وإلى تدفّق عمل أكثر تطوّرًا. وتضمن هذه التحسينات بقاء VTK في طليعة تطوير البرمجيات في مجتمع الحوسبة العلمية.

وفيما كان النموّ مثيرًا ويؤكّد جدوى إنشاء هذا النظام البرمجي ويبشّر بمستقبل VTK، فإنّ إدارته على النحو الجيّد قد تكون صعبة للغاية. ومن ثمّ، يتركّز مستقبل VTK القريب أكثر على إدارة نموّ المجتمع فضلًا عن نموّ البرمجية. وقد اتُّخذت في هذا الصدد عدّة خطوات.

أولًا، يجري إنشاء هياكل إدارة رسمية. فقد أُنشئت مجلس مراجعة معمارية (Architecture Review Board) لتوجيه تطوير المجتمع والتقنية، مع التركيز على القضايا الاستراتيجية والعليا. كما أنّ مجتمع VTK يُنشئ فريقًا معترفًا من قادة المجالات (Topic Leads) لتوجيه التطوير التقني للأنظمة الفرعية المحدّدة في VTK.

ثمّ هناك خطط لمزيد من التحويل إلى وحدات (modularization)، جزئيًا استجابةً لقدرات تدفّق العمل التي قدّمها git، لكن أيضًا من الاعتراف بأنّ المستخدمين والمطوّرين يريدون عادةً العمل مع أنظمة فرعية صغيرة من الحزمة، ولا يريدون البناء والربط بالحزمة كاملة. وإضافةً إلى ذلك، لدعم المجتمع المتنامي، من المهمّ دعم المساهمات التي تقدّم وظائف وأنظمة فرعية جديدة، حتى وإن لم تكن بالضرورة جزءًا من نواة الحزمة. ومن خلال إنشاء مجموعة فضفاضة مقسّمة إلى وحدات، الممكن استيعاب العدد الكبير من المساهمات في الأطراف مع الحفاظ على استقرار النواة.

24.3.2. إضافات تقنية

إلى جانب عملية تطوير البرمجيات، هناك ابتكارات تقنية كثيرة في خط التطوير.

  • المعالجة المشتركة (co-processing) هي قدرة يُدمج فيها محرّك التصوير في شيفرة المحاكاة، فيولّد دوريًا مقتطفات من البيانات للعرض. وتقلّل هذه التقنية كثيرًا الحاجة إلى إخراج كميات كبيرة من بيانات الحلّ الكاملة.
  • لا يزال خط أنابيب معالجة البيانات في VTK معقّدًا أكثر من اللازم. وتجري حاليًا تحسينات لتبسيط هذا النظام الفرعي وإعادة هيكلته.
  • تتزايد شعبية القدرة على التفاعل المباشر مع البيانات لدى المستخدمين. وفيما لدى VTK مجموعة كبيرة من عناصر الواجهة، فإنّ تقنيات تفاعل كثيرة أخرى آخذة في الظهور، بما في ذلك التقنيات المعتمدة على الشاشات اللمسية وتلك ثلاثية الأبعاد. وسيواصل التفاعل تطوّره بإيقاع سريع.
  • تتزايد أهمية الكيمياء الحاسوبية لمصمّمي المواد والمهندسين. ويجري إضافة القدرة على تصوير بيانات الكيمياء والتفاعل معها إلى VTK.
  • انتقد نظام العرض في VTK لأنّه معقّد أكثر من اللازم، ممّا يجعل اشتقاق أصناف جديدة أو دعم تقنيات عرض جديدة أمرًا صعبًا. وإضافةً إلى ذلك، لا يدعم VTK مباشرةً فكرة الرسم البياني للمشهد (scene graph)، وهي بدورها شيء طالبه كثير من المستخدمين.
  • وأخيرًا، تظهر باستمرار أشكال جديدة من البيانات. فمثلًا، في المجال الطبي مجموعات بيانات حجمية هرمية بدقّة متفاوتة (مثل المجهر متطابق البؤر (confocal) مع تكبير موضعي).

24.3.3. العلم المفتوح

وأخيرًا، تلتزم Kitware، وكذلك مجتمع VTK على نحو أعمّ، بالعلم المفتوح (Open Science). ومن الناحية العملية، هذه طريقة لقول إنّنا سنروّج للبيانات المفتوحة، والنشر المفتوح، والمصادر المفتوحة—وهي الميزات الضرورية لضمان أنّنا ننشئ أنظمة علمية قابلة لإعادة الإنتاج. وفيما وُزّع VTK لفترة طويلة بوصفه نظامًا مفتوح المصدر ومفتوح البيانات، إلّا أنّ عملية التوثيق كانت ناقصة. وفيما توجد كتب لائقة [Kit10,SML06]، فقد تنوّعت الطرق الارتجالية (ad hoc) لجمع المنشورات التقنية، بما في ذلك المساهمات الجديدة في الشيفرة المصدرية. ونحن نحسّن الوضع من خلال تطوير آليات نشر جديدة مثل مجلة VTK (VTK Journal)[3] التي تتيح نشر مقالات تتكوّن من التوثيق والشيفرة المصدرية والبيانات وصور اختبار صالحة. كما تتيح المجلة مراجعات آلية للشيفرة (باستخدام عملية اختبار جودة البرمجيات في VTK)، فضلًا عن المراجعات البشرية للإرسال.

24.3.4. الدروس المستفادة

وفيما كان VTK ناجحًا، هناك أشياء كثيرة لم نفعلها على النحو الصحيح:

  • قابلية تقسيم التصميم (Design Modularity): قمنا بعمل جيّد في اختيار قابلية تقسيم أصنافنا. فمثلًا، لم نفعل شيئًا ساذجًا مثل إنشاء كائن لكل بكسل، بل أنشأنا vtkImageClass الأعلى مستوى، الذي يعامل مصفوفات بيانات البكسلات في الكواليس. غير أنّنا في بعض الحالات جعلنا أصنافنا أعلى مستوى وأعقد ممّا ينبغي، وفي حالات كثيرة اضطررنا إلى إعادة هيكلتها إلى قطع أصغر، ولا نزال نواصل هذه العملية. ومن أبرز الأمثلة خط أنابيب معالجة البيانات. ففي البداية طُبِّقت خطوط الأنابيب ضمنًا عبر تفاعل كائنات البيانات والخوارزميات. وانتهينا في النهاية إلى إدراك أنّ علينا إنشاء كائن منفّذ صريح لخط الأنابيب (pipeline executive) لتنسيق التفاعل بين البيانات والخوارزميات، ولتنفيذ استراتيجيات مختلفة في معالجة البيانات.
  • مفاهيم أساسية فوّتناها (Missed Key Concepts): إحدى أكبر ندمنا ألّا نستخدم مُكرِّرات C++ (iterators) على نطاق واسع. ففي حالات كثيرة يكون اجتياز البيانات في VTK شبيهًا بلغة البرمجة العلمية Fortran. وكانت المرونة الإضافية التي توفّرها المُكرِّرات ستمثّل فائدة كبيرة للنظام. فمثلًا، من الفائدية الكبيرة معالجة منطقة محلية من البيانات، أو البيانات التي تستوفي معيار تكرار معيّنًا فقط.
  • مشاكل التصميم (Design Issues): وبالطبع هناك قائمة طويلة من قرارات التصميم غير المثلى. لقد واجهنا صعوبات في خط أنابيب تنفيذ البيانات، إذ مررنا بأجيال متعدّدة، حسّنا التصميم في كل مرّة. ونظام العرض معقّد أيضًا وصعب الاشتقاق. وأدى تحدٍّ آخر إلى التصوّر الأوّلي لـ VTK: رأيناه نظام تصوير للقراءة فقط لعرض البيانات. لكنّ العملاء الحاليين يريدون في غالبية الأحيان أن يكون قادرًا على تحرير البيانات، وهو ما يتطلّب بنى بيانات مختلفة على نحو جوهري.

ومن الأمور الرائعة في نظام مفتوح المصدر مثل VTK أنّ كثيرًا من هذه الأخطاء يمكن تصحيحها وستُصحَّح مع الوقت. ولدينا مجتمع تطوير نشط وقادر يُحسّن النظام كل يوم، ونتوقّع أن يستمرّ ذلك في المستقبل المنظور.

الحواشي

  1. http://en.wikipedia.org/wiki/Opaque_pointer.
  2. راجع أحدث تحليل لشيفرة VTK على http://www.ohloh.net/p/vtk/analyses/latest.
  3. http://www.midasjournal.org/?journal=35

  1. http://en.wikipedia.org/wiki/Opaque_pointer. ↩︎

  2. راجع أحدث تحليل لشيفرة VTK على http://www.ohloh.net/p/vtk/analyses/latest. ↩︎

  3. http://www.midasjournal.org/?journal=35 ↩︎