[
  {
    "path": "README.md",
    "content": "# Fingerprint recognition algorithms\n\nActive development year: 2012\n\n## Summary\nSome implementations of fingerprint recognition algorithms developed for Biometric Methods course at University of Wrocław, Poland.\n\n## Usage\n\n### Prerequisites\n* python 2.7\n* python imaging library (PIL)\n\n### How to use it\nSimply do ```python filename.py --help``` to figure out how to execute ```filename``` algorithm\n\n## Algorithms\n\n### Poincaré Index\nFinds singular points on fingerprint. \n\nHow it works (more detailed description [here](http://books.google.pl/books?id=1Wpx25D8qOwC&lpg=PA120&ots=9wRY0Rosb7&dq=poincare%20index%20fingerprint&hl=pl&pg=PA120#v=onepage&q=poincare%20index%20fingerprint&f=false)):\n* divide image into blocks of ```block_size```\n* for each block: \n    * calculate orientation of the fingerprint ridge in that block (i.e. what is the rigde slope / angle between a ridge and horizon)\n    * sum up the differences of angles (orientations) of the surrounding blocks\n    * there are 4 cases:\n        * sum is 180 (+- tolerance) - loop found\n        * sum is -180 (+- tolerance) - delta found\n        * sum is 360 (+- tolerance) - whorl found\n\nThe python script will mark the singularities with circles:\n* red for loop\n* green for delta\n* blue for whorl\n      \nExample: ```python poincare.py images/ppf1.png 16 1 --smooth```\n\nImages:\n* Original \n\n![fingerprint](https://raw.github.com/rtshadow/biometrics/master/images/ppf1.png)\n\n* With singular points marked by algorithm: \n\n![poincare](https://raw.github.com/rtshadow/biometrics/master/images/ppf1_poincare.gif)\n\nNote: algorithm marked singular points not only inside fingerprint itself, but on its edges and even outside. This is a result of usage of non-preprocessed image - if the image was enhanced (better contrast, background removed), then only singular points inside fingerprint would be marked.\n\n### Thinning (skeletonization)\n\nHow it [works] (http://bme.med.upatras.gr/improc/Morphological%20operators.htm#Thining)\n\nExample: ```python thining.py images/ppf1_enhanced.gif --save```\n\nImages:\n* Before\n\n![before](https://raw.github.com/rtshadow/biometrics/master/images/ppf1_enhanced.gif)\n\n* After:\n\n![after](https://raw.github.com/rtshadow/biometrics/master/images/ppf1_enhanced_thinned.gif)\n\n### Minutiae recognition (crossing number method)\nCrossing number methods is a really simple way to detect ridge endings and ridge bifurcations.\n\nFirst, you'll need thinned (skeleton) image (refer to previous section how to get it). Then the crossing number algorithm will look at 3x3 pixel blocks:\n* if middle pixel is black (represents ridge):\n    * if pixel on boundary are crossed with the ridge once, then we've found ridge ending\n    * if pixel on boundary are crossed with the ridge three times, then we've found ridge bifurcation\n    \nExample: ```python crossing_number.py images/ppf1_enhanced_thinned.gif --save```\n\n![minutiae](https://raw.github.com/rtshadow/biometrics/master/images/ppf1_enhanced_thinned_minutiae.gif)\n"
  },
  {
    "path": "crossing_number.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageDraw\nimport utils\nimport argparse\nimport math\nimport os\n\ncells = [(-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1)]\n\ndef minutiae_at(pixels, i, j):\n    values = [pixels[i + k][j + l] for k, l in cells]\n\n    crossings = 0\n    for k in range(0, 8):\n        crossings += abs(values[k] - values[k + 1])\n    crossings /= 2\n\n    if pixels[i][j] == 1:\n        if crossings == 1:\n            return \"ending\"\n        if crossings == 3:\n            return \"bifurcation\"\n    return \"none\"\n\ndef calculate_minutiaes(im):\n    pixels = utils.load_image(im)\n    utils.apply_to_each_pixel(pixels, lambda x: 0.0 if x > 10 else 1.0)\n\n    (x, y) = im.size\n    result = im.convert(\"RGB\")\n\n    draw = ImageDraw.Draw(result)\n\n    colors = {\"ending\" : (150, 0, 0), \"bifurcation\" : (0, 150, 0)}\n\n    ellipse_size = 2\n    for i in range(1, x - 1):\n        for j in range(1, y - 1):\n            minutiae = minutiae_at(pixels, i, j)\n            if minutiae != \"none\":\n                draw.ellipse([(i - ellipse_size, j - ellipse_size), (i + ellipse_size, j + ellipse_size)], outline = colors[minutiae])\n\n    del draw\n\n    return result\n\nparser = argparse.ArgumentParser(description=\"Minutiae detection using crossing number method\")\nparser.add_argument(\"image\", nargs=1, help = \"Skeleton image\")\nparser.add_argument(\"--save\", action='store_true', help = \"Save result image as src_minutiae.gif\")\nargs = parser.parse_args()\n\nim = Image.open(args.image[0])\nim = im.convert(\"L\")  # covert to grayscale\n\nresult = calculate_minutiaes(im)\nresult.show()\n\nif args.save:\n    base_image_name = os.path.splitext(os.path.basename(args.image[0]))[0]\n    result.save(base_image_name + \"_minutiae.gif\", \"GIF\")\n"
  },
  {
    "path": "frequency.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageDraw\nimport utils\nimport argparse\nimport math\n\n\ndef points_on_line(line, W):\n    im = Image.new(\"L\", (W, 3 * W), 100)\n    draw = ImageDraw.Draw(im)\n    draw.line([(0, line(0) + W), (W, line(W) + W)], fill=10)\n    im_load = im.load()\n\n    points = []\n    for x in range(0, W):\n        for y in range(0, 3 * W):\n            if im_load[x, y] == 10:\n               points.append((x, y - W))\n\n    del draw\n    del im\n\n    dist = lambda (x, y): (x - W / 2) ** 2 + (y - W / 2) ** 2\n\n    return sorted(points, cmp = lambda x, y: dist(x) < dist(y))[:W]\n\ndef vec_and_step(tang, W):\n    (begin, end) = utils.get_line_ends(0, 0, W, tang)\n    (x_vec, y_vec) = (end[0] - begin[0], end[1] - begin[1])\n    length = math.hypot(x_vec, y_vec)\n    (x_norm, y_norm) = (x_vec / length, y_vec / length)\n    step = length / W\n\n    return (x_norm, y_norm, step)\n\ndef block_frequency(i, j, W, angle, im_load):\n    tang = math.tan(angle)\n    ortho_tang = -1 / tang\n\n    (x_norm, y_norm, step) = vec_and_step(tang, W)\n    (x_corner, y_corner) = (0 if x_norm >= 0 else W, 0 if y_norm >= 0 else W)\n\n    grey_levels = []\n\n    for k in range(0, W):\n        line = lambda x: (x - x_norm * k * step - x_corner) * ortho_tang + y_norm * k * step + y_corner\n        points = points_on_line(line, W)\n        level = 0\n        for point in points:\n            level += im_load[point[0] + i * W, point[1] + j * W]\n        grey_levels.append(level)\n\n    treshold = 100\n    upward = False\n    last_level = 0\n    last_bottom = 0\n    count = 0.0\n    spaces = len(grey_levels)\n    for level in grey_levels:\n        if level < last_bottom:\n            last_bottom = level\n        if upward and level < last_level:\n            upward = False\n            if last_bottom + treshold < last_level:\n                count += 1\n                last_bottom = last_level\n        if level > last_level:\n            upward = True\n        last_level = level\n\n    return count / spaces if spaces > 0 else 0\n\ndef freq(im, W, angles):\n    (x, y) = im.size\n    im_load = im.load()\n    freqs = [[0] for i in range(0, x / W)]\n\n    for i in range(1, x / W - 1):\n        for j in range(1, y / W - 1):\n            freq = block_frequency(i, j, W, angles[i][j], im_load)\n            freqs[i].append(freq)\n        freqs[i].append(0)\n\n    freqs[0] = freqs[-1] = [0 for i in range(0, y / W)]\n\n    return freqs\n\ndef freq_img(im, W, angles):\n    (x, y) = im.size\n    freqs = freq(im, W, angles)\n    freq_img = im.copy()\n\n    for i in range(1, x / W - 1):\n        for j in range(1, y / W - 1):\n            box = (i * W, j * W, min(i * W + W, x), min(j * W + W, y))\n            freq_img.paste(freqs[i][j] * 255.0 * 1.2, box)\n\n    return freq_img\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Image frequency\")\n    parser.add_argument(\"image\", nargs=1, help = \"Path to image\")\n    parser.add_argument(\"block_size\", nargs=1, help = \"Block size\")\n    parser.add_argument('--smooth', \"-s\", action='store_true', help = \"Use Gauss for smoothing\")\n    args = parser.parse_args()\n\n    im = Image.open(args.image[0])\n    im = im.convert(\"L\")  # covert to grayscale\n    im.show()\n\n    W = int(args.block_size[0])\n\n    f = lambda x, y: 2 * x * y\n    g = lambda x, y: x ** 2 - y ** 2\n\n    angles = utils.calculate_angles(im, W, f, g)\n    if args.smooth:\n        angles = utils.smooth_angles(angles)\n\n    freq_img = freq_img(im, W, angles)\n    freq_img.show()\n"
  },
  {
    "path": "gabor.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageDraw\nimport utils\nimport argparse\nimport math\nimport frequency\nimport os\n\ndef gabor_kernel(W, angle, freq):\n    cos = math.cos(angle)\n    sin = math.sin(angle)\n\n    yangle = lambda x, y: x * cos + y * sin\n    xangle = lambda x, y: -x * sin + y * cos\n\n    xsigma = ysigma = 4\n\n    return utils.kernel_from_function(W, lambda x, y:\n        math.exp(-(\n            (xangle(x, y) ** 2) / (xsigma ** 2) +\n            (yangle(x, y) ** 2) / (ysigma ** 2)) / 2) *\n        math.cos(2 * math.pi * freq * xangle(x, y)))\n\ndef gabor(im, W, angles):\n    (x, y) = im.size\n    im_load = im.load()\n\n    freqs = frequency.freq(im, W, angles)\n    print \"computing local ridge frequency done\"\n\n    gauss = utils.gauss_kernel(3)\n    utils.apply_kernel(freqs, gauss)\n\n    for i in range(1, x / W - 1):\n        for j in range(1, y / W - 1):\n            kernel = gabor_kernel(W, angles[i][j], freqs[i][j])\n            for k in range(0, W):\n                for l in range(0, W):\n                    im_load[i * W + k, j * W + l] = utils.apply_kernel_at(\n                        lambda x, y: im_load[x, y],\n                        kernel,\n                        i * W + k,\n                        j * W + l)\n\n    return im\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Gabor filter applied\")\n    parser.add_argument(\"image\", nargs=1, help = \"Path to image\")\n    parser.add_argument(\"block_size\", nargs=1, help = \"Block size\")\n    parser.add_argument(\"--save\", action='store_true', help = \"Save result image as src_image_enhanced.gif\")\n    args = parser.parse_args()\n\n    im = Image.open(args.image[0])\n    im = im.convert(\"L\")  # covert to grayscale\n    im.show()\n\n    W = int(args.block_size[0])\n\n    f = lambda x, y: 2 * x * y\n    g = lambda x, y: x ** 2 - y ** 2\n\n    angles = utils.calculate_angles(im, W, f, g)\n    print \"calculating orientation done\"\n\n    angles = utils.smooth_angles(angles)\n    print \"smoothing angles done\"\n\n    result = gabor(im, W, angles)\n    result.show()\n\n    if args.save:\n        base_image_name = os.path.splitext(os.path.basename(args.image[0]))[0]\n        im.save(base_image_name + \"_enhanced.gif\", \"GIF\")\n"
  },
  {
    "path": "hough.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageDraw\nimport utils\nimport argparse\nimport math\n\n\ndef get_hough_image(im):\n    (x, y) = im.size\n    x *= 1.0\n    y *= 1.0\n\n    im_load = im.load()\n\n    result = Image.new(\"RGBA\", im.size, 0)\n    draw = ImageDraw.Draw(result)\n\n    for i in range(0, im.size[0]):\n        for j in range(0, im.size[1]):\n            if im_load[i, j] > 220:\n                line = lambda t: (t, (-(i / x - 0.5) * (t / x) + (j / y - 0.5)) * x)\n                draw.line([line(0), line(x)], fill=(50, 0, 0, 10))\n\n    return result\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Hough transform\")\n    parser.add_argument(\"image\", nargs=1, help = \"Path to image\")\n    args = parser.parse_args()\n\n    im = Image.open(args.image[0])\n    im = im.convert(\"L\")  # covert to grayscale\n    im.show()\n\n    hough_img = get_hough_image(im)\n    hough_img.show()\n"
  },
  {
    "path": "normalization.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageStat\nimport argparse\nfrom math import sqrt\n\n# x - pixel value\n# v0 - desired variance\n# v - actual image variance\n# m - actual image mean\n# m0 - desired mean\ndef normalize_pixel(x, v0, v, m, m0):\n    dev_coeff = sqrt((v0 * ((x - m)**2)) / v)\n    if x > m:\n        return m0 + dev_coeff\n    return m0 - dev_coeff\n\ndef normalize(im, m0, v0):\n    stat = ImageStat.Stat(im)\n    m = stat.mean[0]\n    v = stat.stddev[0] ** 2\n\n    return im.point(lambda x: normalize_pixel(x, v0, v, m, m0))  # normalize each pixel\n\nparser = argparse.ArgumentParser(description=\"Image normalization\")\nparser.add_argument(\"image\", nargs=1, help = \"Path to image\")\nparser.add_argument(\"mean\", nargs=1, help = \"desired mean\")\nparser.add_argument(\"variance\", nargs=1, help = \"desired variance (squared stdev)\")\nargs = parser.parse_args()\n\nim = Image.open(args.image[0])\nim = im.convert(\"L\")  # covert to grayscale\n\nim.show()\n\nnormalizedIm = normalize(im, float(args.mean[0]), float(args.variance[0]))\nnormalizedIm.show()\n"
  },
  {
    "path": "orientation.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageDraw\nimport utils\nimport argparse\n\nparser = argparse.ArgumentParser(description=\"Rao's and Chinese algorithms\")\nparser.add_argument(\"image\", nargs=1, help = \"Path to image\")\nparser.add_argument(\"block_size\", nargs=1, help = \"Block size\")\nparser.add_argument('--smooth', \"-s\", action='store_true', help = \"Use Gauss for smoothing\")\nparser.add_argument('--chinese', \"-c\", action='store_true', help = \"Use Chinese alg. instead of Rao's\")\nargs = parser.parse_args()\n\nim = Image.open(args.image[0])\nim = im.convert(\"L\")  # covert to grayscale\n\nW = int(args.block_size[0])\n\nf = lambda x, y: 2 * x * y\ng = lambda x, y: x ** 2 - y ** 2\n\nif args.chinese:\n    normalizator = 255.0\n    f = lambda x, y: 2 * x * y / (normalizator ** 2)\n    g = lambda x, y: ((x ** 2) * (y ** 2)) / (normalizator ** 4)\n\nangles = utils.calculate_angles(im, W, f, g)\nutils.draw_lines(im, angles, W).show()\n\nif args.smooth:\n    smoothed_angles = utils.smooth_angles(angles)\n    utils.draw_lines(im, smoothed_angles, W).show()\n"
  },
  {
    "path": "poincare.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageDraw\nimport utils\nimport argparse\nimport math\nimport os\n\nsignum = lambda x: -1 if x < 0 else 1\n\ncells = [(-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1)]\n\ndef get_angle(left, right):\n    angle = left - right\n    if abs(angle) > 180:\n        angle = -1 * signum(angle) * (360 - abs(angle))\n    return angle\n\ndef poincare_index_at(i, j, angles, tolerance):\n    deg_angles = [math.degrees(angles[i - k][j - l]) % 180 for k, l in cells]\n    index = 0\n    for k in range(0, 8):\n        if abs(get_angle(deg_angles[k], deg_angles[k + 1])) > 90:\n            deg_angles[k + 1] += 180\n        index += get_angle(deg_angles[k], deg_angles[k + 1])\n\n    if 180 - tolerance <= index and index <= 180 + tolerance:\n        return \"loop\"\n    if -180 - tolerance <= index and index <= -180 + tolerance:\n        return \"delta\"\n    if 360 - tolerance <= index and index <= 360 + tolerance:\n        return \"whorl\"\n    return \"none\"\n\ndef calculate_singularities(im, angles, tolerance, W):\n    (x, y) = im.size\n    result = im.convert(\"RGB\")\n\n    draw = ImageDraw.Draw(result)\n\n    colors = {\"loop\" : (150, 0, 0), \"delta\" : (0, 150, 0), \"whorl\": (0, 0, 150)}\n\n    for i in range(1, len(angles) - 1):\n        for j in range(1, len(angles[i]) - 1):\n            singularity = poincare_index_at(i, j, angles, tolerance)\n            if singularity != \"none\":\n                draw.ellipse([(i * W, j * W), ((i + 1) * W, (j + 1) * W)], outline = colors[singularity])\n\n    del draw\n\n    return result\n\nparser = argparse.ArgumentParser(description=\"Singularities with Poincare index\")\nparser.add_argument(\"image\", nargs=1, help = \"Path to image\")\nparser.add_argument(\"block_size\", nargs=1, help = \"Block size\")\nparser.add_argument(\"tolerance\", nargs=1, help = \"Tolerance for Poincare index\")\nparser.add_argument('--smooth', \"-s\", action='store_true', help = \"Use Gauss for smoothing\")\nparser.add_argument(\"--save\", action='store_true', help = \"Save result image as src_poincare.gif\")\nargs = parser.parse_args()\n\nim = Image.open(args.image[0])\nim = im.convert(\"L\")  # covert to grayscale\n\nW = int(args.block_size[0])\n\nf = lambda x, y: 2 * x * y\ng = lambda x, y: x ** 2 - y ** 2\n\nangles = utils.calculate_angles(im, W, f, g)\nif args.smooth:\n    angles = utils.smooth_angles(angles)\n\nresult = calculate_singularities(im, angles, int(args.tolerance[0]), W)\nresult.show()\n\nif args.save:\n    base_image_name = os.path.splitext(os.path.basename(args.image[0]))[0]\n    result.save(base_image_name + \"_poincare.gif\", \"GIF\")\n"
  },
  {
    "path": "segmentation.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageStat\nimport argparse\nfrom math import sqrt\n\ndef distance(x, y, W):\n    return 1 + sqrt((x - W) ** 2 + (y - W) ** 2)\n\ndef create_segmented_and_variance_images(im, W, threshold):\n    (x, y) = im.size\n    variance_image = im.copy()\n    segmented_image = im.copy()\n    for i in range(0, x, W):\n        for j in range(0, y, W):\n            box = (i, j, min(i + W, x), min(j + W, y))\n            block_stddev = ImageStat.Stat(im.crop(box)).stddev[0]\n            variance_image.paste(block_stddev, box)\n            if block_stddev < threshold:\n                segmented_image.paste(0, box)  # make block black if rejected\n    return (segmented_image, variance_image)\n\nparser = argparse.ArgumentParser(description=\"Image segmentation\")\nparser.add_argument(\"image\", nargs=1, help = \"Path to image\")\nparser.add_argument(\"block_size\", nargs=1, help = \"Block size\")\nparser.add_argument(\"threshold\", nargs=1, help = \"Treshold on stddev for accepting / rejecting blocks\")\nargs = parser.parse_args()\n\nim = Image.open(args.image[0])\nim = im.convert(\"L\")  # covert to grayscale\n\nim.show()\n\n(segmented, variance) = create_segmented_and_variance_images(im, int(args.block_size[0]), int(args.threshold[0]))\nsegmented.show()\nvariance.show()\n"
  },
  {
    "path": "sobel.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageFilter\nfrom math import sqrt\nimport utils\n\nsobelOperator = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]\n\ndef merge_images(a, b, f):\n    result = a.copy()\n    result_load = result.load()\n    a_load = a.load()\n    b_load = b.load()\n\n    (x, y) = a.size\n    for i in range(0, x):\n        for j in range(0, y):\n            result_load[i, j] = f(a_load[i, j], b_load[i, j])\n\n    return result\n\ndef partial_sobels(im):\n    ySobel = im.filter(ImageFilter.Kernel((3, 3), utils.flatten(sobelOperator), 1))\n    xSobel = im.filter(ImageFilter.Kernel((3, 3), utils.flatten(utils.transpose(sobelOperator)), 1))\n    return (xSobel, ySobel)\n\ndef full_sobels(im):\n    (xSobel, ySobel) = partial_sobels(im)\n    sobel = merge_images(xSobel, ySobel, lambda x, y: sqrt(x**2 + y**2))\n    return (xSobel, ySobel, sobel)\n"
  },
  {
    "path": "sobel_showcase.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nimport sobel\nimport argparse\nfrom PIL import Image\n\nparser = argparse.ArgumentParser(description=\"Sobel filter\")\nparser.add_argument(\"image\", nargs=1, help = \"Path to image\")\nparser.add_argument('--showX', \"-x\", action='store_true', help = \"Show Sobel filter for X coordinate\")\nparser.add_argument('--showY', \"-y\", action='store_true', help = \"Show Sobel filter for Y coordinate\")\nargs = parser.parse_args()\n\nim = Image.open(args.image[0])\nim = im.convert(\"L\")  # covert to grayscale\n\n(xSobel, ySobel, fullSobel) = sobel.full_sobels(im)\n\nif args.showX:\n    xSobel.show()\nif args.showY:\n    ySobel.show()\nfullSobel.show()\n"
  },
  {
    "path": "thining.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageDraw\nimport utils\nimport argparse\nimport math\nimport os\nfrom utils import flatten, transpose\n\nusage = False\n\ndef apply_structure(pixels, structure, result):\n    global usage\n    usage = False\n\n    def choose(old, new):\n        global usage\n        if new == result:\n            usage = True\n            return 0.0\n        return old\n\n    utils.apply_kernel_with_f(pixels, structure, choose)\n\n    return usage\n\ndef apply_all_structures(pixels, structures):\n    usage = False\n    for structure in structures:\n        usage |= apply_structure(pixels, structure, utils.flatten(structure).count(1))\n\n    return usage\n\ndef make_thin(im):\n    loaded = utils.load_image(im)\n    utils.apply_to_each_pixel(loaded, lambda x: 0.0 if x > 10 else 1.0)\n    print \"loading phase done\"\n\n    t1 = [[1, 1, 1], [0, 1, 0], [0.1, 0.1, 0.1]]\n    t2 = utils.transpose(t1)\n    t3 = reverse(t1)\n    t4 = utils.transpose(t3)\n    t5 = [[0, 1, 0], [0.1, 1, 1], [0.1, 0.1, 0]]\n    t7 = utils.transpose(t5)\n    t6 = reverse(t7)\n    t8 = reverse(t5)\n\n    thinners = [t1, t2, t3, t4, t5, t6, t7]\n\n    usage = True\n    while(usage):\n        usage = apply_all_structures(loaded, thinners)\n        print \"single thining phase done\"\n\n    print \"thining done\"\n\n    utils.apply_to_each_pixel(loaded, lambda x: 255.0 * (1 - x))\n    utils.load_pixels(im, loaded)\n    im.show()\n\ndef reverse(ls):\n    cpy = ls[:]\n    cpy.reverse()\n    return cpy\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Image thining\")\n    parser.add_argument(\"image\", nargs=1, help = \"Path to image\")\n    parser.add_argument(\"--save\", action='store_true', help = \"Save result image as src_image_thinned.gif\")\n    args = parser.parse_args()\n\n    im = Image.open(args.image[0])\n    im = im.convert(\"L\")  # covert to grayscale\n    im.show()\n\n    make_thin(im)\n\n    if args.save:\n        base_image_name = os.path.splitext(os.path.basename(args.image[0]))[0]\n        im.save(base_image_name + \"_thinned.gif\", \"GIF\")\n"
  },
  {
    "path": "utils.py",
    "content": "# Metody biometryczne\n# Przemyslaw Pastuszka\n\nfrom PIL import Image, ImageDraw\nimport math\nimport sobel\nimport copy\n\ndef apply_kernel_at(get_value, kernel, i, j):\n    kernel_size = len(kernel)\n    result = 0\n    for k in range(0, kernel_size):\n        for l in range(0, kernel_size):\n            pixel = get_value(i + k - kernel_size / 2, j + l - kernel_size / 2)\n            result += pixel * kernel[k][l]\n    return result\n\ndef apply_to_each_pixel(pixels, f):\n    for i in range(0, len(pixels)):\n        for j in range(0, len(pixels[i])):\n            pixels[i][j] = f(pixels[i][j])\n\ndef calculate_angles(im, W, f, g):\n    (x, y) = im.size\n    im_load = im.load()\n    get_pixel = lambda x, y: im_load[x, y]\n\n    ySobel = sobel.sobelOperator\n    xSobel = transpose(sobel.sobelOperator)\n\n    result = [[] for i in range(1, x, W)]\n\n    for i in range(1, x, W):\n        for j in range(1, y, W):\n            nominator = 0\n            denominator = 0\n            for k in range(i, min(i + W , x - 1)):\n                for l in range(j, min(j + W, y - 1)):\n                    Gx = apply_kernel_at(get_pixel, xSobel, k, l)\n                    Gy = apply_kernel_at(get_pixel, ySobel, k, l)\n                    nominator += f(Gx, Gy)\n                    denominator += g(Gx, Gy)\n            angle = (math.pi + math.atan2(nominator, denominator)) / 2\n            result[(i - 1) / W].append(angle)\n\n    return result\n\ndef flatten(ls):\n    return reduce(lambda x, y: x + y, ls, [])\n\ndef transpose(ls):\n    return map(list, zip(*ls))\n\ndef gauss(x, y):\n    ssigma = 1.0\n    return (1 / (2 * math.pi * ssigma)) * math.exp(-(x * x + y * y) / (2 * ssigma))\n\ndef kernel_from_function(size, f):\n    kernel = [[] for i in range(0, size)]\n    for i in range(0, size):\n        for j in range(0, size):\n            kernel[i].append(f(i - size / 2, j - size / 2))\n    return kernel\n\ndef gauss_kernel(size):\n    return kernel_from_function(size, gauss)\n\ndef apply_kernel(pixels, kernel):\n    apply_kernel_with_f(pixels, kernel, lambda old, new: new)\n\ndef apply_kernel_with_f(pixels, kernel, f):\n    size = len(kernel)\n    for i in range(size / 2, len(pixels) - size / 2):\n        for j in range(size / 2, len(pixels[i]) - size / 2):\n            pixels[i][j] = f(pixels[i][j], apply_kernel_at(lambda x, y: pixels[x][y], kernel, i, j))\n\ndef smooth_angles(angles):\n    cos_angles = copy.deepcopy(angles)\n    sin_angles = copy.deepcopy(angles)\n    apply_to_each_pixel(cos_angles, lambda x: math.cos(2 * x))\n    apply_to_each_pixel(sin_angles, lambda x: math.sin(2 * x))\n\n    kernel = gauss_kernel(5)\n    apply_kernel(cos_angles, kernel)\n    apply_kernel(sin_angles, kernel)\n\n    for i in range(0, len(cos_angles)):\n        for j in range(0, len(cos_angles[i])):\n            cos_angles[i][j] = (math.atan2(sin_angles[i][j], cos_angles[i][j])) / 2\n\n    return cos_angles\n\ndef load_image(im):\n    (x, y) = im.size\n    im_load = im.load()\n\n    result = []\n    for i in range(0, x):\n        result.append([])\n        for j in range(0, y):\n            result[i].append(im_load[i, j])\n\n    return result\n\ndef load_pixels(im, pixels):\n    (x, y) = im.size\n    im_load = im.load()\n\n    for i in range(0, x):\n        for j in range(0, y):\n            im_load[i, j] = pixels[i][j]\n\ndef get_line_ends(i, j, W, tang):\n    if -1 <= tang and tang <= 1:\n        begin = (i, (-W/2) * tang + j + W/2)\n        end = (i + W, (W/2) * tang + j + W/2)\n    else:\n        begin = (i + W/2 + W/(2 * tang), j + W/2)\n        end = (i + W/2 - W/(2 * tang), j - W/2)\n    return (begin, end)\n\ndef draw_lines(im, angles, W):\n    (x, y) = im.size\n    result = im.convert(\"RGB\")\n\n    draw = ImageDraw.Draw(result)\n\n    for i in range(1, x, W):\n        for j in range(1, y, W):\n            tang = math.tan(angles[(i - 1) / W][(j - 1) / W])\n\n            (begin, end) = get_line_ends(i, j, W, tang)\n            draw.line([begin, end], fill=150)\n\n    del draw\n\n    return result\n"
  }
]