Categories
Diary

Pyodide – แนะนำการเริ่มต้นใช้งาน และการพล็อตกราฟเพื่อแสดงบนหน้าเว็บ

Python เป็นภาษาคอมพิวเตอร์ภาษาหนึ่งที่ได้รับความนิยมภาษาหนึ่ง โดยภาษานี้เป็นภาษา General-purpose ท่เน้นการอ่านตัวโค้ดได้ง่าย รวมถึงง่ายต่อการเรียนรู้ ภาษานี้ปกติจะไม่ได้ออกแบบเพื่อการทำงานบนหน้าเว็บไซต์แบบจาวาสคริป อย่างไรก็ดีมีทีมงานกลุ่มหนึ่งใน Mozilla ที่พัฒนาตึวโค้ด CPython ให้ทำงานอยู่บน Webassembly ที่ออกแบบมาให้รันตัวโค้ดที่ได้รับการคอมไพล์บนหน้าเว็บไซต์

เมื่อตัวโค้ดได้รับการคอมไพล์ให้อยู่บน Webassembly แล้ว ตัวโค้ดที่เขียนในรูปแบบภาษา Python จะเข้าถึง Web APIs ทั้งหลายแหล่ที่อยู่บนเว็บเบราวเซอร์ได้นั่นเอง

ตัว Pyodide นี้นอกจากคอมไพล์ตัว Python แล้ว ยังรวบรวมไลบรารี Numpy, Matplotlib, Pandas, scikit-learn และอื่น ๆ อีกหลายสิบไลบรารี แถมยังรองรับการติดตั้งไลบรารี Python เพิ่มเติมได้โดยการติดตั้งผ่าน micropip

การเริ่มต้นการใช้งาน Pyodide

เราเริ่มต้นการใช้งานได้โดยการนำเข้าไลบรารี Pyodide ผ่านการแนบลิ้งค์ CDN ลงบนหน้า HTML ผ่านแท็ก script

  <script src = "https://cdn.jsdelivr.net/pyodide/v0.19.1/full/pyodide.js"></script>

หรือเรียกผ่านการใช้คำสั่ง import แบบ dynamic โดย

await import('https://cdn.jsdelivr.net/pyodide/v0.19.1/full/pyodide.js');  await import('https://cdn.jsdelivr.net/pyodide/v0.19.1/full/pyodide.js');

เมื่อนำเข้าไลบรารีนี้แล้ว เราเริ่มต้นการทำงานของ Pyodide ได้โดย

  let pyodide = await loadPyodide({
      indexURL : "https://cdn.jsdelivr.net/pyodide/v0.19.1/full/"
  });

การโหลดไลบรารี และติดตั้งไลบรารีเพิ่มเติม

เมื่อเริ่มต้นการทำงานแล้ว เราสามารถโหลดไลบรารีที่ต้องการได้โดยคำสั่ง loadPackage ตามด้วยไลบรารีที่ต้องการโดยเขัยนตามด้านล่างนี้

  await pyodide.loadPackage("< ชื่อไลบรารี >");

ส่วนกรณีที่ต้องติดตั้งไลบรารีเพิ่มเติม เราสามารถติดตั้งได้โดยการใช้ micropip ซึ่งทำได้โดยการโหลดไบรารีผ่านการใช้ loadPackage และการเรียกใช้คำสั่ง micropip.install ผ่านการใช้คำสั่ง runPythonAsync ที่ให้เราเริ่มต้นการทำงานโค้ดไพทอนแบบ Asynchronous ได้ครับ

เราเขียนโค้ดได้ตามด้านล่างนี้

  await pyodide.loadPackage("micropip");
  await pyodide.runPythonAsync(`
      import micropip
      micropip.install('< ไลบรารีที่ต้องการ ตัวอย่างเช่น seaborn >')
  `);

การพล็อตกราฟด้วย Matplotlib + Seaborn แล้วแสดงบนแท็ก img

เราสามารถพล็อตกราฟได้โดยการใช้ไลบรารี Matplotlib และไลบรารีที่พัฒนาต่อยอดจากไลบรารีก่อนหน้าอย่าง Seaborn ที่ทำให้การพล็อตกราฟเป็นไปได้โดยง่ายขึ้น

ปกติเวลาที่เราพล็อตกราฟเสร็จแล้ว เราจะแสดงบนหน้าต่างใหม่ หรือแสดงบนหน้าเว็บใน Jupter Notebook ได้โดยการพิมพ์คำสั่งผ่านการเรียก pyplot ที่อยู่ใน matplotlib ได้โดย

  pyplot.show()

หรือเซฟเป็นไฟล์ .png หรืออะไรแนวนี้ได้โดยการใช้คำสั่ง

  pyplot.savefig("< ที่อยู่ไฟล์ >")

อย่างไรก็ดี เมื่อเราเขียนโค้ดแล้วนำมาทำงานบนหน้าเว็บไซต์ผ่านการใช้งาน Pyodide แล้ว มันก็จะไม่สามารถแสดงหน้าต่างใหม่ได้ หรือจะเซฟแบบนั้นโดยตรงก็ไม่น่าจะได้ ดังนั้นแล้วมีวิธีหนึ่งที่นำผลที่ได้จากการพล็อตกราฟมาแสดงบนหน้าเว็บไซต์ได้โดยการแปลงตัวกราฟที่พล็อตให้อยู่ในรูป base64 ที่สามารถเพิ่มลงไปใน src attribute ได้เลย

นอกเหนือจากนี้ เรายังนำ base64 ไปแปลงให้เป็นรูปแบบ blob เพื่อนำไปอัพโหลด หรือเซฟไฟล์สำหรับการนำไปใช้งานต่อได้อีก แต่ในบทความนี้ขอแนะนำถึงการพล็อต แล้วส่งออกให้อยู่ในรูปแบบ base64 เพื่อนำมาแสดงบนแท็ก img ครับ

เราทำได้ในขั้นตอนตัวอย่างตามด้านล่างนี้ครับ

1. เริ่มต้น Pyodide + เรียกใช้ไลบรารี

เราเริ่มต้นการทำงานของ Pyodide และเรียกใช้งานไลบรารี numpy, matplotlib, seaborn (ที่ติดตั้งโดย micropip) ได้ตามหัวข้อก่อนหน้าครับ โดยเราเขียนโค้ดออกมาเป็น

  async function importLib()
  {
      await import('https://cdn.jsdelivr.net/pyodide/v0.19.1/full/pyodide.js');

      let pyodide = await loadPyodide({
          indexURL : "https://cdn.jsdelivr.net/pyodide/v0.19.1/full/"
      });

      await pyodide.loadPackage("numpy");
      await pyodide.loadPackage("matplotlib");
      await pyodide.loadPackage("micropip");
      await pyodide.runPythonAsync(`
          import micropip
          micropip.install('seaborn')
      `);

      return pyodide;
  }

เราอธิบายโค้ดตามข้างบนนี้ โดยเป็นฟังก์ชัน importLib ที่เริ่มมาจากการนำเข้าไลบรารี เริ่มต้นการทำงานของ Pyodide แล้วนำเข้าแพคเกจเข้าไปในระบบโดยผ่านการใช้คำสั่ง loadPackage และ micropip.install (ที่ผ่านการใช้คำสั่ง runPythonAsync)

2. เริ่มการพล็อตกราฟ

เริ่มต้นการพล็อตกราฟโดยใช้คำสั่งในไลบรารี

  • numpy มีไว้สำหรับการจำองข้อมูลขึ้นมา
  • matplotlib + seaborn มีไว้ใช้สำหรับการพล็อตกราฟ
  • base64 และ io มีไว้สำหรับการส่งออกกราฟให้อยู่ในรูปไบนารีที่แปลงออกมาเป็น base64

เราเขียนโค้ดได้โดยตามขั้นตอนตามด้านล่างนี้

2.1 เรียกใช้ไลบรารี

เราเรียกใช้ไลบรารี numpy, matplotlib, seaborn, base64 และ io ผ่านการพิมพ์คำสั่ง Python ตามด้านล่างนี้

  import numpy as np
  import seaborn as sns
  from matplotlib import pyplot as plt
  import base64, io

2.2 จำลองข้อมูลขึ้นมา

เราสามารถจำลองข้อมูลขึ้นมาได้โดยผ่านการสุ่มตัวเลขโดยผลลัพธ์ที่ต้องการเป็นในรูปแบบอาเรย์ที่สามารถพล็อตกราฟได้โดยใช้คำสั่งในไลบรารี numpy โดยในที่นี่เราจะสุ่มโดยการใช้ Normal Distribution (หรือ Gaussian Distribution) ผ่านการใช้คำสั่ง Python ตามด้านล่างนี้

    mu, sigma = 0, 0.1
    data = np.random.normal(mu, sigma, 1000)

เราจะได้อาเรย์ที่ผ่านการสุ่มแบบ Normal Distribution ที่มีตัวเลขทั้งหมด 1,000 ค่าครับ โดยกำหนดค่าเฉลี่ยเท่ากับ 0 และค่าส่วนเบี่ยงเบนมาตรฐานเท่ากับ 0.1

2.3 พล็อตกราฟ

เรานำอาเรย์ที่ผ่านการสุ่มแบบ Normal Distribution มาพล็อตกราฟได้โดยการใช้คำสั่งในไลบรารี matplotlib + seaborn ได้โดยการพิมพ์โค้ด Python ตามด้านล่างนี้

    plt.xlabel("Sample")
    plt.ylabel("Count")
    sns.histplot(data=data, color="y", kde = True)

่จากตัวโค้ดเรากำหนดข้อความในแกน x เป็น Sample และแกน y เป็น Count แล้วพล็อตกราฟให้อยู่ในรูป Histrogram ที่มีพื้นหลังตัวกราฟเป็นสีเหลือง ร่วมกับกำหนด Distribution Cruve ผ่านการคำนวณโดย Kernel Density Estimation

2.4 ส่งออกกราฟให้อยู่ในรูป base64

เราสามารถส่งออกกราฟที่พล็อตผ่าน matplotlib + seaborn ให้อยู่ในรูป base64 ที่สามารถแสดงได้ในแท็ก img โดยการพิมพ์โค้ด Python ตามด้านล่างนี้

    buf = io.BytesIO()
    plt.savefig(buf, format='png')
    buf.seek(0)
    img_str = 'data:image/png;base64,' + base64.b64encode(buf.getvalue()).decode('UTF-8')

โค้ดตามด้านบนนี้ต้องการให้ส่งออกกราฟที่พล็อตได้ให้อยู่ในรูปไบนารีที่อยู่ในฟอร์แมต png และอยู่ในหน่วยความจำคอมพิวเตอร์ แล้วแปลงตัวไบนารีให้อยู่ในรูป base64

2.5 นำมาเขียนรวมกัน

เราสามารถเขียนรวมกันได้ตามด้านล่างนี้ครับ

    import numpy as np
    import seaborn as sns
    from matplotlib import pyplot as plt
    import base64, io

    mu, sigma = 0, 0.1
    data = np.random.normal(mu, sigma, 1000)

    plt.xlabel("Sample")
    plt.ylabel("Count")
    sns.histplot(data=data, color="y", kde = True)
        
    buf = io.BytesIO()
    plt.savefig(buf, format='png')
    buf.seek(0)
    img_str = 'data:image/png;base64,' + base64.b64encode(buf.getvalue()).decode('UTF-8');

2.6 นำมาเขียนเพื่อให้ทำงานผ่านการใช้ไลบรารี Pyodide

เราเขียนให้ทำงานผ่าน Pyodide ได้ตามด้านล่างนี้ผ่านการใช้คำสั่งใน runPythonAsync แล้วนำค่าตัวแปรที่ได้ใน Python ออกมาให้ใช้งานผ่าน JavaScript ได้โดยเขียนโค้ดตามด้านล่างนี้

  async function drawGraph(pyodide)
  {
    await pyodide.runPythonAsync(`
        import numpy as np
        import seaborn as sns
        from matplotlib import pyplot as plt
        import base64, io

        mu, sigma = 0, 0.1
        data = np.random.normal(mu, sigma, 1000)

        plt.xlabel("Sample")
        plt.ylabel("Count")
        sns.histplot(data=data, color="y", kde = True)

        buf = io.BytesIO()
        plt.savefig(buf, format='png')
        buf.seek(0)
        img_str = 'data:image/png;base64,' + base64.b64encode(buf.getvalue()).decode('UTF-8')
        plt.clf()
    `);

    let b64result = pyodide.globals.get("img_str");
    return b64result;
  }

เมื่อพิมพ์เสร็จแล้ว เราสามารถเรียกใช้ฟังก์ชัน drawGraph(<ไลบรารี pyodide ที่โหลดมาแล้ว>);

2.7 นำโค้ดที่ได้มาเขียนรวมกันเพื่อแสดงกราฟบนแท็ก img

เรานำโค้ดที่เขียนอยู่ในรูปฟังก์ชันตามข้างบนนี้เพื่อให้ได้ภาพมาแสดงบนแท็ก img ได้โดย

  async function onPageLoaded(event)
  {
      let img_canvas = document.getElementById('< id ของแท็ก img >'),
      let pyodide = await importLib();
      let b64result = await drawGraph(pyodide);
      img_canvas.src = b64result;
  }

  window.addEventListener('DOMContentLoaded', onPageLoaded);

เมื่อนำมารวมกันแล้วทดลองรันบนเว็บเบราวเซอร์ เราจะพบว่ากราฟจะพล็อตเมื่อหน้าเว็บเริ่มโหลด แล้วเมื่อเว็บโหลดเสร็จแล้ว เราจะเห็นกราฟที่อยู่ในแท็ก img ออกมาเป็นตามภาพด้างล่างนี้ครับ

Graph Result

เป็นไงล่ะ เราจะเห็นกราฟ Histrogram ที่ผ่านการสุ่มแบบ Normal Distribution นั่นเองครับ

สำหรับตัวอย่างหน้าเว็บที่ทำเสร็จแล้ว ผู้อ่านสามารถเข้าไปดูที่ Demo นี่ได้เลยครับ

จุดสังเกต

ตัวเว็บทำงานได้ช้าเนื่องมาจาก

  1. ตัวโค้ดตามด้านบนต้องดาวน์โหลดไลบรารี pyodide จาก CDN
  2. ตัวโค้ดต้องเริ่มต้นการทำงาน pyodide ร่วมกับโหลดไลบรารีที่จำเป็น่ผ่านการดาวน์โหลดไลบรารีบน server
  3. พล็อตกราฟผ่านการใช้คำสั่ง Python ที่อยู่บน pyodide อีกที

จากจุดสังเกตตามข้างบนนี้ สำหรับผู้อ่านที่มาอ่านบทความนี้แล้วจะนำไปใช้ อาจจะต้องพิจารณาประสิทธิภาพในการทำงานอีกทีครับ

ถ้าเป็นไปได้ อาจจะลองใช้ไลบรารีที่มีอยู่แล้วอย่าง Chart.js หรือ Plotly ก่อนก็ได้ครับผม ทั้งสองไลบรารีที่กล่าวมาในย่อหน้านี้ก็สามารถพล็อตกราฟให้สวยงามได้ตามใจชอบได้เช่นกัน

By Kittisak Chotikkakamthorn

My name is Nick (or Kittisak Chotikkakamthorn) who is working as a research assistant in biomedical engineering, Mahidol University. I research artificial intelligence in medicine about computer vision, which I have to write the code. It is the skill that I am practicing.

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

Privacy Preferences

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

Allow All
Manage Consent Preferences Save