• Komputer,  Programming,  Software

    OCR

    Beberapa waktu lalu sempet ngasih komentar di twitter atas postingan orang yang isinya protes atas data dari website Kementrian Kesehatan tentang vaksinasi Covid-19. Protes karena data yang disajikan berupa image (gambar) dan menurut orang yg posting data yang disajikan tidak “properly consumable by the public?”

    Saya pikir kalau orang gak suka data ditampilkan dalam bentuk image, berarti orang itu mau “mengambil” data itu lalu (mungkin) diolah dan disajikan dalam bentuk lain. Otomatis saya kasih tanggapan “Coba ambil gambarnya, split utk di-OCR utk dapet teksnya”. Jawaban khas “data scraper” 🙂

    Eh ternyata orangnya gak suka jawaban saya, dia bales lagi “The point is that the public *don’t* have to do the things you mentioned”. Saya setuju, masyarakat umum (“the public”) gak mungkin harus nge-OCR data dalam bentuk image. “The public” cukup lihat saja langsung image-nya, dan harusnya gak peduli tentang bagaimana data ditampilkan, yang penting kelihatan pake mata. Intinya bagi saya, orang yang peduli dengan bagaimana data ditampilkan bukan bagian dari “the public”.

    Terlepas dari perdebatan di atas, jiwa programmer saya terus tergelitik untuk nyoba bikin program yang melakukan OCR ke data vaksinasi covid-19 yang disebut di atas. Langkah pertama yg saya lakukan adalah: cari pake google dengan kata kunci “javascript ocr“.

    Yang muncul paling atas tentu saja “Tesseract.js“, proyek tesseract memang ngerajain bidang OCR. Tapi saya mau nyari OCR yg “slim”, yang cukup dengan 1 file js dan sangat mudah digunakan. Dan ternyata apa yg saya mau muncul di halaman pertama juga, namanya Ocrad.js.

    Ocrad.js sebenernya bukan benar2 implementasi teknik OCR pake javascript, karena menurut yang buat, Ocrad.js itu hasil konversi program Ocrad yang dibuat pake C++ ke WebAssembly pake Emscripten. Jadi jangan harap bisa “ngintip” cara OCR pake Javascript murni di Ocrad.js.

    Oke, langsung aja, ini langkah2 bikin program OCR untuk nge-scrap data image dari Kemenkes tentang vaksinasi covid-19.

    1. Install Node.js
    2. Clone Ocrad.js dari github. Sebenernya yang dibutuhkan cuma 1 file, yaitu file ocrad.js, tapi ada baiknya kalo di-clone semua aja karena ada contoh2nya yang bisa dipelajari.
    3. Buat 1 folder untuk programnya, lalu buat dua file (yang saya sertakan di bawah) di dalam folder tsb. Sebenernya yang paling penting cuma file vaksinasi.js, file package.json cuma untuk mempermudah instalasi node-module yang dibutuhkan.
    4. Buka command prompt, ganti folder aktif ke folder program di atas, terus ketik perintah:
      • Kalau ada file package.json, cukup pake 1 perintah: npm install
      • Kalau tidak ada file package.json ada dua perintah:
        • npm i --save canvas
        • npm i --save request
    5. Sesudah itu tinggal jalankan script-nya: node vaksinasi.js

    File: package.json

    {
      "name": "vaksinasi-covid19",
      "version": "1.0.0",
      "description": "OCR image data vaksinasi covid-19 dari Kemenkes RI ",
      "main": "vaksinasi.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "Ureh",
      "license": "ISC",
      "dependencies": {
        "canvas": "^2.7.0",
        "request": "^2.88.2"
      }
    }

    File: vaksinasi.js

    var OCRAD = require('./ocrad.js');
    var Canvas = require('canvas');
    var fs = require('fs');
    var request = require('request');
    
    var Image = Canvas.Image;
    var png = "https://www.kemkes.go.id/web/assets/images/infografis/vaksin/VAKSINASI-Infografis.png";
    png = "http://localhost/infografis.png"; // for local testing
    
    function downloadFile(url, dest, cb) {
      console.log('Downloading %s', url);
      var file = fs.createWriteStream(dest);
    
      var req = request.get(url);
      req.pipe(file).on('error', function(err) { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result)
        if (cb) cb(err.message);
      });
    
      file.on('finish', function() {
        file.close(cb);  // close() is async, call cb after close completes.
      });
    }
    
    console.log('Starting OCR');
    
    downloadFile(png, 'infografis.png', function(){
    	fs.readFile(__dirname + '/infografis.png', function(err, src) {
    	  if (err) {
    		throw err;
    	  }
    
    	  var dw = 383;
    	  var dh = 439;
    	  var xl = 4;
    	  var xl2 = 4;
    
          var img = new Image();
    	  img.src = src;
    
    // JUDUL ATAS
    	  console.log('\n');
    	  var c0 = new Canvas.createCanvas(1280*xl, 195*xl);
    	  var x0 = c0.getContext('2d');
    	  x0.drawImage(img, 0,0,1280,195, 0,0,1280*xl,195*xl);
    	  console.log(OCRAD(c0));
    
    // PANEL KIRI
    	  console.log('\n');
    	  var c1 = new Canvas.createCanvas(dw*xl, dh*xl);
    	  var x1 = c1.getContext('2d');
    	  x1.drawImage(img, 35,207,dw,dh, 0,0,dw*xl,dh*xl);
    	  console.log(OCRAD(c1));
    
    // PANEL TENGAH
    	  console.log('\n');
    	  var c2 = new Canvas.createCanvas(dw*xl2, dh*xl2);
    	  var x2 = c2.getContext('2d');
    	  //x2.filter = 'grayscale(1) invert(1)';
    	  x2.drawImage(img, 447,207,dw,dh, 0,0,dw*xl2,dh*xl2);
    
    	  // konversi ke hitam-putih dan di invert
    	  // karena text putih gak kebaca
    	  // filter di canvas context gak jalan
    	  const imgData = x2.getImageData(0, 0, dw*xl2, dh*xl2);
    	  for (i = 0; i < imgData.data.length; i += 4) {
    	  	let count = imgData.data[i] + imgData.data[i + 1] + imgData.data[i + 2];
    		let colour = 255;
    		if (count > 500) colour = 0;
    
    		imgData.data[i] = colour;		// red
    		imgData.data[i + 1] = colour;	// green
    		imgData.data[i + 2] = colour;	// blue
    		imgData.data[i + 3] = 255;		// alpha
    	  }
    	  x2.putImageData(imgData, 0,0);
    	  console.log(OCRAD(c2));
    
    // PANEL KANAN
    	  console.log('\n');
    	  var c3 = new Canvas.createCanvas(dw*xl, dh*xl);
    	  var x3 = c3.getContext('2d');
    	  x3.drawImage(img, 859,207,dw,dh, 0,0,dw*xl,dh*xl);
    
    	  // konversi ke hitam-putih tdk mempengaruhi output OCR
    	  /*
    	  const imgData3 = x3.getImageData(0, 0, dw*xl, dh*xl);
    	  for (i = 0; i < imgData3.data.length; i += 4) {
    	  	let count = imgData3.data[i] + imgData3.data[i + 1] + imgData3.data[i + 2];
    		let colour = 0;
    		if (count > 300) colour = 255;
    
    		imgData3.data[i] = colour;
    		imgData3.data[i + 1] = colour;
    		imgData3.data[i + 2] = colour;
    		imgData3.data[i + 3] = 255;
    	  }
    	  x3.putImageData(imgData3, 0,0);
    	  */
    	  console.log(OCRAD(c3));
    
    	});
    });

    Sekarang kita bahas programnya, program ini adalah hasil modifikasi dari contoh Ocrad.js.

    Baris 1 s.d. 8 adalah inisialisasi variabel, dimana di baris ke-8 saya set ulang variabel png supaya program mengambil gambar dari localhost, karena saya akan mencoba mengulang-ulang program dengan image yang sama biar gak membebani servernya kemenkes.

    Baris 10 s.d. 23 adalah fungsi untuk mengunduh file lalu menjalankan callback saat proses pengunduhan selesai. Fungsinya dapet dari codota.com.

    Bahasannya segitu dulu aja yah. Lain kali disambung lagi.

  • Komputer,  Programming

    Doodle

    Menurut Wikipedia, doodle itu :

    DoodleSimple drawing

    A doodle is a drawing made while a person’s attention is otherwise occupied. Doodles are simple drawings that can have concrete representational meaning or may just be composed of random and abstract lines, generally without ever lifting the drawing device from the paper, in which case it is usually called a “scribble”.

    Nah, ini kalo saya (sebagai programmer) bikin doodle:

    Di post sebelumnya saya udah pernah bilang kalo saya seneng banget yang gaya retro, pake text-mode. Dan sebelumnya saya udah pernah bikin simulasi terminal di http://sedjat1.dx.am, nah waktu di kantor saya diminta untuk ngoprek2 masalah canvas saya iseng2 nyari2 info tentang canvas di javascript.

    Pas kebetulan saya kepikiran ide untuk bikin game simulasi hacking dengan UI text-mode, saya cari2 deh ada gak sih yg bisa simulasiin ui text kaya jaman DOS dulu pake canvas javascript. Ternyata banyak banget. Tapi rata-rata bener2 simulasi terminal aja, malahan ada yg bener2 bisa jalanin program di terminal itu.

    Yang lebih mantap lagi saya nemu web http://www.pcjs.org yang isinya emulator PC yang dibuat pake javascript. Yg ini bener2 bisa ngejalanin program DOS jaman dulu, malah bisa jalanin Windows 95. Tapi emang kalo dipikir-pikir, dulu waktu pertama kali saya pake Windows 95 juga prosesornya cuma 486 + RAM 16MB. Kalo dibandingin sama resource yang dipake web browser jaman sekarang, jauh banget, jadi wajar aja kalo secara hardware memang bisa untuk simulasiin PC jadul.

    Akhirnya, saya nemuin titik terang, ternyata ada juga yang pengen make UI mode text di browser, tapi cuma sekadar untuk UI doang (persis banget yg saya mau). Projectnya ada di Github, namanya ASCII-UI (liat contohnya di https://ascii-ui.danikaze.com/basic/basic.html).

    Tapi kendalanya ada aja. Waktu mau make, liat contohnya, kok gak segampang yang saya bayangin. Dibikin pake TextScript yang harus “dicompile” dulu jadi javascript, ribet (mungkin nanti kalo kepala saya udah normal lagi saya coba-coba lagi).

    Akhirnya dengan terpaksa, saya bikin sendiri. Karena dulu saya pertama kali belajar pake Turbo Pascal, maka saya bikin class dengan method2 yang mirip2 bahasa Pascal kaya: GotoXY, Write, Writeln, ClrScr (tapi karena ini javascript, jadi case sensitive).

    Karena baru tahap awal, jadi belom banyak yg dibuat, yg pasti udah ada cursornya yg ngedip2, bisa geser pake arrow key, layar bisa scroll ke atas kalo udah sampe bawah “layar”. Udah bisa set warna teks dan backgroundnya. Terus 1 lagi fungsi yang lumayan bikin enak adalah window. Untuk dapetin keydown nya, udah ada bindingnya yg bisa di custom, jadi pergerakan cursor nggak diatur di classnya.

    Ada 1 hal yg belum di implementasi. Mode teks di DOS nyimpen tiap sel (yg 80×25) dalam 2 byte, 1 byte untuk kode ascii (antara 0 sampai 255) dan 1 byte untuk warna (4 bit utk warna teks dan 4 bit utk warna latar, tapi lupa urutannya yg mana), jadi program bisa ngeset warna dari sebuah “titik sel” tanpa merubah karakter yang sudah ditampilkan.

    Dalam canvas javascript, tiap titik (pixel) disimpan dalam 4 sel array (kalo pake getImageData) [R (red), G (green), B (blue), A (alpha)]. tiap sel teks saya simulasikan dimana defaultnya Height = Font Size, dan Width = 0.5 * Height. Jadi untuk 1 karakter dengan ukuran 20pt akan disimpan dalam array 10×20 pixel. Awalnya saya coba ganti warna dengan mengubah warna per pixel, tapi gak nemuin hasil yang pas, karena saya pakai asumsi bahwa pixel pertama adalah warna latar dan selain warna latar dianggap warna karakter. Akhirnya saya belum menerapkan ganti warna. Rencananya nanti saya implementasikan dengan menyimpan isi canvas dalam array karakter dan warna.

    Masalah ketemu lagi waktu saya mau menampilkan ascii art. Bikin simulasi mode teks tanpa menampilkan ascii-art bagaikan sayur tanpa garam kata peribahasa — kurang pas. Nah waktu ascii-art nya pake karakter “pattern” (kalo gak salah dulu karakter yg dipake itu #176, #177, #178, #219) ada anomali waktu “layar” scrolling atau waktu menghapus karakter di layar (pake backspace).

    Begitu diperhatiin, ternyata untuk karakter2 itu digambar melewati ukuran karakter yang saya tetapkan, dan kalau saya tuning geser 1 atau 2 pixel untuk ukuran karakternya akan mempengaruhi karakter yang lain, jadi aneh. Sesudah ngubek2 berbagai font, akhirnya saya putusin untuk nge-crop karakter sesuai ukuran yg sudah ditetapkan. Saya bikin lagi canvas dengan ukuran 1 karakter, saya gambar karakter di canvas itu, terus baru di-copy ke canvas aslinya.

    Yaaah… itulah cerita doodling nya seorang programmer. Uhuy.

  • Komputer,  Software

    Kunjungan ke Kaskus

    Yang saya bilang kunjungan, bukannya secara offline dateng ke kantornya kaskus, tapi karena udah lama gak buka forum ini, dan paling cuma liat-liat aja, makanya saya kasih judul “kunjungan”.

    Kaskus udah gak serame dulu, seperti IRC yang sudah tidak “seramai” dulu, sekarang mungkin orang-orang lebih suka bikin konten untuk youtube atau ngelola toko online yang lebih menghasilkan uang.

    Ada cukup banyak yang berubah, dari sisi visual dan fungsi jadi lebih baik dan menarik. Tapi ada juga yang jadi agak kurang dan saya bisa nebak alasannya. Contoh: dulu di bagian thread list, tiap thread pasti ada tanggal terakhir posting, sekarang gak ada, dan saya kira karena bisa jadi user gak mau nge-klik thread yang umurnya udah “tua”.

    Ada juga yang bikin saya kurang suka: di tiap page ada kaskus tv, video yang otomatis play. Asli, gak suka banget. Akhirnya karena gak bisa diakalin pakai cara-cara biasa, saya terpaksa install Violentmonkey, salah satu penerus GreaseMonkey yang bisa saya pasang di browser Maxthon yang saya pakai sehari-hari.

    Dan karena gak ada userscript yang bisa langsung dipake, dengan sangat terpaksa, saya bikin sendiri deh scriptnya. Cari elmentnya kaskus tv pake inspector, yang ternyata berupa iframe. Akhirnya bikin script untuk ngeset iframe src jadi ‘about:blank’.

    Userscript memang lumayan dibutuhkan untuk yang suka nge-custom segala macem.