Tuesday, December 16, 2014

การอ่านภาพขนาดใหญ่ทีละส่วน ด้วย Python และ PIL ตอนที่ 5 (Efficiency)

เมื่อเราพยายามแก้ปัญหาด้วยวิธีหนึ่ง เราอาจจะต้องยอมสูญเสียบางอย่างไปใช่ไหมครับ

เช่นเดียวกับปัญหาในการอ่านภาพขนาดใหญ่ วิธีที่ง่ายที่สุดคือเพิ่มประสิทธิภาพของคอมพิวเตอร์ เช่น เปลี่ยน CPU หรือเพิ่ม RAM ซึ่งสิ่งที่เสียไปคือค่าใช้จ่าย

หากเลือกช่องทางอื่น เช่นเหมือนกับที่เราพยายามทำ คือการแบ่งส่วนรูปเป็นส่วนย่อยๆ วิธีนี้ก็จะต้องแลกด้วยการเสียเวลาเพิ่มขึ้น เนื่องจาก
- ความซับซ้อนของ Algorithm มากขึ้น
- การอ่านข้อมูลหลายๆครั้งจาก Hard disk น่าจะเสียเวลามากกว่าการอ่านครั้งเดียว
นอกจากนี้ ยังต้องแลกด้วยการเสียหน่วยความจำเพิ่มเติมอีก ได้แก่หน่วยความจำที่ใช้เก็บส่วนของรูปในระหว่างรอประมวลผล

เราลองมาทำการทดลองวัดเฉพาะเรื่องของเวลา ที่ใช้ในการอ่านและประมวลผลรูป ระหว่างการอ่านครั้งเดียว กับการอ่านทีละส่วนกันครับ

วิธีวัดเวลาใน python ก็ตรงไปตรงมาคือ
import time
start = time.time()
#our codes
elapse = time.time()-start

อย่างไรก็ตาม มีข้อสังเกตว่า python มีการสร้าง cache ของรูปที่เคยอ่านไว้ ทำให้การอ่านรูปซ้ำใช้เวลาน้อยกว่าการอ่านรูปครั้งแรก ดังนั้น ในการวัดเวลาครั้งนี้เราจะเอาเวลาโดยประมาณ ที่ไม่รวมการอ่านรูปในรอบแรก
ในที่นี้เราใช้รูปทดสอบขนาด 7076x7001 พิกเซล เป็นรูปแบบ RGB
กรณีที่ 1 อ่านทั้งรูป เวลาที่ใช้โดยประมาณ 0.65 วินาที
from PIL import Image
import time
#starting time
start = time.time()
img = Image.open("mfu2009(chs. 1,2,3).tif")
#load image
img.load()
#time used
elapse = time.time() - start
print elapse

กรณีที่ 2 อ่านทีละส่วนของรูป เวลาที่ใช้โดยประมาณ 1.10 วินาที
from PIL import Image
import time
#starting time
start = time.time()
img = Image.open("mfu2009(chs. 1,2,3).tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 128 rows
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead

#image header size, please check by img.tile, it is different for images
header = img.tile[0][2];
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("mfu2009(chs. 1,2,3).tif")
    #------- tile parameters --------
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3
    #if the last set of row is additional
    if rr==totalRead-1 and rowLeft!=0:
        rowRead = rowRead + rowLeft
    #set tile size
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('RGB', 0, 1))]
    #------- size parameters --------
    img.size = (oriW, rowRead)
    #load part of image
    img.load()
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(img,(0,rr*rowReadExtra))    


elapse = time.time() - start
print elapse

กรณีที่ 3 อ่านทั้งรูป แล้วประมวลผลแต่ละพิกเซล เวลาที่ใช้โดยประมาณ 1.24 วินาที
from PIL import Image
import time
#starting time
start = time.time()
img = Image.open("mfu2009(chs. 1,2,3).tif")
#load image and perform point processing
out = img.point(lambda i: 255-i)
#time used
elapse = time.time() - start
print elapse

กรณีที่ 4 อ่านทีละส่วนของรูป แล้วประมวลผลแต่ละพิกเซล เวลาที่ใช้โดยประมาณ 1.83 วินาที
โค้ดเหมือนกรณีที่ 2 เพียงแค่แก้ไขในส่วนสุดท้ายของลูปนิดหน่อย
from PIL import Image
import time
#starting time
start = time.time()
img = Image.open("mfu2009(chs. 1,2,3).tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 128 rows
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead

#image header size, please check by img.tile, it is different for images
header = img.tile[0][2];
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("mfu2009(chs. 1,2,3).tif")
    #------- tile parameters --------
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3
    #if the last set of row is additional
    if rr==totalRead-1 and rowLeft!=0:
        rowRead = rowRead + rowLeft
    #set tile size
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('RGB', 0, 1))]
    #------- size parameters --------
    img.size = (oriW, rowRead)
    #load image part and perform point processing
    img.load()
    temp = img.point(lambda i: 255-i)    
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(temp,(0,rr*rowReadExtra))  

elapse = time.time() - start
print elapse

สรุปว่า
กรณีที่ 2 ใช้เวลาเป็น 1.69 เท่าของวิธีที่ 1
กรณีที่ 4 ใช้เวลาเป็น 1.48 เท่าของวิธีที่ 3
นั่นคือ พอจะบอกได้ว่า การแบ่งส่วนอ่านและประมวลผลพิกเซลรูป จะใช้เวลาเพิ่มขึ้นอีกประมาณ 1.5-1.7 เท่าของวิธีปกติ แต่ความเป็นจริงแล้วค่านี้ยังขึ้นอยู่กับหลายปัจจัย ทั้งจำนวนแถวทีอ่านในแต่ละครั้ง อัลกอริธึมในการประมวลผลรูป ขนาดและชนิดของรูป ซึ่งถ้าต้องการได้ค่าที่ถูกต้อง คงต้องทดสอบอีกหลายๆครั้งในหลายๆกรณีครับ

Sunday, December 14, 2014

การอ่านภาพขนาดใหญ่ทีละส่วน ด้วย Python และ PIL ตอนที่ 4 (Color image + extra rows)

บางทีทุกอย่างไม่ได้ลงตัวไปทั้งหมดใช่ไหมครับ เช่น โค้ดที่ใช้กับรูปเทาได้ ก็จะใช้กับรูปสีไม่ได้โดยตรง หรือ เมื่ออ่านรูปทีละแถบเหมือนที่เราเคยทำ ดันเหลือเศษของแถวที่เกินขึ้นมา

ในหัวข้อนี้เราจะพยายามปรับโค้ดของเราให้ยืดหยุ่น รองรับสองสถานการณ์นี้ครับ

กรณีแรก รูปสีแบบ RGB
อันนี้ค่อนข้างง่าย รูป RGB ก็จะมี offset เพิ่มขึ้นเป็น 3 เท่าของรูปเทา เนื่องจากมี 3 channels
เราก็จะใช้โค้ดประมาณนี้
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3

กรณีที่สอง มีเศษเหลือของแถวจากการอ่าน
ตัวอย่างเช่น รูปมีทั้งสิ้น 7001 แถว (ความสูง) เราต้องการอ่านทีละ 128 พิกเซล
ก็จะต้องวนอ่านเท่ากับ 7001/128 = 54.69 หรือ 55 รอบ ซึ่งรอบสุดท้ายจะอ่านแค่ 7001%128 = 89 แถว
โค้ดที่ใช้ก็จะคือ
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead
#if there are some rows left at the bottom, add one more row to process
if rowLeft!=0:
    totalRead = totalRead+1

ลองดูตัวอย่างเต็มได้ตามโค้ดนี้ครับ ในที่นี้เราจะวนอ่านรูปสี RGB ขนาด 7076x7001 พิกเซลและบันทึกไว้เป็นไฟล์ใหม่ชื่อ output.tif ซึ่งเหมือนรูปตั้งต้นทุกประการ
from PIL import Image
img = Image.open("mfu2009(chs. 1,2,3).tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 128 rows
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead
#if there are some rows left at the bottom, add one more row to process
if rowLeft!=0:
    totalRead = totalRead+1

#image header size, please check by img.tile, it is different for images
header = img.tile[0][2];
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("mfu2009(chs. 1,2,3).tif")
    #------- tile parameters --------
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3
    #if the last set of row is additional
    if rr==totalRead-1 and rowLeft!=0:
        rowRead = rowLeft
    #set tile size
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('RGB', 0, 1))]
    #------- size parameters --------
    img.size = (oriW, rowRead)
    #load part of image
    img.load()
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(img,(0,rr*rowReadExtra))    

#save output to file
out.save("output.tif","TIFF")

สำหรับกรณีที่สองนี้ เราสามารถคิดใหม่ก็ได้ว่า ให้อ่านแถวสุดท้ายเพิ่มขึ้น ซึ่งก็จะดีกว่าวิธีที่แล้ว เพราะลดขั้นตอนในการวนลูปลงหนึ่งขั้น ตัวอย่างเช่น
รูปมีทั้งสิ้น 7001 แถว (ความสูง) เราต้องการอ่านทีละ 128 พิกเซล
ก็จะต้องวนอ่านเท่ากับ 7001/128 = 54.69 หรือ 54 รอบ ซึ่งรอบสุดท้ายจะอ่านเพิ่มเป็น 128+(7001%128) = 128+89 = 217 แถว

โค้ดใหม่จะประมาณนี้ครับ ให้ผลลัพธ์เหมือนเดิม เร็วกว่าเดิมนิดนึง
from PIL import Image
img = Image.open("mfu2009(chs. 1,2,3).tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 128 rows
rowRead = 128
#keep original rowRead, used when there are extra last set of rows
rowReadExtra = rowRead
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead
#is oriH is divisible by rowRead?
rowLeft = oriH%rowRead

#image header size, please check by img.tile, it is different for images
header = img.tile[0][2];
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("mfu2009(chs. 1,2,3).tif")
    #------- tile parameters --------
    #for grayscale
    offset = rr*(rowRead*oriW)
    #if RGB
    if img.mode=='RGB':
        offset = offset*3
    #if the last set of row is additional
    if rr==totalRead-1 and rowLeft!=0:
        rowRead = rowRead + rowLeft
    #set tile size
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('RGB', 0, 1))]
    #------- size parameters --------
    img.size = (oriW, rowRead)
    #load part of image
    img.load()
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(img,(0,rr*rowReadExtra))    

#save output to file
out.save("output.tif","TIFF")

ต่อไปเราคงต้องมาลองเทียบเวลาในการประมวลผลว่า การอ่านทั้งหมดและอ่านทีละส่วน เร็วหรือช้ากว่ากันเท่าใดครับ

Saturday, December 13, 2014

การอ่านภาพขนาดใหญ่ทีละส่วน ด้วย Python และ PIL ตอนที่ 4

สมมติว่าเราต้องการทำ Convolution หรือ Filtering แบบ Sliding neighborhood operation กับรูปทีละส่วน จะทำได้อย่างไร

ถ้าเราใช้โค้ดของเดิมแล้ว filter เลย เช่น ใช้ average filter เพื่อเบลอรูป ซึ่งใน PIL จะมีคำสั่งให้เช่น
kernel = [1]*25
img.filter(ImageFilter.Kernel((5,5),kernel))

ผลลัพธ์จะได้ประมาณนี้ (ให้สังเกตรอยต่อในแนวนอนในภาพ)
หมายเหตุ ข้อนี้ได้ลดขนาดของรูปเหลือ 256x256 เพื่อให้เห็นผลลัพธ์ชัดขึ้น


ซึ่งมาจากโค้ดต่อไปนี้
from PIL import Image, ImageTk, ImageFilter
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw-256.tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 64 rows
rowRead = 64
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead

#image header size, please check by img.tile, it is different for images
header = 8;
#kernel size 5x5
ksize = 5

#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("Lenna-gray-raw-256.tif")
    #reading size eg. 256,64
    img.size = (oriW, rowRead)
    #set tile size
    offset = rr*(rowRead*oriW)
    img.tile = [('raw', (0, 0, oriW, rowRead), header + offset, ('L', 0, 1))]
    #load part of image
    img.load()
    #filter part of image
    #create an average kernel of 5x5 = 25 elements
    kernel = [1]*(ksize*ksize)
    temp = img.filter(ImageFilter.Kernel((ksize,ksize),kernel))
    #copy to new image
    #paste(source image, top-left corner)
    out.paste(temp,(0,rr*rowRead))

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()

จะเห็นว่ามีข้อผิดพลาดเกิดขึ้นเป็นแถบในแนวนอน ซึ่งก็คือรอยต่อระหว่างแต่ละรูปที่เราแบ่งส่วนในการโหลด

เพื่อที่จะแก้ปัญหาดังกล่าว อาจทำได้โดย อ่านภาพในแต่ละครั้ง ให้จำนวนแถวมากกว่าเดิมนิดนึง คือเพิ่มด้วยขนาดความสูงของ filter ที่จะใช้หารด้วยสอง เช่น



สมมติว่าแบ่งรูปออกเป็น 3 แถบ บน กลาง ล่าง
ถ้าอ่านทีละ 64 แถว และ filter มีขนาด 5x5
1. แถบบน จำนวนแถวที่อ่านใหม่ก็จะเป็น 64+(5-1)/2 = 64+2 = 66 แถว นั่นคือ เพิ่มล่างสองแถว
2. แถบกลาง จำนวนแถวที่อ่านใหม่ก็จะเป็น 64+4 = 68 แถว นั่นคือ เพิ่มบนสองแถว ล่างสองแถว
3. แถบล่าง 64 จำนวนแถวที่อ่านใหม่ก็จะเป็น 64+2 = 66 แถว นั่นคือ เพิ่มบนสองแถว
ซึ่งการกำหนดดังกล่างจะมีผลต่อค่า size และ tile ต้องกำหนดค่านี้ใหม่ T_T นอกจากนี้แล้ว ตอนประมวลผลเสร็จแต่ละแถบ ผลลัพธ์จะใช้เพียงแค่ 64 แถวเหมือนเดิม

ผลลัพธ์ใหม่คือ (สังเกตว่าไม่มีรอยต่อของแถบแล้ว)


โค้ดใหม่ก็จะประมาณนี้ครับ
from PIL import Image, ImageTk, ImageFilter
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw-256.tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 64 rows
#total read = 256/64 = 4
rowRead = 64
#size of original image
oriW, oriH = img.size
totalRead = oriH/rowRead

#image header size, please check by img.tile, it is different for images
header = 8;
#kernel size 5x5
ksize = 5

#loop for each partial read
for rr in range(totalRead):   
    #additional row to read at top of bottom
    extraTopRow = (ksize-1)/2
    extraBottomRow = extraTopRow
    #if the first set of row
    if rr==0:
        extraTopRow = 0
    elif rr==totalRead-1:   #last set of row
        extraBottomRow = 0
    #reopen image, necessary due to partial loading
    img = Image.open("Lenna-gray-raw-256.tif") 
    #tile
    offset = rr*(rowRead*oriW)
    img.tile = [('raw', (0, 0 - extraTopRow, oriW, rowRead +extraTopRow +extraBottomRow), header + offset - (extraTopRow*oriW), ('L', 0, 1))]
    #set reading size
    img.size = (oriW, rowRead + extraTopRow + extraBottomRow)
    #load part of image
    img.load()
    #filter part of image
    #create an average kernel of 5x5 = 25 elements
    kernel = [1]*(ksize*ksize)
    temp = img.filter(ImageFilter.Kernel((ksize,ksize),kernel))
    #crop to normal size
    #img.crop((x1,y1,x2,y2)) --> top left to bottom right
    crop = temp.crop((0,extraTopRow, oriW,extraTopRow+rowRead))
    #copy to new image
    #paste(source image, top-left corner)
    out.paste(crop,(0,rr*rowRead))

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()
หมายเหตุ
คำสั่ง ImageFilter.Kernel มีข้อจำกัดคือทำได้เฉพาะ kernel ขนาด 3x3 หรือ 5x5 เท่านั้นครับ

Tuesday, December 9, 2014

การอ่านภาพขนาดใหญ่ทีละส่วน ด้วย Python และ PIL ตอนที่ 3

ต่อจากคราวที่แล้ว เราได้ลองอ่านส่วนหนึ่งของภาพขึ้นมาจำนวน 64 แถว

ตอนนี้เราจะลองใช้ลูปอ่านค่าจนครบรูป

ทบทวนจากเดิมที่เราทราบว่า เมื่ออ่านรูปขึ้นมาด้วย PIL บางทีจะอ่านเป็นส่วนอยู่แล้วเช่นรูปนี้


จะเกี่ยวข้องกับคำสั่งในการอ่านคือ size และ tile
i = Image.open("Lenna-gray-raw.tif")
i.size
(512, 512)
i.tile
[('raw', (0, 0, 512, 64), 8, ('L', 0, 1)), ('raw', (0, 64, 512, 128), 32776, ('L', 0, 1)), ('raw', (0, 128, 512, 192), 65544, ('L', 0, 1)), ('raw', (0, 192, 512, 256), 98312, ('L', 0, 1)), ('raw', (0, 256, 512, 320), 131080, ('L', 0, 1)), ('raw', (0, 320, 512, 384), 163848, ('L', 0, 1)), ('raw', (0, 384, 512, 448), 196616, ('L', 0, 1)), ('raw', (0, 448, 512, 512), 229384, ('L', 0, 1))]

หากเราจะใช้ลูปวนอ่าน ก็ต้องเปลี่ยนสองค่านี้ เช่น
ถ้าขนาดรูปเป็น 512 คอลัมน์ x 512 แถว
สมมติว่าอ่านทีละ 64 แถว ก็จะต้องอ่าน 512/64 = 8 ครั้ง

รอบที่ 0
size ก็จะเป็น (512,64)
tile ก็จะเป็น ('raw', (0, 0, 512, 64), 8, ('L', 0, 1))

รอบที่ 1
size ก็จะเป็น (512,64)
tile ก็จะเป็น ('raw', (0, 0, 512, 64), (8+512*64), ('L', 0, 1))

รอบที่ 2
size ก็จะเป็น (512,64)
tile ก็จะเป็น ('raw', (0, 0, 512, 64), (8+512*64+512*64), ('L', 0, 1))

ไปเรื่อยๆ โดยสังเกตว่าในที่นี้ขนาดของ tile เป็น (0, 0, 512, 64) ตลอด

ถ้าสมมติว่า rr เป็นตัวแปรเอาไว้บอกรอบ มีค่าตั้งแต่ 0-7 (8 รอบ) ดั้งนั้น
รอบที่ rr
size ก็จะเป็น (512, 64)
tile ก็จะเป็น ('raw', (0, 0, 512, 64), (8 + (rr*(512*64)), ('L', 0, 1))

สมมติว่าในตัวอย่างแรกนี้ เราจะลองอ่านรูปขึ้นมาทีละส่วน แล้วแปะลงไปในรูปใหม่ ให้ผลลัพธ์หน้าตาเหมือนรูปต้นฉบับเป๊ะ


โค้ดก็จะประมาณนี้ครับ
from PIL import Image, ImageTk
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw.tif")
#create new output image
out = Image.new(img.mode, img.size, None)

#try to read every 64 rows
rowRead = 64
#size of original image
oriW, oriH = img.size
#total read = 512/64 =8
totalRead = oriH/rowRead

#image header size, please check by img.tile, it is different for images
header = 8;
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("Lenna-gray-raw.tif")
    #tile
    offset = rr*(rowRead*oriW)
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('L', 0, 1))]
    #reading size eg. 512,64
    img.size = (oriW, rowRead)   
    #load part of image
    img.load()
    #copy part of image to new image
    #paste(source image, top-left corner)
    out.paste(img,(0,rr*rowRead))

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()
ต่อมา ถ้าเราต้องการประมวลผลแต่ละพิกเซลไปด้วย ในที่นี้เอาแบบง่ายๆคือทำ Image Negative เพื่อให้ได้ผลลัพธ์ดังนี้


โค้ดก็จะประมาณนี้ครับ
from PIL import Image, ImageTk
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw.tif")
#create new output image
out = Image.new(img.mode, img.size, None)
pixOut = out.load()

#try to read every 64 rows
rowRead = 64
#size of original image
oriW, oriH = img.size
#total read = 512/64 =8
totalRead = oriH/rowRead

#image header size, please check by img.tile, it is different for images
header = 8;
#loop for each partial read
for rr in range(totalRead):
    #reopen image, necessary due to partial loading
    img = Image.open("Lenna-gray-raw.tif")
    #tile
    offset = rr*(rowRead*oriW)
    img.tile = [('raw', (0, 0, oriW, rowRead),  header + offset, ('L', 0, 1))]
    #reading size eg. 512,64
    img.size = (oriW, rowRead)   
    #load part of image to pixel object
    pix = img.load()
    
    #try to perform pixel processing
    #loop for each row and column
    w,h = img.size
    for r in range(h):
        for c in range(w):
            pixOut[c,r+(rr*rowRead)] = 255-pix[c,r]

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()

การอ่านค่าจาก Checkbox ด้วย jQuery

ลองมาอ่านค่าจาก checkbox กันสองอย่างคือ

1. มี checkbox กี่อันถูกเลือก
2. ค่าใน checkbox ที่ถูกเลือกทั้งหมดเป็นเท่าใด

แบบนี้ครับ
โค้ด
<!doctype html>
<html>
 <head>
  <meta charset="utf-8">
  <script src="jquery-2.1.1.min.js"></script>
  <script>
   $(document).ready(function(){  
    $("button").on("click",function(){
     //number of checked checkbox
     var num = $("input[type=checkbox]:checked").length;
     var value = "";
     //loop for each checked checkbox
     $("input[type=checkbox]:checked").each(function() {
      value = value + $(this).val() + " ";
     });
     alert("Number of selected checkbox = "+num+"\n"+"Value = "+value);
    });
   });  
  </script>
 </head>

<body>
 <input type="checkbox" name="fruit" value="Apple">Apple<br/>
 <input type="checkbox" name="fruit" value="Papaya">Papaya<br/>
 <input type="checkbox" name="fruit" value="Banana">Banana<br/>
 <button>OK</button>
</body>
</html>

อ้างอิง
http://api.jquery.com/checked-selector/
http://stackoverflow.com/questions/14679916/jquery-get-multiple-checked-values-from-checkbox

Thursday, December 4, 2014

การอ่านภาพขนาดใหญ่ทีละส่วน ด้วย Python และ PIL ตอนที่ 2

จากตอนที่แล้ว เรารู้ระบบการอ่านค่าของ PIL แล้ว

ต่อไปเราจะลองอ่านค่าส่วนหนึ่งของรูปขึ้นมา สมมติว่ารูปของเราคือ Lenna-gray-raw.tif ซึ่งเมื่อใช้คำสั่งในการอ่านรูปพบว่า
img.size ให้ค่า (512,512) ซึ่งเป็น ความกว้าง x ความสูง หรือ จำนวนคอลัมน์ x จำนวนแถว
และ
img.tile แล้วพบว่า มันเป็นแบบ ('raw', (0, 0, 512, 64), 8, ('L', 0, 1))

ซึ่งหมายเหตุตรงนี้นิดนึงว่า
1. ในการอ่านค่ารูปด้วย PIL แม้ว่ารูปเดียวกัน แต่คนละระบบหรือคนละเครื่อง มันอาจจะแสดงการอ่านจำนวนแถวครั้งละไม่เท่ากัน เช่นตอนนี้เป็น 64 แถว แต่เครื่องอื่นที่ผมทดลองใช้มันอ่านทีละ 16 หรือ 32 แถวก็มี ทั้งนี้เดาว่าแล้วแต่หน่วยความจำที่มี OS ที่ใช้และขนาดของรูป แต่ไม่มีผลอะไรกับสิ่งที่เราจะทำในตอนนี้ครับ
2. L ในที่นี้คือ 8-bit pixels, black and white แต่รูปแบบ tif อาจจะเก็บแบบ P คือ 8-bit pixels, mapped to any other mode using a colour palette ก็ได้ ตรวจสอบให้ดีก่อนจะเขียนโปรแกรมต่อไปนะครับ

เมื่อทราบข้อมูลข้างต้นแล้ว สมมติว่าผมจะลองอ่านค่าเฉพาะ 64 แถวแรกของรูปแบบ raw tif ขนาด 512x512 พิกเซลขึ้นมาแสดงผลให้ได้ดังนี้


ก็จะเขียนโค้ดดังนี้ครับ
from PIL import Image, ImageTk
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw.tif")

#try to read first 64 lines
img.size = (512,64)
#we will skip for 8 bytes (image header)
img.tile = [('raw', (0, 0, 512, 64), 8, ('L', 0, 1))]
#load part of image according to new size and tile's properties
#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(img)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()

และถ้าอยากประมวลผลรูปส่วนที่โหลดขึ้นมา ก็ทำได้ตามปกติครับ เช่นจะกลับหัวรูปก็แค่เพิ่มคำสั่ง
#try to flip image
img = img.transpose(Image.FLIP_TOP_BOTTOM)

หลังคำสั่ง img.load() ก็น่าจะได้ผลประมาณนี้ครับ


ถ้าเราต้องการเข้าถึงพิกเซลก็ทำได้ครับ โดยเปลี่ยนคำสั่ง img.load() ให้เป็น pix = img.load() เพื่ออ่านค่าพิกเซลมาเก็บไว้ในโครงสร้างของ PIL และสามารถอ่านค่าแต่ละพิกเซลโดยใช้คำสั่ง pix[c,r] เมื่อ c และ r คือคอลัมน์และแถวของภาพตามลำดับ อย่างไรก็ตาม ผมพบว่าการอ่านส่วนของภาพแบบนี้ จะทำให้โครงสร้างที่เก็บพิกเซลนี้เป็นแบบ read only คือแก้ไขไม่ได้ เพราะฉะนั้นจำเป็นต้องสร้างรูปใหม่ขึ้นมาอีกหนึ่งรูปเพื่อเก็บผลลัพธ์จากการประมวลผลภาพ

ลองดูตัวอย่างต่อไปนี้ ที่เราทำการอ่านส่วนของภาพแล้วประมวลผลทีละพิกเซล แล้วแสดงผลเป็นรูปใหม่ การประมวลผลจะเป็นแบบง่ายๆคือทำ inverse ของรูปครับ


โค้ดครับ
from PIL import Image, ImageTk
from Tkinter import Tk, Label
img = Image.open("Lenna-gray-raw.tif")

#try to read first 64 lines
img.size = (512,64)
#we will skip for 8 bytes (image header)
img.tile = [('raw', (0, 0, 512, 64), 8, ('L', 0, 1))]
#load part of image according to new size and tile's properties to pixel object
pix = img.load()
#create new output image
out = Image.new(img.mode, img.size, None)
pix2 = out.load()
#loop for each row and column
w,h = img.size
for r in range(h):
    for c in range(w):
        pix2[c,r] = 255-pix[c,r]        

#display on Tkinter
root = Tk()
imgTk = ImageTk.PhotoImage(out)
lbl = Label(root, image=imgTk)
lbl.pack()
root.mainloop()
ในตอนต่อไปเราจะลองใช้ลูปเพื่อวนอ่านค่ารูปทีละส่วนมาประมวลผลดูครับ

การอ่านภาพขนาดใหญ่ทีละส่วน ด้วย Python และ PIL ตอนที่ 1

เรื่องนี้ค้างคามานานแล้ว คงถึงเวลาที่ต้องทำให้สำเร็จซะที 

มีโจทย์อยู่ว่าต้องการอ่านภาพขนาดใหญ่มาก เช่น ภาพพวก Remote Sensing แบบ Multi-Spectral ซึ่งปกติต้องใช้เครื่องคอมที่มีแรมเยอะๆ เพราะต้องอ่านภาพทั้งหมดไปไว้ในหน่วยความจำก่อนการประมวลผล และถ้าต้องประมวลผลแบบพวก Convolution ก็จะต้องใช้หน่วยความจำมากขึ้นไปอีก 

ทีนี้ก็เลยมาคิดว่า ถ้าเครื่องมีแรมไม่พอจะทำได้ไหม ถ้าโหลดภาพกันตรงๆก็คงเจอปัญหา Out of Memory แน่ๆ 

ทางออกนึงที่น่าจะเป็นไปได้คือ การอ่านภาพทีละส่วน เนื่องจากการประมวลผลภาพส่วนใหญ่ไม่ได้ใช้ข้อมูลจากทั้งรูปในทีเดียว ส่วนใหญ่จะใช้เป็นก้อนๆไป

 ต่อไปก็หาเทคนิคที่จะใช้ ไปค้นดูปรากฎว่า Java ก็ทำได้ ลองเขียนโค้ดไปพักใหญ่ๆ 

ต่อมา ลองค้นหากับ Python บ้าง เพื่อว่าจะมีวิธีเหมือนกัน ก็ไปพบที่เว็บนี้ Using PIL On Large Images
เค้าบอกว่า ถ้าใช้ PIL (Python Imaging Library) ในการอ่านค่ารูป มันสามารถแบ่งส่วนได้

เมื่อเราใช้คำสั่งในการอ่านรูป คือ
im = Image.open("Lenna.png")

จริงๆแล้วรูปยังไม่ถูกอ่านและโหลดเข้าหน่วยความจำทั้งหมด คำสั่งนี้เพียงแค่โหลดส่วนต้นของไฟล์ภาพ ที่เกี่ยวข้องกับข้อมูลภาพ เช่น การเข้ารหัส ขนาดของภาพ ชนิดของภาพ (ภาพสีเทา ภาพสี) ฯลฯ กระบวนการนี้ถูกเรียกว่า Lazy operation

ซึ่งหลังจากคำสั่งนี้ เราสามารถใช้คำสั่งอื่นเพื่อดูข้อมูลนี้ได้ เช่น
im.size ก็จะให้ค่าเป็น (width, height) ออกมา

ถ้าใช้คำสั่ง im.tile จะได้โครงสร้างรูปแบบการอ่านค่าแบบบล็อกออกมา (แสดงว่าจริงๆแล้ว PIL ก็อ่านค่ารูปแบบแบ่งส่วนอยู่แล้ว ไม่ได้อ่านทีเดียวทั้งหมด เพียงแต่กระบวนการนี้ต่อเนื่องกันทั้งรูป)

ตัวอย่างเช่น ถ้ามีรูป Lenna.png ที่ดาวน์โหลดมาจากที่นี่


เมื่อเราอ่านรูปโดย
im = Image.open("Lenna.png")
และดูขนาด
im.size
(512, 512)
หมายเหตุว่าเป็น (width, height) ครับ

และดูการแบ่งส่วนในการอ่านรูป
im.tile
[('zip', (0, 0, 512, 512), 54L, 'RGB')]

ก็จะเห็นว่ามีค่าอยู่สี่ชุด ได้แก่
1. การเข้ารหัส ในที่นี้คือ zip ถ้าไม่เข้ารหัสเลยก็จะเป็น raw
2. ขนาดกรอบสี่เหลี่ยมในการอ่านรูป เริ่มตั้งแต่มุมซ้ายบน (x1,y1) จนถึงขวาล่างสุด (x2,y2) ของรูป
3. ค่า offset ในการอ่านรูปแต่ละครั้ง ในที่นี้คือ 54L ซึ่งในที่นี้ไม่มีการแบ่งส่วนของรูป จึงเป็นค่าขนาดของ header ของไฟล์รูปนี้ (ไฟล์รูปจะมีส่วนหัวที่บอกถึงการเข้ารหัส ชนิดของรูป ขนาด ฯลฯ) ค่า offset นี้จะเป็นตัวบอกว่าเราจะเริ่มอ่านไฟล์เนื้อหาของรูปจริงๆที่ตรงไหน เช่น 54L ก็คือเริ่มอ่านหลังจาก 54 คูณ ขนาดของ long (ไม่รู้เท่าไหร่เหมือนกันครับ 555) ไบต์เป็นต้นไป
4. โหมดของรูป ในที่นี้คือ RGB

ในหลักการ เราสามารถเปลี่ยนค่าเหล่านี้ได้ และจะทำให้สามารถแบ่งส่วนการอ่านรูปได้

ถ้าลองเปลี่ยนมาเป็นรูปสีเทา โดยในที่นี้จะเป็นรูปเดิมที่ถูกแปลงเป็นไฟล์ tif โดยไม่เข้ารหัส (ไฟล์ raw)


i = Image.open("Lenna-gray-raw.tif")
i.size
(512, 512)
i.tile
[('raw', (0, 0, 512, 64), 8, ('L', 0, 1)), ('raw', (0, 64, 512, 128), 32776, ('L', 0, 1)), ('raw', (0, 128, 512, 192), 65544, ('L', 0, 1)), ('raw', (0, 192, 512, 256), 98312, ('L', 0, 1)), ('raw', (0, 256, 512, 320), 131080, ('L', 0, 1)), ('raw', (0, 320, 512, 384), 163848, ('L', 0, 1)), ('raw', (0, 384, 512, 448), 196616, ('L', 0, 1)), ('raw', (0, 448, 512, 512), 229384, ('L', 0, 1))]

จะเห็นว่าคราวนี้ ข้อมูลชุดแรกของค่า tile (แถบแรก) คือ
('raw', (0, 0, 512, 64), 8, ('L', 0, 1))
บอกว่าไม่มีการเข้ารหัส (raw) และ มีการอ่านค่าทุกๆ 64 บรรทัด โดยครั้งแรกที่อ่านจะต้องเลื่อนไป 8 bytes ก่อน (offset) ซึ่งเป็น header ของไฟล์รูปนี้ รูปจริงๆก็จะอยู่ต่อจาก byte ที่ 8 นั่นเอง

ข้อมูลชุดถัดมา (แถบที่สอง) คือ
('raw', (0, 64, 512, 128), 32776, ('L', 0, 1))
บอกว่าให้อ่านอีก 64 แถวต่อไป (ทุกคอลัมน์) ค่า Offset คราวนี้ก็จะเท่ากับ 64 แถว * 512 คอลัมน์ + 8 ไบต์แรก = 32776 ไบต์

เหมือนกับรูปประมาณนี้ครับ


ในตอนต่อไปเราจะลองเปลี่ยนค่า size และ tile ของรูป เพื่อแบ่งส่วนในการอ่านรูปดูครับ

หมายเหตุ รูปจะถูกอ่านจริงจากไฟล์และโหลดมาไว้ในหน่วยความจำก็ต่อเมื่อ
1) มีการใช้คำสั่งในการประมวลผลรูป หรือมีการอ่านค่าพิกเซลของรูป หรือ
2) บังคับโดยใช้คำสั่ง im.load()

Wednesday, December 3, 2014

[OpenCV กับการแสดงผลใน Tkinter] และ [PIL กับการแสดงผลด้วย OpenCV]

ตั้งใจจะเขียนเรื่องนี้นานแล้ว พึ่งจำได้ครับ

ตอนเราประมวลผลรูปด้วย OpenCV มันก็จะมีหน้าต่างแสดงผลมาให้ ด้วยคำสั่ง เช่น imshow

อย่างไรก็ตาม ถ้าเราใช้ Python ทางเลือกหนึ่งในการแสดงผลก็คือ การใช้ Tkinter ซึ่งเป็น UI ที่มีมากับ Python เลย

สิ่งที่ต้องทำคือ
1. รูปแบบการเก็บข้อมูลภาพของ OpenCV ต่างกับแบบอื่น คือเป็น BGR จำเป็นต้องเปลี่ยนให้เป็น RGB ก่อน
2. OpenCV เก็บข้อมูลภาพเป็น NumPy Array การอ่านไปแสดงผลใน Tkinter จำเป็นต้องใช้คำสั่งการอ่านจาก Array แทน

โค้ดก็จะเป็นประมาณนี้ครับ คืออ่านค่าภาพ (และประมวลผล) ด้วย OpenCV จากนั้นสร้าง UI ด้วย Tkinter และแสดงผล
import cv2
from Tkinter import Tk, Label
from PIL import Image, ImageTk

#load image in grayscale, output is numpy array
img = cv2.imread("lena.jpg", cv2.IMREAD_COLOR)

if img==None:
    print "Image not loaded"
else:
    #convert BGR to RGB
    imgRGB = img[:,:,::-1]
    #convert to PIL format
    imgtk = Image.fromarray(imgRGB)
    #create main window
    root = Tk()
    root.title("Image Display")
    #convert to Tkinter format
    imgTk = ImageTk.PhotoImage(imgtk)
    #create a label to store and display image
    lbl = Label(root, image=imgTk)
    #pack a label to fit image
    lbl.pack()
    #lbl.image = imgTk
    root.mainloop()

ผลลัพธ์ที่ได้ก็คือ


ในทางกลับกันถ้าเราต้องการโหลดรูปด้วย PIL แต่ไปแสดงผลแบบ OpenCV กระบวนการก็จะกลับกันคือ
1. แปลงรูปให้เป็น numpy array ก่อน
2. เปลี่ยนจาก RGB ให้เป็น BGR

ลองดูโค้ดตามนี้ครับ
import cv2
from PIL import Image
import numpy as np

#load image by PIL
img = Image.open("lena.jpg")

#convert PIL image to numpy array
img = np.array(img)

#convert RGB to BGR
imgBGR = img[:,:,::-1]

#display in opencv
cv2.namedWindow("Output")
cv2.imshow("Output",imgBGR)
cv2.waitKey()
cv2.destroyAllWindows()

ผลลัพธ์ที่ได้น่าจะประมาณนี้


อ้างอิง
http://stackoverflow.com/questions/384759/pil-and-numpy

Tuesday, December 2, 2014

ลองติดตั้ง VirtualBox บน LXLE

พอติดตั้ง LXLE ที่เดิมทีตั้งใจจะให้เป็นการใช้งานทั่วไปเบาๆ ก็อยากลองอะไรที่มันมากขึ้น

อยากลองติดตั้ง VirtualBox แล้วรัน Android Emulator ดู ถ้าเซ็ต memory ต่ำๆ ซัก 216 MB น่าจะไหวอยู่

ว่าแล้วก็ติดตั้งเลย เลือก Software Center ของ lubuntu ติดตั้ง VirtualBox เวอร์ชันที่มีให้ก็ประมาณ 4.1 ทำนองนี้

ติดตั้งเสร็จ เพิ่มตัว emu (ในที่ใช้ OVA ของ AndroVM) รันไม่ได้ซะงั้น มีข้อความผิดพลาดบอกว่า

Kernel driver not installed (rc=-1908)

The VirtualBox Linux kernel driver (vboxdrv) is either not loaded or there is a permission problem with /dev/vboxdrv. Please reinstall the kernel module by executing

'/etc/init.d/vboxdrv setup'

as root. If it is available in your distribution, you should install the DKMS package first. This package keeps track of Linux kernel changes and recompiles the vboxdrv kernel module if necessary.


แย่ละ ไปต่อไม่ถูกเลย

ไปลองค้นหาดู หลายๆที่บอกว่าเพราะไม่มีพวก kernel drivers + build tools เลยลองทำตามเค้า เช่นที่เว็บ http://www.binarytides.com/fix-vbox-kernel-driver-error/

ก็ยังไม่สำเร็จในขั้นสุดท้าย

ไปหาดูอีก บางคนบอกว่านอกจากไม่มีพวกไฟล์ข้างต้นแล้ว เวอร์ชันของ VirtualBox ใน Software Center มันเก่าไป ไม่สนับสนุนกัน

สุดท้าย วิธีที่มั่วแล้วใช้ได้คือ
1. ติดตั้ง kernel drivers + build tools ก่อน โดยใช้คำสั่ง

$ sudo apt-get install build-essential module-assistant 
$ sudo m-a prepare

2. ดาวน์โหลด VirtualBox มาใหม่จาก https://www.virtualbox.org/wiki/Linux_Downloads เลือกเวอร์ชันให้ตรงกับ OS ของเรา อย่างเช่น ผมใช้ LXLE 12.04 ที่มีพื้นฐานมาจาก Ubuntu 12.04 LTS ก็โหลดเวอร์ชันนี้

3. ติดตั้งโดยดับเบิลคลิกทีไฟล์ที่โหลดมาเลย มันจะเรียก GDebi Package Installer มาติดตั้งไฟล์ .deb ที่โหลดมา รวมถึงคอมไพล์ไฟล์ที่เกี่ยวข้องให้ใหม่เอง

เย้ ใช้การได้แล้วครับ


Sunday, November 30, 2014

ลองเล่น Linux LXLE

มีคอมเก่าอยู่ครับ อายุประมาณหกเจ็ดปี อยากลองเอามาเล่นใหม่ โดยกะว่าจะใช้งานทั่วไป ใช้พิมพ์งาน Word คำนวณ Excel เล่นเน็ต ดูหนัง ฟังเพลง เขียนโปรแกรม JAVA ฯลฯ เริ่มจะเยอะ ยกเว้นเล่นเกมส์

สเปกที่มีอยู่ก็ประมาณ
CPU Centrino Duo
RAM 1 GB
HDD 100 GB
USB 2.0 สามารถกำหนด BIOS ให้บูตผ่าน USB ได้ (พึ่งรู้เหมือนกัน)
CD/DVD drive
แบตเสื่อม แต่เสียบสายใช้งานได้อยู่
OS เดิม Windows XP

ของเดิมก็ใช้งานได้อยู่ ติดแค่ว่ามันหน่วงบ้าง เพราะลง Firewall + Antivirus และเกรงว่า XP กำลังจะเป็นเป้าหมายด้านความปลอดภัย

เลยเลือกหา OS ที่จะลงใหม่โดยอยากได้ของฟรี ทำงานได้เร็วๆ ไม่หน่วงมาก แต่ไม่ถึงขั้นกับ UI ดูไม่ได้ พอเข้าไปดูใน distrowatch.com ก็เห็นว่าตามสถิติ Page Hits ที่ผ่านมา 6 เดือน เจ้า Linux Mint ยังคงเป็นผู้นำ ตามมาด้วย Ubuntu แต่ความนิยมไม่ใช่วัตถุประสงค์ของเรานี่นา เราต้องการใช้เครื่องเก่า งั้นก็ไปค้นหาดีกว่าโดยใช้ keyword คือประเภท Old computers ผลลัพธ์ก็คือ

LXLE (อะไรอ่ะ) ได้รับความนิยมมาอันดับ 1 และในเว็บตัวเองคือ www.lxle.net บรรยายตัวเองว่า Revive that old PC! อืม น่าสนใจว่าโม้หรือเปล่า เอามาลองสักหน่อย

ว่าแล้วก็ไปโหลดทันใด ซึ่งบังคับให้โหลดแบบ torrent เท่านั้น (ไม่เข้าใจ เว็บที่ให้เก็บไฟล์ฟรีมีเยอะแยะ) ซึ่งเค้าก็มีเหตุผลของเค้าอยู่แต่ขี้เกียจอ่าน

โหลดมาเสร็จได้ไฟล์ iso ก็จะติดตั้งผ่าน USB drive ขั้นตอนแรกเลย ก็ต้อง format มันใหม่ให้เป็น FAT32 จากนั้นก็ไปโหลดตัวช่วยคือ http://unetbootin.sourceforge.net/ เพื่อคัดลอกไฟล์จาก iso ที่โหลดมาไปยัง USB drive และทำให้มันบูตได้

เมื่อเตรียมการเสร็จ ก็เสียบ USB drive เข้ากับคอมเก่า แล้วเปิดเครื่อง มีเมนูให้เลือกติดตั้ง แล้วก็กำหนดค่าเล็กน้อย ใช้เวลาไม่เกินครึ่งชั่วโมงก็เสร็จ

จากนั้นก็เป็นการลองใช้งาน มีข้อสังเกตเบื้องต้นดังนี้
ข้อดี
-ติดตั้งง่าย หาอุปกรณ์ที่มีอยู่ในเครื่องเช่น Network card และ sound ได้เอง ไม่ต้องลง drivers เพิ่ม
-บูตใช้เวลาไม่ถึง 1 นาทีก็พร้อมใช้งาน
-สวย (เกี่ยวไหมนี่) UI ดี มีหลายโหมดการใช้งานให้เลือกว่าจะเป็นแบบ Windows หรือ Mac ทำนองนี้ มี Wallpaper สวยๆมาให้เป็นร้อยแบบ (มีรูปเมืองไทยด้วย)
-มีตัวคอยบอกการใช้งานทรัพยากรให้เห็น ซึ่งถ้าข้อมูลถูกต้อง ตั้งแต่ใช้งานมาวันกว่าๆ นี้ ยังไม่เคยเห็น CPU หรือ RAM ของระบบถูกใช้ถึง 50% เลย (เพราะเล่นแต่เน็ต กับพิมพ์งาน 555) แต่แทบไม่เห็นการกระตุกหรือช้าจากการใช้งานทั่วไป (ไม่นับเน็ตช้านะครับ)
-มีซอฟต์แวร์ที่จำเป็นมาให้พร้อม เล่นเน็ตมี Firefox ใช้งานเอกสารมี Libre Office ฯลฯ แทบจะไม่ต้องหาลงเอง ถ้าจะลงเองก็มี Software Center ของ lubuntu ให้

ข้อสังเกต (อาจรวมข้อด้อยด้วยมั้ง)
-มีซอฟต์แวร์เกินความจำเป็นแถมมาให้มากไป เช่น เกมส์
-หาไอคอนระบุภาษาไม่เจอ ตอนนี้ก็ยังหาวิธีให้แสดงผลภาษาปัจจุบันไม่ได้ 
-switch ภาษาอังกฤษไทยลำบาก ตอนแรกเซ็ตให้ใช้ Grave ได้ ตอนนี้ทำไม่ได้เฉยเลย สงสัยเรายังไม่เข้าใจพอ เลยต้องใช้ Alt+Left Shift ไปก่อน
-หาได้แล้วครับ แต่ยุ่งยากสักหน่อย อันดับแรกต้องไปที่ System Tools/Synaptic Package Manager แล้วค้นหา iBus จากนั้นติดตั้ง ibus-m17n เพิ่มเติม แล้วไปที่ Preferences/Input Method Switcher แล้วเลือก iBus จากนั้นรีสตาร์ทเครื่อง แล้วไปที่ Preferences/Keyboard Input Methods มันจะขึ้น iBus Preferences ให้เราเลือก Input Method แล้วเลือกภาษาตามรูป


จากนั้น ในหน้า General ให้เลือกดังนี้ สังเกตว่าในส่วนของ Next input method เราจะสามารถกำหนดได้ว่าจะใช้แป้นอะไรในการเลือกภาษาครับ


ก็น่าจะเกิดไอคอนภาษา ด้านขวาล่างของจอครับ
แต่อย่างไรก็ตาม มันก็ยังดูขัดๆอยู่เวลาพิมพ์ เพราะมันจะมีเส้นใต้ขีดเป็นแนวในการพิมพ์ให้ รู้สึกแปลกๆครับ และหากใช้ตัว Grave เป็นตัวเปลี่ยนภาษา มันก็จะติดตัว ` หรือ _ มาด้วย ต้องเลือกตัวอื่นที่กดแล้วไม่เป็นตัวอักษรแทน เช่น Window Key

-ปรับความสว่างหน้าจอไม่ได้ มีซอฟต์แวร์ให้ปรับ แต่ทำแล้วก็ไม่เห็นการเปลี่ยนแปลง ปรับได้นะครับ อยู่ที่ Preferences/Brightness
-ตัว Software Center เหมือนขาดบางอย่างไป เช่น OpendJDK-jdk ก็ไม่มี แล้วจะเขียน JAVA ยังไง ต้องไปติดตั้งผ่าน terminal เอง

ถ้าเจออะไรจะมาเขียนเพิ่มนะครับ

สรุป
ชอบ lxle ค่อนข้างมากครับ ตรงกับความต้องการที่อยากใช้คอมเก่ากับงานง่ายๆ ทำให้รู้สึกดีใจที่สามารถเอาคอมเดิมที่อยู่ในกรุ ปัดฝุ่นขึ้นมาทำงานอีกครั้ง (บางทีเราก็มีความผูกพันกับคอมใช่ไหมล่ะ เช่น คอมเครื่องนี้ใช้มาตั้งแต่ตอนเรียนเลยนะ) คิดว่าถ้ามีคนถามว่าคอมเก่าแล้ว อยากเปลี่ยนใหม่เพราะมันช้า จะตอบว่าถ้าแค่ใช้งานทั่วไป ลอง lxle ไหมล่ะ

ปิดท้ายด้วยรูปหน้าจอครับ


Thursday, November 20, 2014

jQuery Mobile กับปัญหาการคลิกเอง (Firing twice / Firing multiple times)

เขียน jQuery Mobile กับ Cordova อยู่ เจอปัญหาปุ่มถูกคลิกเอง คลิกหลายๆครั้งด้วย ทำให้กระโดดไปหน้าอื่นทั้งๆที่ยังไม่ได้คลิก

ไปค้นดูครับ คนเจอปัญหาเยอะแยะ เช่น
http://stackoverflow.com/questions/14411532/all-jquery-mobile-events-firing-twice
http://stackoverflow.com/questions/10794181/jquery-mobile-tap-event-triggered-for-twice

และมีหลายวิธีแก้เหลือเกิน เช่น
1. เปลี่ยน event สำหรับการแตะ จาก tap เป็น click คือให้ใช้
$("#btt").off().on("click",function(){
 //do something
})

2. ใช้ off ปิด event ก่อนหน้า แล้วตามด้วย on เช่น
$("#btt").off().on("tap",function(){
      //off then on
});

หรือ
$("#btt").off("tap").on("tap",function(){
      //off then on
});

3. ย้าย script ไปไว้ระหว่างแท็ก head ไม่ใช่ส่วน body

4. เพิ่ม event.preventDefault(); เช่น
$("#btt").on("tap",function(event){
    event.preventDefault();
    //other codes
});

5. วางลำดับการอ้างถึง JavaScript และแทรก script ต่อไปนี้ลงไป
<link rel="stylesheet" href="css/jquery.mobile-1.4.4.min.css" />
<script src="js/jquery-2.1.1.min.js"></script>
<script>
 $(document).on("mobileinit", function() {
  $.mobile.ajaxEnabled = false;
  $.mobile.pushStateEnabled = false;
  $.mobile.linkBindingEnabled = false;
  $.mobile.hashListeningEnabled = false;
  $.mobile.defaultTransition = 'none';
 });   
</script>
<script src="js/jquery.mobile-1.4.4.min.js"></script>  
<script src="js/index.js"></script>
<script src="cordova.js"></script>
<script src="cordova_plugins.js"></script>

และอีกมากมาย

ส่วนตัวผมเองเจอปัญหากับ tap แล้วแค่พอเปลี่ยนเป็น click ก็หาย

สาเหตุอีกอย่างนึง ที่สังเกตดูคือ ถ้าเราสร้างแอพโดยมีหลายๆ หน้า และแต่ละหน้าเชื่อมโยงกันไปมา คือสามารถกระโดดไป-กลับ ไปหน้าอื่นๆได้
การกำหนด event สำหรับปุ่มต่างๆ ควรจะเอาไว้ใน

$(document).on("pagecreate","#pageID",function(){
    //begin code here
});

ส่วนตัวแปรที่ต้องการอัพเดททุกครั้ง เช่น เพื่อการแสดงผล ให้เอาไว้ใน

$(document).on("pagebeforeshow","#pageID",function(){
    //begin code here
});

ไม่ควรเอา event สำหรับปุ่มไว้ใน page event: pagebeforeshow เพราะทุกครั้งที่กลับมาที่หน้านี้ ผมเข้าใจว่าปุ่มมันเหมือนจะถูกกำหนด event สองครั้งซ้อน เมื่อเรากดปุ่มก็เลยเหมือนกดสองครั้ง

ถ้าใครเจอปัญหานี้ ลองวิธีแก้ต่างๆข้างต้นดูครับ