نرم‌افزار نمایش تصاویر پزشکی در کلوژر - clojure conj 2018

clojurescript
clojure
talk

#1

توی صحبتهای که درمورد پردازش دیتاهای حجیم در زبانهای برنامه نویسی مختلف انجام دادیم، به یه ویدیو اشاره کردم که تصمیم گرفتم برای معرفیش یه تاپیک بزنم (تا بحث بیشتر از این off-topic نشه)

این ویدیو، یکی از talkهای کنفرانس ۲۰۱۸ کلوژر هست و پروژه‌ای رو معرفی میکنه که عکسهای ۱۲مگاپیکسلی ماموگرافی رو تحت بروزر نمایش میده و با استفاده از اون، یه سری دیتا برای یادگیری ماشینی جمع‌آوری میکنن.

توضیحات زیادی میتونم درمورد کاری که انجام داده بگم. توی چند ماه اخیر اطلاعات خیلی زیادی درمورد نرم‌افزارها و فایلهای پزشکی جمع‌آوری کردم. میتونم بخشهایی که توی ویدیو توضیح داده شده و توضیح داده نشده رو کاملتر شرح بدم (عمق رنگ تصاویر پزشکی و تبدیلشون به رنگهایی که مانیتور بتونه نمایش بده و چیزی که تحت عنوان window leveling معرفی میشه)
اینجا نمینویسم چون حس میکنم براتون مهم نیست ولی اگه بخواید میتونم زیر همین تاپیک توضیح بدم.
خلاصه توی این پروژه، ۴تا عکس ۱۲مگاپیکسلی (تقریبا هر عکس ۱۲ملیون پیکسل) رو داخل بروزر باز میکنه و امکان حرکت و زوم و تغییر مقدار تمام پیکسلها هست و اینکار رو به صورت ۳۰فریم بر ثانیه انجام میده. روی کامپیوتری که خیلی هم قوی نیست و البته داخل بروزر (که در داخل sand box کار میکنه و دسترسی کامل به منابع سیستم نداره)

نسخه‌ی قدیمی نرم‌افزار رو با js نوشته بودن و با دیتاهایی که در حقیقت mutable بودن. به همین خاطر حجم زیادی از دیتا باید تغییر میکرد و هر تغییر نیاز داشت تا کل دیتا از اول بازنویسی بشه و دیتای قدیمی GC بشه. نتیجش شد چیزی حدود یک فریم بر ثانیه.

توی این تاک، درمورد استخدام برنامه نویس هم صحبت میکنه و توضیح میده که چرا استخدام برنامه نویس کلوژر خیلی راحتتر از استخدام برنامه نویس برای جاوااسکریپت و پایتون هست (با توجه به اینکه برنامه نویسهای کلوژر تعدادشون خیلی خیلی کمتره)


نکته‌ای که خیلی دوست دارم بهش اشاره کنم اینه که هردو نسخه‌ی نرم‌افزار در حقیقت جاوااسکریپت بودن.
نسخه‌ی اول با خود js نوشته شده بود و یک فریم بر ثانیه سرعت داشت.
نسخه‌ی دوم با clojurescript نوشته شد که در حقیقت transpile میشه به js و توی همون پلتفورم، سرعتش به ۳۰فریم بر ثانیه میرسه.


#2

میشه در این مورد یکم بهتر بیشتر توضیح بدی ؟ مگر کلوژور اسکریپت به جی اس تبدیل نمیشه در نهایت ؟ آیا کدها رو دوباره بهینه تر نوشتند با کلوژور اسکریپت یا تو این وسط چیزهایی فرق کرده که تاثیر گذاشته ؟مثلا بالا گفتی که بخاطر Mutable بودن دیتا و …

اتفاقا من علاقه زیادی به این کارها دارم و بیشتر توضیح بدی ممنون میشم .

اصلا داستان فرانت اند و بک اند تو این پروژه چیه ؟ یکم گیج شدم


#4

خوب این اولین بار نیست که همچین چیزی رو میبینیم. نمونه‌ی بزرگتر (از نظر کارایی) لایبرری om هست. این لایبرری در حقیقت به برنامه نویسهای کلوژراسکریپت اجازه میده با react کار کنن. (به جای اینکه لازم باشه به روش javascript interop کد بزنن و اسامی کلاسها و توابع react رو بنویسن و صدا بزنن)
راندمان پروژه‌های om نسبت به مشابه‌هاش که با react توی js نوشته شدن، تقریبا دو برابره.
این مساله رو یک بار rick hickey توی یکی از تاک‌ها گفت. بعد از تاک چندتا از برنامه نویسهای خود react (سازندگانش) رفتن سراغ ریچ هیکی و باهاش صحبت کردن ببینن قضیه از چه قراره :joy:
چندتا ورژن بعد، راندمان react بیشتر شد (با تشکر از رهنمودهای ریچ هیکی) ولی هنوزم به نسخه‌ی کلوژریش نمیرسه.
(این صحبتها رو خود ریچ هیکی توی یکی از کنفرانسها گفت فکر میکنم برای سال ۲۰۱۵ بود)


قضیه اینه که کلوژر به شما اجازه‌ی کدنویسی به روشهای غلط رو نمیده. پارادایمهایی که توی ساخت کلوژر درنظر گرفته شدن، همشون با دقت انتخاب شدن. مثلا همین قضیه‌ی immutable بودن همه‌ی دیتاتایپها بجز atom به شما اجازه نمیده دیتاها رو پاک کنید و از اول بنویسید (و یه تایمی از پردازنده گرفته بشه برای gc و reallocate کردن رم) و در عین حال اگه واقعا‌ نیاز به نگهداری state هست (مثل app-db توی re-frame که این هم لایبرری برای ساختن اپ‌های react هست) میتونید از atom استفاده کنید.
و این بخشهای mutable و بخشهایی از کد که side effect دارن رو (همونطور که توی آموزشها میبینید و یاد میگیرید) میذارید یه گوشه که وسط کدهای تمیز و pure function نباشه. (یه جور لایه‌ی abstraction میسازید بین کدهای هسته‌ای و کدهایی که قراره لاگ بگیرن یا محتوایی رو به کاربر نمایش بدن)


بگذریم. بیشتر قضیه همینه.
یه بخش دیگش نحوه‌ی نگهداری دیتاتایپها روی رم و نحوه‌ی دسترسی بهشون هست. (توضیحش یه کم سخته امیدوارم واضح بگم)
توی کلوژر لیست‌ها (مثل array توی زبانهای دیگه) به صورت tree ذخیره میشن با branch factorی به اندازه‌ی ۳۲. توضیحش سخته کشیدنش خیلی سختتر. ولی تصور کردنش سادست. ما هممون میدونیم binary tree چیه. همین درخت باینری رو درنظر بگیرید، به جای اینکه هر نود، ۲تا شاخه داشته باشه، ۳۲تا شاخه داره.

نتیجش این میشه که توی دیتاست‌های کوچیک مثلا ۱۰۰رکوردی، تفاوت چندانی با binary tree نداره توی قضیه‌ی lookup کردن. ولی توی دیتاستهای بزرگ، مثلا ۳۰ ملیون رکورد، عمق درخت باینری ما ۲۴ سطح خواهد بود در حالی که با branching factor 32 عمقش میشه ۴ سطح. مسلما سرچ کردن داخل این لیست درختی، خیلی سریعتر از نسخه‌ی باینریش هست. مثل این هست که بخوایم توی یه باینری تری بگردیم که ۳۰تا عضو داره. بهش میگن almost instant lookup table (اونهم به خاطر اینه که در واقعیت instant وجود نداره و این تقریبا instant هست)

مسلما ریچ هیکی تصمیمات طراحی هوشمندانه‌ی دیگه هم هم گرفته که من خبر ندارم ولی همین یکی توضیح میده که چطوری lookup کردن توی یه رکورد ۱۲ملیون تایی اینقدر سریع بوده و باعث شده به سرعت 30fps برسن.

چیزی که مسلمه اینه که جاوا‌اسکریپت کلا توی ۱۰روز ساخته شده. سازندش اگه آدم فضایی هم بود نمیتونست چیز درستی از آب در بیاد. همینطور که میبینیم یکی از آشغالترین زبانهای برنامه نویسی ساخته شده توسط بشر هست و اگه اسمش شبیه زبان java نبود، شاید هیچوقت معروف نمیشد و ما زندگی زیباتری داشتیم.
یه کامیک باحال درمورد این موضوع

یه نکته‌ای رو هم اینجا بگم.
اینکه میگم بهتره مشکلات در سطح زبان برنامه نویسی حل بشن و نه در سطح لایبرری، دقیقا به خاطر همینه.


فایلهای پزشکی فرمت‌های خاصی دارن. یعنی دستگاه‌های پزشکی به ما png (برای عکسهای دو بعدی) و obj (برای سه بعدی) نمیدن و گرچه داکیومنتهاشون خیلی کم و نایابه، برای اینکه یه نرم‌افزار بنویسیم تا فایلها رو باز کنه نیاز به نوشتن لایبرری داریم. یه لایبرری مثلا مخصوص فایلهای خروجی ماموگرافی. (داکیومنت فایلهای ماموگرافی رو نخوندم ولی با mri آشنایی دارم، بخشی از توضیحتم با توجه به چیزی هست که توی فایلهای mri هست)
فایلها دو مدل هستن. یا به صورت دوتا فایل کنار هم و یا به صورت یک فایل (که این دوتا به هم چسبیدن)
یکی از فایلها metadata هست. مثلا ۳۰۰بایت دیتا درمورد نوع نوشته شدن دیتا در فایل دوم و اینکه چند بعد داره و آیا زمان هم توش درنظر گرفته شده (مثلا آزمایش ۱۵دقیقه طول کشیده و تاثیر یک دارو یا یک محرک عصبی رو نشون میده روی بافت) و…
بعد از خوندن این فایل، تازه میفهمیم برای خوندن فایل دوم چطوری دیتاها رو تکه تکه جدا کنیم و بریزیم توی متغیرهای زبان برنامه نویسیمون.
فایل دوم اصولا به شکل ماتریس عددی هست. اعدادی که توی اون تاک نشون داده شد int 16 بودن (بین ۰ تا ۶۵۵۳۵) که فشردگی بافت (یا اطلاعات دیگه مثل حرارت اون نقطه) رو نشون میده. (دیتای دستگاههای mri معمولا ۵ بعدی هست. طول، عرض، ارتفاع، زمان، دما. ممکنه متغیرهای دیگه‌ای هم داشته باشه)

مسلما مانیتور ما برای نمایش همچین چیزی ساخته نشده. مانیتورها میتونن رنگهای ۸بیتی رو نمایش بدن (بین ۰ تا ۲۵۵ و سه رنگ)
برای اینکه بتونیم یه int16 رو به int8 تبدیل کنیم، یا باید به روش ریاضی عمل کنیم (مثلا برای تبدیل ۶۵۲۴۴ از int16 به int8):

65244*255/65535 = 254

یا میتونیم باینری این عدد رو به تعداد ۸ بیت به سمت راست شیفت کنیم:

(65244)10 = (1111111011011100)2
(254)2 = (11111110)2

محاسبه‌ی بالایی سختتره و البته باید نتیجه رو گرد کنیم (اعشار به دست میاد) ولی پایینی راحتتره و فقط بیت‌های کم ارزش رو میندازیم دور.

حالا این عمل برای این بود که بخوایم کل تصویر رو روی مانیتور معمولی نمایش بدیم. (که نسبتا خیلی کار راحتیه)
حرکت اصلی زمانیه که بخوایم window leveling داشته باشیم.
توی window leveling چندتا کار انجام میشه که نوشتنش واقعا سخته و نیاز به این داره که روی یه نرم‌افزار به صورت ویدیویی اینکار رو انجام بدم و نتیجه (به همراه histogram) رو نشونتون بدم. پس فقط یکیشو میگم:

بعضیوقتها (زمانی که بخوان یه بافت که تراکمش خیلی کمتر یا بیشتر از میانگین هست رو با دقت ببینن) لازم میشه که فقط ۲۵۵تا مقدار دیده بشه و بقیه‌ی مقادیر (مقادیری که بالاتر یا پایینتر هستن) نشون داده نشه.
به این صورت که مثلا اعداد ۰ تا ۲۵۵ به همون شکل که توی دیتای اصلی هستن نشون داده بشن و اعداد بالاتر تبدیل بشن به ۲۵۵ یا مثلا اعداد ۳۵۰ تا ۶۰۵ تبدیل بشن به ۰ تا ۲۵۵ و هرچی بالاتر هست تبدیل بشه به ۲۵۵ و هرچی پایینتر هست به ۰.
یه سری حالتهای دیگه‌هم داره که هرچی فکر میکنم نمیتونم توی متن توضیح بدم.

یه سری ویرایشهای دیگه هم روی تصاویر انجام میشه. مثلا تغییر روشنایی و کنتراست، اعمال یه سری kernel روی تصاویر که اینها هم همشون ویرایش کردن تمام پیکسلها (یک لیست ۱۲ملیون تایی عدد) هستن.
کرنل‌هایی که استفاده میشه معمولا برای edge detect و sharpen هستن. یعنی کرنل‌های 3x3 و 5x5 که وضعیت رنگ هر پیکسل رو با توجه به ۸ یا ۲۴ پیکسل اطرافش تغییر میده.


این پروژه کلا یه front-end هست که دکترها میان تصویر رو داخلش باز میکنن و تومور رو پیدا میکنن و علامت میزنن.
بعد تصویر به اضافه‌ی محل اون علامتها توی دیتابیس ذخیره میشه. (کلا بک-اند کارش اینه که این دیتا رو ذخیره کنه. همین)

بعدا میان از این دیتا استفاده میکنن برای تعلیم neural network و دیتا رو میدن به کامپیوتر و میگن «این عکس تومور خوش‌خیم است» و «این عکس تومور بدخیم است»
و بقیه‌ی ماجرای یادگیری ماشینی.

در نهایت احتمالا یه سیستمی ساخته میشه که هر بیماری میره پیش پزشک و ماموگرافی میکنه، اون تصویر فرستاده میشه به یه سرور و اون سرور میگه «این به احتمال ۹۳درصد خوش‌خیم است» و دیگه نیازی به نمونه‌برداری نیست (نمونه‌برداری طولانیه و دردناک. در بعضی مواقع خطرناک)
مشکل هم از اونجای شروع میشه که دکترها (انسانها) نمیتونن از روی ۴تا عکس تشخیص بدن یه تومور خوش‌خیم هست یا نه. چون تجربه‌ی آنالیز چند ملیون تومور رو نداشتن. (ولی کامپیوتر به لطف این سیستم، تجربش رو داره)


چقدر زیاد نوشتم! کاش میتونستم توی زمان سفر کنم و این توضیحات رو یک سال پیش به خودم بدم.


#5

توضیحات خیلی عالی و مفیدی بود ممنون ،هیچ منبعی وجود داره که کلوژور رو فقط برای جی اس آموزش بده ؟ چون هرچقدر حساب کتاب میکنم فعلا وقت و گنجایش کلوژور بک اند رو ندارم و نمیتونم کتابخونه ها و … یاد بگیرم .

Number * 0xFF /0xFFFF
احساس میکنم قبلا برنامه نویس سطح پایین بودی و زیاد با میکروکنترلر و چیزهای سیستی ور رفتی


#6

خوب مساله اینجاست که کلوژر کلا یکیه.
شما یه کد مینویسید که میتونه تبدیل به js بشه یا تبدیل به java و توی پلتفورم هرکدومشون اجرا بشه. میشه گفت multi platformترین حالت ممکن.
هیچ فرقی بین کدهای clojure و clojurescript وجود نداره. بجز زمانی که میخوایم به صورت مستقیم از زبان میزبان استفاده کنیم. مثلا وقتی میخوایم تایم رو از js یا java بگیریم. در اون صورت باید توابع یا کلاسهای اون زبان رو صدا بزنیم و این قسمت از کدمون یه کم فرق میکنه.
لایبرریهایی که برای کلوژر نوشته شدن هم تقریبا همشون برای هردو پلتفورم قابل استفاده هستن. یه سریاشون اختصاصی برای جاوا یا جاوااسکریپت ساخته شدن. مسلما re-frame که باهاش میشه وبسایت‌های spa ساخت و از react استفاده میکنه، نمیتونه توی پلتفورم جاوا ازش استفاده کرد و فقط توی clojurescript کار میکنه.
در غیر این صورت همش یکی هست.

البته باید بگم که هدف اینطوری بوده. در واقع یه جاهایی شاید یه کم تفاوت وجود داشته باشه. یکیشو چندوقت پیش خودم پیدا کردم که احتمالا تا الآن درست شده.

من خودم brave clojure رو خوندم و با چیزهایی که اونجا یاد گرفتم یه پروژه‌ی clojurescript نوشتم (برای تست و یادگیری یه سری چیزا). تنها چیز اضافه‌ای که نیاز داشتم این بود که بدونم برای ساختن پروژه‌ی re-frame با استفاده از leiningen باید چه کامندی رو بزنم و یه سری چیزهای این مدلی.
و بعدش یه پروژه‌ی کامل (front-end + back-end) انجام دادم و تنها چیز جدیدی که لازم بود یاد بگیرم، داکیومنت لایبرریهایی بود که میخواستم استفاده کنم. بقیش یکی بود. (البته پروژه یه جاش گیر کرده و فعلا ولش کردم تا یه کم بیشتر با کلوژر مخصوصا core.async آشنا بشم)

جالبش اینجاست که یه سری کدها دقیقا توی front-end و back-end کپی میشن. مثل کدهای مربوط به clojure.spec که برای form validation استفاده میشن.

تا دلت بخواد :sunglasses:
اصلا یه مدت روی Bare machine زندگی میکردم. (میکروکنترلرهای ۸بیتی بدون سیستم عامل)
فقط این پایتون یه کم منو بد عادت کرده :sweat_smile:


#7

اگه اینطوری باشه خوبه پس چون خودمم وقت میکنم همین کتابو یواش یواش میخونم .
یواش یواش میرم جلو فقط یه پروژه لعنتی هست از دست اون راحت بشم بهتر میتونم یاد بگیرم .

میدونی انگولک کردن سخت افزار و موارد مشابه یه هیجان دیگه داره ، من یه زمانی از اینکه میتونستم داخل فلاپی برنامه ای بزارم که بدون هر سیستم عاملی کار کنه از خوشحالی پرواز میکردم ، اسمشم گذاشته بودم سیستم عامل :joy::joy: یه بوت لودر داشت که خودمم نمیدونم چی بود کدشو کپی کرده بودم ( اسمبلی خیلی تلاش کردم یاد بگیرم) بعد این بوت لودر برنامه مسخره منو اجرا میکرد و تونسته بودم چیزهای خیلی خیلی ساده ای هم توسعه بدم که اسمشون رو مثلا بشه گذاشت برنامه و این سیستم عامل من اجرا کنه :joy: خودشم برنامه های سیستم عامل من نهایت پیشرفتشون ضرب و تقسیم و چیزهای مسخره بود و برای مولتی تسک و … مخم نمیکشید ، با هزار مصیبتم موفق شده بودم رزولیشن 1024 * 768 رو بیارم روی برنامه ، متن ها رو سبز پر رنگ کرد بودم احساس میکردم هم خودم هم سیستم عاملم خیلی خفنه :joy: از نظر خودم سیستم عامل بود اما در حقیقت یک برنامه ساده بود
یادش بخیر عجب دورانی بود ، اما وب خیلی هیجان انگیزتره و مثل سیاه چاله ای هست که هرچقدر میره جا داره تموم نمیشه و بیشتر کنجکاوترت میکنه

پایتون سرعت توسعش عالیه ، اگر یک روز احساس کنی که ازش قراره سیر بشی که محاله، اون روز به جی اس یا جاوا فکر کن سریع توبه میکنی برمیگردی پایتون