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