text: #!/usr/bin/env python
  # -*- coding: utf-8 -*-
  
  u'''
  An script to crack captchas of `正方教务系统`.
  Greatly inspired by "http://www.wausita.com/captcha/"
  rewrite by reverland
  
  Usage:
  
      >>> load_trainset()
      True
      >>> crack_file(u'test1.gif')
      u'48353'
      >>> crack_file(u'test2.gif')
      u'85535'
      >>> crack_buf(open('test3.gif', 'rb').read())
      u'23297'
  
  '''
  
  from __future__ import unicode_literals, division, print_function
  
  import sys
  import os
  import math
  import bz2
  import json
  
  try:
      import cStringIO as StringIO
  except ImportError:
      import StringIO
  
  from PIL import Image
  
  _TRAINSET = []
  
  PKG_PATH = os.path.realpath(os.path.split(__file__)[0])
  TRAINSET_FILENAME = 'trainset.json.bz2'
  
  
  def process_image(im):
      '''Get an image file and return a black-white Image object'''
      im2 = Image.new("P", im.size, 255)
      for x in range(im.size[0]):
          for y in range(im.size[1]):
              pix = im.getpixel((x, y))
              if pix != 212 and pix != 169 and pix != 255 and pix != 40:
                  # these are the numbers to get
                  im2.putpixel((x, y), 0)
      return im2
  
  
  def image_from_file(filename):
      im = Image.open(filename)
      return process_image(im.convert('P'))
  
  
  def image_from_buffer(buf):
      fp = StringIO.StringIO(buf)
      im = Image.open(fp)
      return process_image(im)
  
  
  def find_letters(im):
      '''Find where letters begin and end'''
      inletter = False
      foundletter = False
      start = 0
      end = 0
      letters = []
      for x in range(im.size[0]):  # slice across
          for y in range(im.size[1]):  # slice down
              pix = im.getpixel((x, y))
              if pix != 255:
                  inletter = True
          if foundletter is False and inletter is True:
              foundletter = True
              start = x
          if foundletter is True and inletter is False:
              foundletter = False
              end = x
              letters.append((start, end))
          inletter = False
      return letters
  
  
  def magnitude(lst):
      '''Calculate for magnitude of a lst'''
      total = 0
      for c in lst:
          total += c ** 2
      return math.sqrt(total)
  
  
  def relation(lst1, lst2):
      '''Calculate relation between two given list based on vector space theory.
      Truncate when not the same length.'''
      dot_multiply = 0
      if len(lst1) == len(lst2):
          for i in xrange(len(lst1)):
              dot_multiply += lst1[i] * lst2[i]
          cosin = dot_multiply / (magnitude(lst1) * (magnitude(lst2)))
      elif len(lst1) > len(lst2):
          lst1 = lst1[:len(lst2)]
          cosin = relation(lst1, lst2)
      else:
          lst2 = lst2[:len(lst1)]
          cosin = relation(lst1, lst2)
      return cosin
  
  
  def buildvector(im):
      '''buildvector for Image object, return list'''
      lst = []
      for p in im.getdata():
          lst.append(p)
      return lst
  
  
  ## Functions to crack
  def guess(im, trainset):
      '''guess what's the image is and return a tuple (relation, character)'''
      guess = []
      for image in trainset:
          for k, v in image.items():
              guess.append((relation(v, buildvector(im)), k))
      guess.sort(reverse=True)
      return guess[0]
  
  
  ## Crack image
  def crack_image(im):
      trainset = _TRAINSET[0]
      letters = find_letters(im)
      results = ''
      for letter in letters:
          im2 = im.crop((letter[0], 0, letter[1], im.size[1]))
          digit = guess(im2, trainset)[1]
          results += digit
      return results
  
  
  def crack_file(filename):
      '''receive filename and trainset, give out crack result'''
  
      return crack_image(image_from_file(filename))
  
  
  def crack_buf(buf):
      '''Crack image stored in a memory buffer.
  
      The image must be a 60x22 GIF, as generated by the ZFsoft
      system.
  
      '''
  
      return crack_image(image_from_buffer(buf))
  
  
  def load_trainset(filename=TRAINSET_FILENAME):
      if _TRAINSET:
          return True
  
      path = os.path.join(PKG_PATH, filename)
      with open(path, 'rb') as fp:
          trainset_jsonbz2 = fp.read()
  
      _TRAINSET.append(json.loads(bz2.decompress(trainset_jsonbz2)))
      return True
  
  
  # vim:set ai et ts=4 sw=4 sts=4 fenc=utf-8:
  