5
\$\begingroup\$

I wanted to start contributing to the Open Source community, and decided to release some of the Python tools I have written awhile ago. The problem is that I am not really familiar with good practices, and would really appreciate any advice and/or pointers you guys could give me.

The code below is a package to visualize the results of the NIST NBIS library for fingerprints. I tried putting the PyDoc in the code as well, in case you need more information. I am planning on pushing it to the PyPI, which leads me to another question - should I or should I not? I don't want to pollute the PyPI with something useless and of low quality.

I would really appreciate if you could critique the code.

Here is the code repository

This is the structure:

fpview
├── LICENSE
├── README.md
├── demo.png
├── demo.py
├── orig.jpg
├── proc.png
└── fpview
    ├── __init__.py
    ├── dm.py
    ├── hcm.py
    ├── lfm.py
    ├── qm.py
    ├── map.py
    ├── imgtools.py
    └── tests ## This doesn't work yet
        ├── __init__.py
        ├── data
        │   ├── test.brw
        │   ├── test.dm
        │   ├── test.hcm
        │   ├── test.lcm
        │   ├── test.lfm
        │   ├── test.min
        │   ├── test.qm
        │   └── test.xyt
        └── test_all.py # This doesn't work yet

fpview/demo.py

#!/usr/bin/env python

from fpview import (DirectionMap, LowFlowMap, HighCurvatureMap, QualityMap)
# from fpview import (Map,ImgTools)

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as ptch

from PIL import Image
from mpl_toolkits.axes_grid.anchored_artists import AnchoredDrawingArea


# Set the location of the test data
from os import sys, path
test_dir = path.dirname(path.abspath(__file__)) + '/fpview/tests/data/'

# Create a figure
fig, ax = plt.subplots(1,2)
# ax = plt.gca()
for axes in ax:
    axes.set_frame_on(False)
    axes.set_axis_off()


plt.hold(True)

# Show Direction Map
dm = DirectionMap(test_dir+'test.dm', fg = [0,0,0,1], bg = [1,1,1,1])
dm.create()
dm.plot(ax=ax[1])

# Highlight 'Low-Flow' regions as red
lfm = LowFlowMap(test_dir+'test.lfm', fg = [1,0,0,1])
lfm.create()
lfm.plot(ax=ax[1], alpha=.5)

# Highlight 'High-Curvature' regions as blue
hcm = HighCurvatureMap(test_dir+'test.hcm', fg = [0,0,1,1])
hcm.create()
hcm.plot(ax=ax[1], alpha=.5)

# Highlight the quality as green - the brighter, the higher
qm = QualityMap(test_dir+'test.qm', fg = [0,1,0,1])
qm.create()
qm.plot(ax=ax[1], alpha=.3)

# plt.hold(False)

# Show processed fingerprint:
processed_image = bytearray(open(test_dir+'test.brw', 'rb').read())
dim = (len(dm.raw_data)*8, len(dm.raw_data[0])*8)
# print dim[0]*dim[1], len(processed_image)
processed_image = np.reshape(processed_image, dim)
ax[0].imshow(processed_image, cmap='gray')

# Show minutia:
# minutia_color = (0,1,0,.8)
minutia_list = np.loadtxt(test_dir+'test.xyt')
for minutia in minutia_list:
    quality = minutia[3] / 100.
    minutia_color = (0, 1, 0, quality )
    # minutia_color = (1 - quality, quality, 0, .8)
    crcl = ptch.Circle((minutia[0], minutia[1]), radius = 3, axes = ax[0], edgecolor='none', facecolor = minutia_color)
    ax[0].add_patch(crcl)

    #arc = ptch.Arc((minutia[0], minutia[1]), angle = minutia[2], width = 0, height = 5, edgecolor=minutia_color)
    #ax[0].add_patch(arc)

# Show the plot
plt.show()

fpview/fpview/__init__.py

"""Tools for viewing the fingerprint results extracted by NBIS.

TODO:
    - Documentation
    - Minutia plotting
"""

from .dm import DirectionMap
from .lfm import LowFlowMap
from .hcm import HighCurvatureMap
from .qm import QualityMap

from .map import Map
from .imgtools import ImgTools

fpview/fpview/map.py

import abc

import matplotlib.pyplot as plt

class Map(object):
    """
    This is an abstract class for all the map classes.

    The abstract methods are;
        create -- Creates data for plotting. Can use `create_from_f()` declared in the ImgTools
    """
    __metaclass__ = abc.ABCMeta

    def __new__(cls, *args, **kwargs):
        """Factory method for base/subtype creation. Simply creates an
        (new-style class) object instance and sets a base property."""
        instance = object.__new__(cls)
        instance.bg = None
        instance.fg = None
        instance.img_data = None
        instance.raw_data = None
        instance.__box_size = 8
        return instance


    def help(self):
        """Help - just shows the `__doc__`"""
        return self.__doc__

    def get_box_size(self):
        """Getter for the __box_size"""
        return self.__box_size

    def set_box_size(self, num=8):
        """Setter for the __box_size.

        Args:
            num:
                New value for the `__box_size` (default=8)

        NOTE: 
            Resets `img_data`
        """
        self.__box_size = num
        self.img_data = None

    def load(self, name):
        """Loads '...Map' of the fingerprint ridges.

        Args:
            name: 
                Name of the file, filehandler, or variable to be loaded
        """
        self.raw_data = []
        if type(name) is str:
            with open(name, 'r') as f:
                for line in f:
                    self.raw_data.append(map(int, line.split()))
        elif type(name) is file:
            name.seek(0,0)
            for line in name:
                self.raw_data.append(map(int, line.split()))
        else:
            # It's not a file - must be loaded already
            self.raw_data = name

        self.img_data = None

    @abc.abstractmethod
    def create(self):
        """Create a ...Map usable for plotting"""
        pass

    def plot(self, ax = None, cmap='gray', *args, **kwargs):
        """Plot and create/attach axes

        Args:
            ax:
                Axes of a figure to be attached to. If `None`, a new 
                `matplotlib.pyplot.subplots()` is created (default=None)

        Returns:
            Axes of the plotted image

        NOTE:
            Any additional arguments passed to this function will be directly
            passed to the `ax.imshow()`
        """
        if ax is None:
            fig, ax = plt.subplots()
        ax.imshow(self.img_data,  *args, **kwargs)

        return ax

fpview/fpview/imgtools.py

import numpy as np
import math

class ImgTools():
    """Different tools for image plotting

    TODO: Make a list of the tools here
    """
    def create_from_f(self, f):
        """Create the image data using a box function `f`

        The function pases the values in the `raw_data` to the `f`. The results
        are concatenated horizontally to produce rows of an image. Function `f`
        should return a `list` or `numpy.ndarray`

        Args:
            f:  
                Function for generating the image boxes.

        NOTE:
            `f` has to be able to accept 1 argument
        """
        assert len(self.raw_data) > 0
        assert len(self.raw_data[0]) > 0
        self.img_data = []
        for row in self.raw_data:
            img = [f(x) for x in row]
            # print len(img), len(img[0])
            img = np.hstack(img)
            for r in img:
                self.img_data.append(r)
        self.img_data = np.array(self.img_data)

    def create_block(self, val):
        """Create a solid monochrome box

        Create a box of size `__box_size x __box_size`.

        Args:
            val:
                Binary input. If `True` or non-0, creates a box made of `fg` 
                pixels, otherwise made of `bg` pixels

        Returns:
            Monochrome box
        """
        if val: return [[self.fg]*self.get_box_size() for _ in xrange(self.get_box_size())]
        else: return [[self.bg]*self.get_box_size() for _ in xrange(self.get_box_size())]
        # return self.__get_block(val)

    def create_block_gradient_alpha(self, val):
        """Create a box with variable alpha channel

        Create a box of size `__box_size x __box_size` with its transparancy
        scaled to `val`

        Args:
            val:
                Scaling factor ranging from 0 to `q_max`. Sets the value of the
                alpha channel of the current box. The scaling is done using
                `val / q_max`. The color is set to `fg`

        Returns:
            A single color box with transparancy set to `val/q_max`
        """
        color = self.fg[:]
        # self.q_max = float('inf')
        if type(color) is int or type(color) is float:
            color = [color]*4
        elif type(color) is list:
            while len(color) < 4:
                color += [0]    # color = color + [0]
        color[3] = val*1. / self.q_max
        return [[color]*self.get_box_size() for _ in xrange(self.get_box_size())]


    # TODO: Need to finish that
    # def superimpose_blob_line(self, rot = 0):
    #     res = self.create_line(rot)

    #     origin = [(self.get_box_size() - 1) / 2, (self.get_box_size() - 1) / 2]
    #     res[c[0]]



    def create_line(self, rot=0, base = None):
        """Create a line on a 8x8 grid box

        Args:
            rot:    Rotation of the line. 0 represents vertical line (range 0-15)

        Returns:
            `__box_size x __box_size` np.array() with a line

        NOTE:
            The rotation is in the increments of 11.25 degrees clockwise
        """
        if rot == -1:
            return [[self.bg]*self.get_box_size() for _ in xrange(self.get_box_size())]

        assert (0 <= rot <= 15)
        rot = rot*11.25

        # In case we want to draw something else:
        if base is None:
            # res = np.full((self.get_box_size(), self.get_box_size()), self.bg)
            res = [[self.bg]*self.get_box_size() for _ in xrange(self.get_box_size())]

        def bound_point(p):
            while p[0] < 0: p[0] += 1
            while p[0] >= self.get_box_size(): p[0] -= 1
            while p[1] < 0: p[1] += 1
            while p[1] >= self.get_box_size(): p[1] -= 1
            return p

        # conv = lambda x: map(int, np.floor(x))
        def conv(x):            # Readability
            return map(int, np.floor(x))

        ## The reason the indeces are extending to -2 and 10 is because the
        ## rotation is calculated as sin/cos, meaning, we need a long line to
        ## fill the diagonals
        if self.get_box_size() % 2 == 0:
            ## 4 different origins for rotation
            # First quadrant
            origin = [(self.get_box_size() - 1) / 2, (self.get_box_size() - 1) / 2]
            for idx in xrange(-2, origin[1] + 1):
                #for jdx in xrange(4):
                jdx = origin[1]
                point = self.rotate_point(origin, [idx, jdx], rot)
                point = conv(point)
                point = bound_point(point)
                res[point[0]][point[1]] = self.fg

            # Second quadrant
            origin[1] += 1
            for idx in xrange(-2, origin[1] + 1):
                jdx = origin[1]
                point = self.rotate_point(origin, [idx, jdx], rot)
                point = conv(point)
                point = bound_point(point)
                res[point[0]][point[1]] = self.fg

            # Third quadrant
            origin[0] += 1
            for idx in xrange(origin[1] + 1, self.get_box_size() + 2):
                jdx = origin[1]
                point = self.rotate_point(origin, [idx, jdx], rot)
                point = conv(point)
                point = bound_point(point)
                res[point[0]][point[1]] = self.fg

            # Fourth quadrant
            origin[1] -= 1
            for idx in xrange(origin[1] + 1, self.get_box_size() + 2):
                jdx = origin[1]
                point = self.rotate_point(origin, [idx, jdx], rot)
                point = conv(point)
                point = bound_point(point)
                res[point[0]][point[1]] = self.fg
        else:
            ## Single rotation origin
            origin = [self.get_box_size() / 2, self.get_box_size() / 2]
            for idx in xrange(self.get_box_size()):
                jdx = origin[1]
                point = self.rotate_point(origin, [idx, jdx], rot)
                point = conv(point)
                point = bound_point(point)
                res[point[0]][point[1]] = self.fg
        return res

    # This should be declared static, but I don't care
    def rotate_point(self, center, point, angle):
        """Rotate point around center by angle

        Args:
            center: 
                Center point
            point: 
                Point to be rotated
            angle: 
                Angle in radians to rotate by

        Returns:
            New coordinates for the point
        """
        angle = math.radians(angle)
        temp_point = point[0]-center[0] , point[1]-center[1]
        temp_point = ( -temp_point[0]*math.cos(angle)+temp_point[1]*math.sin(angle) , temp_point[0]*math.sin(angle)-temp_point[1]*math.cos(angle))
        temp_point = [temp_point[0]+center[0] , temp_point[1]+center[1]]
        return temp_point

fpview/fpview/dm.py

# TODO: DOCUMENTATION

import numpy as np

from .map import Map
from .imgtools import ImgTools

class DirectionMap(Map,ImgTools):
    """Manipulate the 'Direction Map'.

    The Direction Map represents the direction of ridge flow within the 
    fingerprint image. The map contains a grid of integer directions, where 
    each cell in the grid represents an 8x8 pixel neighborhood in the image. 
    Ridge flow angles are quantized into 16 integer bi-directional units equally
    spaced on a semicircle. Starting with vertical direction 0, direction units
    increase clockwise and represent incremental jumps of 11.25 degrees,
    stopping at direction 15 which is 11.25 degrees shy of vertical. Using this
    scheme, direction 8 is horizontal. A value of -1 in this map represents a
    neighborhood where no valid ridge flow was determined.

    Usage:
        DirectionMap(name, [bg], [fg], [box])

        name:   
            Name of the file to open, name of the file pointer, or name of the 
            variable with the loaded map
        bg:
            RGBA Background color to use (default=[0,0,0,0])
        fg: 
            RGBA Foreground color to use (default=[1,1,1,1])
        box:
            Box size (default=8)

    Methods:
        create():
            Creates the `img_data` variable from the `raw_data`

    Internal variables:
        bg, fg:
            Background and foreground

    Inherited variables and methods:
        raw_data:   
            Initially loaded raw_data
        img_data:   
            Converted data
        load():
            Loads the file/variable (Called in the __init__)
        plot(ax):   
            Plots the current img_data and attaches it to `ax`. Returns `ax`
        set_box_size(num):
            Setter for the single block size (default=8)
        get_box_size():
            Getter for the single block size
    """
    def __init__(self, name, bg = None, fg = None, box = 8):
        if bg is None:
            bg = [0,0,0,0]
        if fg is None:
            fg = [1,1,1,1]
        self.bg = bg
        self.fg = fg
        self.__box_size = box
        self.load(name)

    def create(self):
        """Create a Direction Map usable for plotting"""
        self.create_from_f(self.create_line)

fpview/fpview/hcm.py

import numpy as np

from .map import Map
from .imgtools import ImgTools

class HighCurvatureMap(Map,ImgTools):
    """
    Manipulate the 'High-Curvature Map'

    The High-Curvature Map represents areas in the image having high-curvature
    ridge flow. This is especially true of core and delta regions in the
    fingerprint image, but high-curvature is not limited to just these cases. 
    This is a bi-level map with same dimension as the Direction Map. Cell values
    of 1 represent 8x8 pixel neighborhoods in the fingerprint image that are
    located within a high-curvature region, otherwise cell values are set to 0.

    Usage:
        HighCurvatureMap(name, [bg], [fg], [box])

        name:   
            Name of the file to open, name of the file pointer, or name of the 
            variable with the loaded map
        bg:
            RGBA Background color to use (default=[0,0,0,0])
        fg: 
            RGBA Foreground color to use (default=[1,1,1,1])
        box:
            Box size (default=8)

    Methods:
        create():
            Creates the `img_data` variable from the `raw_data`

    Internal variables:
        bg, fg:
            Background and foreground

    Inherited variables and methods:
        raw_data:   
            Initially loaded raw_data
        img_data:   
            Converted data
        load():
            Loads the file/variable (Called in the __init__)
        plot(ax):   
            Plots the current img_data and attaches it to `ax`. Returns `ax`
        set_box_size(num):
            Setter for the single block size (default=8)
        get_box_size():
            Getter for the single block size
    """

    def __init__(self, name, bg = None, fg = None, box = 8):
        if bg is None:
            bg = [0,0,0,0]
        if fg is None:
            fg = [1,1,1,1]
        self.bg = bg
        self.fg = fg
        self.__box_size = box
        self.load(name)

    def create(self):
        """Create a High-Curvature Map usable for plotting"""
        self.create_from_f(self.create_block)

fpview/fpview/lfm.py

import numpy as np

from .map import Map
from .imgtools import ImgTools

class LowFlowMap(Map,ImgTools):
    """Manipulate the 'Low Flow Map' file

    The Low-Flow Map represents areas in the image having non-determinable 
        ridge flow. Ridge flow is determined using a set of discrete cosine wave 
        forms computed for a predetermined range of frequencies. These wave 
        forms are applied at 16 incremental orientations. At times none of the 
        wave forms at none of the orientations resonate sufficiently high within
        the region in the image to satisfactorily determine a dominant 
        directional frequency. This is a bi-level map with same dimension as the
        Direction Map. Cell values of 1 represent 8x8 pixel neighborhoods in the
        fingerprint image that are located within a region where a dominant 
        directional frequency could not be determined, otherwise cell values are
        set to 0. The Direction Map also records cells with nondeterminable 
        ridge flow. The difference is that the Low-Flow Map records all cells 
        with nondeterminable ridge flow, while the Direction Map records only 
        those that remain non-determinable after extensive interpolation and 
        smoothing of neighboring ridge flow directions.

    Usage:
        LowFlowMap(name, [bg], [fg], [box])

        name:   
            Name of the file to open, name of the file pointer, or name of the 
            variable with the loaded map
        bg:
            RGBA Background color to use (default=[0,0,0,0])
        fg: 
            RGBA Foreground color to use (default=[1,1,1,1])
        box:
            Box size (default=8)

    Methods:
        create():
            Creates the `img_data` variable from the `raw_data`

    Internal variables:
        bg, fg:
            Background and foreground

    Inherited variables and methods:
        raw_data:   
            Initially loaded raw_data
        img_data:   
            Converted data
        load():
            Loads the file/variable (Called in the __init__)
        plot(ax):   
            Plots the current img_data and attaches it to `ax`. Returns `ax`
        set_box_size(num):
            Setter for the single block size (default=8)
        get_box_size():
            Getter for the single block size
    """

    def __init__(self, name, bg = None, fg = None, box = 8):
        if bg is None:
            bg = [0,0,0,0]
        if fg is None:
            fg = [1,1,1,1]
        self.bg = bg
        self.fg = fg
        self.__box_size = box
        self.load(name)

    def create(self):
        """Create a Low-Flow Map usable for plotting"""
        self.create_from_f(self.create_block)

fpview/fpview/qm.py

import numpy as np

from .map import Map
from .imgtools import ImgTools

class QualityMap(Map,ImgTools):
    """
    Manipulate the 'Quality Map'

    The Quality Map represents regions in the image having varying levels of
    quality. The maps above are combined heuristically to form 5 discrete levels
    of quality. This map has the same dimension as the Direction Map, with each
    value in the map representing an 8x8 pixel neighborhood in the fingerprint
    image. A cell value of 4 represents highest quality, while a cell value of 0
    represent lowest possible quality.

    Usage:
        QualityMap(name, [bg], [fg], [box], [qmax])

        name:   
            Name of the file to open, name of the file pointer, or name of the 
            variable with the loaded map
        bg:
            RGBA Background color to use (default=[0,0,0,0])
        fg: 
            RGBA Foreground color to use (default=[1,1,1,1])
        box:
            Box size (default=8)
        qmax:
            Maximum quality (default=4)

    Methods:
        create():
            Creates the `img_data` variable from the `raw_data`

    Internal variables:
        bg, fg:
            Background and foreground
        q_max:
            Maximum quality

    Inherited variables and methods:
        raw_data:   
            Initially loaded raw_data
        img_data:   
            Converted data
        load():
            Loads the file/variable (Called in the __init__)
        plot(ax):   
            Plots the current img_data and attaches it to `ax`. Returns `ax`
        set_box_size(num):
            Setter for the single block size (default=8)
        get_box_size():
            Getter for the single block size
    """

    def __init__(self, name, bg = None, fg = None, box = 8, qmax = 4.):
        if bg is None:
            bg = [0,0,0,0]
        if fg is None:
            fg = [1,1,1,1]
        self.bg = bg
        self.fg = fg
        self.q_max = float(qmax)
        self.__box_size = box
        self.load(name)


    def create(self):
        """Create a High-Curvature Map usable for plotting"""
        self.create_from_f(self.create_block_gradient_alpha)
\$\endgroup\$
  • \$\begingroup\$ Noone? Is it perfect? LOL \$\endgroup\$ – RafazZ Oct 25 '16 at 16:59
  • \$\begingroup\$ It is a lot of code. Quite daunting to even get started... \$\endgroup\$ – Graipher Oct 26 '16 at 9:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.