Commit d4b928d1 authored by Alessandro dos Santos Ferreira's avatar Alessandro dos Santos Ferreira
Browse files

Pynovisao - Documentando o codigo com docstrings

parent f2370ad8
......@@ -15,14 +15,27 @@ from util.utils import ImageUtils
from extractor import Extractor
class ColorStats(Extractor):
"""Implements color feature extraction."""
def __init__(self):
"""Constructor.
"""
pass
def run(self, image):
"""Calculate min, max, mean and standard deviation for color channels RGB, HSV and CIELab in input image.
Parameters
----------
image : opencv image
Image to be analyzed.
Returns
-------
features : tuple
Returns a tuple containing a list of labels, type and values for each feature extracted.
"""
image_hsv = ImageUtils.image_to_hsv(image, bgr = True)
image_cielab = ImageUtils.image_to_cielab(image, bgr = True)
......
......@@ -11,6 +11,7 @@
from abc import ABCMeta, abstractmethod
class Extractor(object):
"""Abstract class for feature extraction algorithms."""
__metaclass__ = ABCMeta
......@@ -18,9 +19,19 @@ class Extractor(object):
NOMINAL = 'nominal'
def get_name(self):
"""Return the name of class.
Returns
-------
name : string
Returns the name of instantiated class.
"""
return self.__class__.__name__
@abstractmethod
def run(self):
"""Perform the feature extraction.
Implement this method to extend this class with a new feature extraction algorithm.
"""
pass
......@@ -21,12 +21,46 @@ from util.utils import TimeUtils
from extractor import Extractor
class FeatureExtractor(object):
"""Handle the feature extraction."""
def __init__(self, extractors):
"""Constructor.
Parameters
----------
extractor : list of Extractor
Initial set of active extractors.
"""
self.extractors = extractors
def extract_all(self, dataset, output_file = None, dirs = None, overwrite = True):
"""Runs the feature extraction algorithms on all images of dataset.
Parameters
----------
dataset : string
Path to dataset.
output_file : string, optional, default = None
Name of output file continaing the features. If not informed is considered the name of dataset.
dirs : list of string, optional, default = None
List of directories to be serched. If not informed search in all directories with images inside dataset.
overwrite : boolean, optional, default = True
If False check if already exists a file containing the features.
Returns
-------
out : tuple
Returns a tuple containing the name of output file and time spent in milliseconds.
Raises
------
IException 'Please select at least one extractor'
Empty list of extractors.
IException 'Image %s is possibly corrupt'
Error opening some image inside dataset.
IException 'There are no images in dataset: %s'
Dataset does not contain any image.
"""
if len(self.extractors) == 0:
raise IException("Please select at least one extractor")
......@@ -34,6 +68,7 @@ class FeatureExtractor(object):
output_file = File.get_filename(dataset)
output_file = File.make_path(dataset, output_file + '.arff')
# if already exists a output file and must not override, return current file
if overwrite == False and os.path.isfile(output_file):
return output_file, 0
......@@ -44,6 +79,7 @@ class FeatureExtractor(object):
data = []
# Runs the feature extraction for all classes inside the dataset
for cl in dirs:
items = sorted(os.listdir( File.make_path(dataset, cl)))
print("Processing class %s - %d itens" % (cl, len(items)))
......@@ -69,6 +105,7 @@ class FeatureExtractor(object):
if len(data) == 0:
raise IException("There are no images in dataset: %s" % dataset)
# Save the output file in ARFF format
self._save_output(File.get_filename(dataset), classes, labels, types, data, output_file)
end_time = TimeUtils.get_time()
......@@ -77,6 +114,29 @@ class FeatureExtractor(object):
def extract_one_file(self, dataset, image_path, output_file = None):
"""Runs the feature extraction algorithms on specific image.
Parameters
----------
dataset : string
Path to dataset.
image_path : string
Path to image.
output_file : string, optional, default = None
Name of output file continaing the features. If not informed is considered the name of dataset.
Returns
-------
out : tuple
Returns a tuple containing the name of output file and time spent in milliseconds.
Raises
------
IException 'Please select at least one extractor'
Empty list of extractors.
IException 'Image %s is possibly corrupt'
Error opening image.
"""
if len(self.extractors) == 0:
raise IException("Please select at least one extractor")
......@@ -104,7 +164,23 @@ class FeatureExtractor(object):
def _save_output(self, relation, classes, labels, types, data, output_file):
"""Save output file in ARFF format.
Parameters
----------
relation : string
Name of relation.
classes : list of string
List of classes names.
labels : list of string
List of attributes names.
types : list of string
List of attributes types.
data : list of list of string
List of instances.
output_file : string
Path to output file.
"""
arff = open(output_file,'wb')
arff.write("%s %s\n\n" % ('@relation', relation))
......
......@@ -17,6 +17,7 @@ from util.utils import ImageUtils
from extractor import Extractor
class GLCM(Extractor):
"""Implements GLCM (Gray-Level Co-Occurrence Matrix) feature extraction."""
def __init__(self, glcm_levels = 256):
......@@ -24,7 +25,19 @@ class GLCM(Extractor):
def run(self, image):
"""Extract the following texture properties defined in the GLCM: energy, contrast, correlation, homogeneity and dissimilarity,
with distance 1 and 2 and angles 0, 45 and 90 degrees.
Parameters
----------
image : opencv image
Image to be analyzed.
Returns
-------
features : tuple
Returns a tuple containing a list of labels, type and values for each feature extracted.
"""
image_grayscale = ImageUtils.image_grayscale(image, bgr = True)
g = feature.greycomatrix(image_grayscale, [1, 2], [0, np.pi / 4, np.pi / 2], self.glcm_levels, normed=True, symmetric=True)
......
......@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
"""
Extract HOG (Histogram of Oriented Gradient) feature.
Extract HOG (Histogram of Oriented Gradient) features.
Dalal, N and Triggs, B, Histograms of Oriented Gradients for Human Detection, IEEE Computer Society Conference on Computer Vision and Pattern Recognition 2005 San Diego, CA, USA
......@@ -16,6 +16,7 @@ from util.utils import ImageUtils
from extractor import Extractor
class HOG(Extractor):
"""Implements HOG (Histogram of Oriented Gradient) feature extraction."""
def __init__(self):
......@@ -23,7 +24,18 @@ class HOG(Extractor):
def run(self, image):
"""Extract HOG (Histogram of Oriented Gradient) features.
Parameters
----------
image : opencv image
Image to be analyzed.
Returns
-------
features : tuple
Returns a tuple containing a list of labels, type and values for each feature extracted.
"""
image_grayscale = ImageUtils.image_grayscale(image, bgr = True)
image_128x128 = ImageUtils.image_resize(image_grayscale, 128, 128)
......
......@@ -16,14 +16,27 @@ from util.utils import ImageUtils
from extractor import Extractor
class RawCentralMoments(Extractor):
"""Calculate raw and central set of image moments."""
def __init__(self):
"""Constructor.
"""
self._moments_order = [(0, 1), (1, 0), (1, 1), (0, 2), (2, 0)]
def run(self, image):
"""Calculate raw and central set of image moments of order 1 and 2.
Parameters
----------
image : opencv image
Image to be analyzed.
Returns
-------
features : tuple
Returns a tuple containing a list of labels, type and values for each feature extracted.
"""
image_binary = ImageUtils.image_binary(image, bgr = True)
m = measure.moments(image_binary)
......@@ -47,14 +60,25 @@ class RawCentralMoments(Extractor):
class HuMoments(Extractor):
"""Calculate Hu's set of image moments."""
def __init__(self):
pass
def run(self, image):
"""Calculate Hu's set set of image moments.
Parameters
----------
image : opencv image
Image to be analyzed.
Returns
-------
features : tuple
Returns a tuple containing a list of labels, type and values for each feature extracted.
"""
image_binary = ImageUtils.image_binary(image, bgr = True)
m = measure.moments(image_binary)
......
......@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
"""
Extract LBP (Local Binary Patterns) feature.
Extract LBP (Local Binary Patterns) features.
Multiresolution Gray-Scale and Rotation Invariant Texture Classification with Local Binary Patterns. Timo Ojala, Matti Pietikainen, Topi Maenpaa. 2002.
......@@ -17,15 +17,35 @@ from util.utils import ImageUtils
from extractor import Extractor
class LBP(Extractor):
"""Implements LBP (Local Binary Patterns) feature extraction."""
def __init__(self, lbp_radius = 2, n_bins = 18):
"""Constructor.
Parameters
----------
lbp_radius : float, optional, default = 2
Radius of circle (spatial resolution of the operator).
n_bins : integer, optional, default = 18
Number of circularly symmetric neighbour set points (quantization of the angular space).
"""
self.lbp_radius = lbp_radius
self.n_bins = n_bins
def run(self, image):
"""Extract LBP (Local Binary Patterns) features.
Parameters
----------
image : opencv image
Image to be analyzed.
Returns
-------
features : tuple
Returns a tuple containing a list of labels, type and values for each feature extracted.
"""
image_grayscale = ImageUtils.image_grayscale(image, bgr = True)
lbp = feature.local_binary_pattern(image_grayscale, 8 * self.lbp_radius, self.lbp_radius, 'uniform')
......
......@@ -4,6 +4,16 @@
"""
Read the args, creates GUI and add all actions of pynovisao.
This file is the main code of Pynovisao.
To add a new action ( functionality ) to this software, you must implement the method body of action in file pynovisao.py
and bind the method to menu, using the call tk.add_command.
To add a new Feature Extractor, Segmenter or Classifier, place it in correct directory and extend its abstract class.
As PEP 20 says, "Readability counts". So, follow the current conventions adopted in this project.
For more information access PEP 8 -- Style Guide for Python Code ( https://www.python.org/dev/peps/pep-0008/ ).
Use it as your guideline. Only use CamelCase in class names. DON'T USE CamelCase ou mixedCase in variable or functions.
Name: main.py
Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
"""
......@@ -15,6 +25,7 @@ from pynovisao import Act
def get_args():
"""Read the arguments of program."""
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required=False, help="Dataset path", default="../data/demo", type=str)
......@@ -26,9 +37,10 @@ def get_args():
if __name__ == "__main__":
# Interface of software
tk = interface.TkInterface("Pynovisao")
# Bind all Pynovisao actions to menu
act = Act(tk, get_args())
tk.add_menu("File")
......@@ -71,6 +83,7 @@ if __name__ == "__main__":
tk.add_separator()
tk.add_command("Execute", act.run_classifier, 'C')
# Render the GUI
tk.render_menu()
tk.add_panel_classes( act.classes )
......
......@@ -24,8 +24,18 @@ from util.file_utils import File as f
from util.utils import TimeUtils
class Act(object):
"""Store all actions of Pynovisao."""
def __init__(self, tk, args):
"""Constructor.
Parameters
----------
tk : Interface
Pointer to interface that handles UI.
args : Dictionary
Arguments of program.
"""
self.tk = tk
self.segmenter = [segmentation._segmenter_list[segmenter].meta for segmenter in segmentation._segmenter_list
......@@ -51,6 +61,13 @@ class Act(object):
def _init_dataset(self, directory):
"""Initialize the directory of image dataset.
Parameters
----------
directory : string
Path to directory.
"""
if(directory[-1] == '/'):
directory = directory[:-1]
......@@ -58,6 +75,16 @@ class Act(object):
f.create_dir(self.dataset)
def _init_classes(self, classes = None, colors = None):
"""Initialize the classes of dataset.
Parameters
----------
classes : list of string, optional, default = None
List of classes. If not informed, the metod set as classes all classes in dataset.
If there's no classes in dataset, adds two default classes.
colors : list of string, optional, default = None
List de colors representing the color of classe, in same order. If not informed, chooses a color at random.
"""
self.classes = []
classes = sorted(f.list_dirs(self.dataset)) if classes is None else classes.split()
......@@ -74,8 +101,16 @@ class Act(object):
def open_image(self, imagename = None):
"""Open a new image.
Parameters
----------
imagename : string, optional, default = None
Filepath of image. If not informed open a dialog to choose.
"""
def onclick(event):
"""Binds dataset generator event to click on image."""
if event.xdata != None and event.ydata != None and int(event.ydata) != 0 and self._dataset_generator == True:
x = int(event.xdata)
y = int(event.ydata)
......@@ -109,6 +144,8 @@ class Act(object):
def restore_image(self):
"""Refresh the image and clean the segmentation.
"""
if self._const_image is not None:
self.tk.write_log("Restoring image...")
self.tk.refresh_image(self._const_image)
......@@ -116,7 +153,13 @@ class Act(object):
self.segmenter.reset()
def close_image(self):
"""Close the image.
Raises
------
IException 'Image not found'
If there's no image opened.
"""
if self._const_image is None:
raise IException("Image not found")
......@@ -126,17 +169,37 @@ class Act(object):
self._image = None
def add_class(self, dialog = True, name = None, color = None):
"""Add a new class.
Parameters
----------
dialog : boolean, optional, default = True
If true open a config dialog to add the class.
name : string, optional, default = None
Name of class. If not informed set the name 'Class_nn' to class.
color : string, optional, default = None
Name of color in X11Color format, representing the class. It will used to paint the segments of class.
If not informed choose a color at random.
Raises
------
IException 'You have reached the limite of %d classes'
If you already have created self.tk.MAX_CLASSES classes.
"""
n_classes = len(self.classes)
if n_classes >= self.tk.MAX_CLASSES:
raise IException("You have reached the limite of %d classes" % self.tk.MAX_CLASSES)
def edit_class(index):
"""Calls method that edit the class."""
self.edit_class(index)
def update_current_class(index):
"""Calls method that update the class."""
self.update_current_class(index)
def process_config():
"""Add the class and refresh the panel of classes."""
new_class = self.tk.get_config_and_destroy()
new_class["name"].value = '_'.join(new_class["name"].value.split())
......@@ -165,7 +228,15 @@ class Act(object):
def edit_class(self, index):
"""Edit a class.
Parameters
----------
index : integer.
Index of class in list self.classes.
"""
def process_update(index):
"""Update the class."""
updated_class = self.tk.get_config_and_destroy()
updated_class["name"].value = '_'.join(updated_class["name"].value.split())
......@@ -179,9 +250,28 @@ class Act(object):
self.tk.dialogue_config(title, current_config, lambda *_ : process_update(index))
def update_current_class(self, index):
"""Update the current class.
"""
self._current_class = index
def get_class_by_name(self, name):
"""Return the index for class.
Parameters
----------
name : string.
Name of class.
Returns
-------
index : integer.
Index of class in list self.classes.
Raises
------
Exception 'Class not found'
If name not found in self.classes.
"""
name = name.strip()
for cl in self.classes:
......@@ -191,6 +281,8 @@ class Act(object):
def set_dataset_path(self):
"""Open a dialog to choose the path to directory of image dataset.
"""
directory = self.tk.utils.ask_directory(default_dir = self.dataset)
if directory:
self._init_dataset(directory)
......@@ -202,17 +294,21 @@ class Act(object):
if self.classifier: self.classifier.reset()
def toggle_dataset_generator(self):
"""Enable/disable the dataset generator on click in image.
"""
self._dataset_generator = not self._dataset_generator
def select_segmenter(self):
"""Open a dialog to choose the segmenter.
"""
title = "Choosing a segmenter"
self.tk.write_log(title)
current_config = segmentation.get_segmenter_config()
def process_config():
"""Update the current segmenter."""
new_config = self.tk.get_config_and_destroy()
self.segmenter = [new_config[segmenter].meta for segmenter in new_config
......@@ -224,13 +320,15 @@ class Act(object):
self.tk.dialogue_choose_one(title, current_config, process_config)
def config_segmenter(self):
"""Open a dialog to configure the current segmenter.
"""
title = "Configuring %s" % self.segmenter.get_name()
self.tk.write_log(title)
current_config = self.segmenter.get_config()
def process_config():
"""Update the configs of current segmenter."""
new_config = self.tk.get_config_and_destroy()
self.segmenter.set_config(new_config)
......@@ -240,7 +338,13 @@ class Act(object):
self.tk.dialogue_config(title, current_config, process_config)
def run_segmenter(self):
"""Do the segmentation of image, using the current segmenter.
Raises
------
IException 'Image not found'
If there's no image opened.
"""
if self._const_image is None:
raise IException("Image not found")
......@@ -254,13 +358,20 @@ class Act(object):
def select_extractors(self):
"""Open a dialog to select the collection of extractors.
Raises
------
IException 'Please select at least one extractor'
If no extractor was selected.
"""
title = "Selecting extractors"
self.tk.write_log(title)
current_config = extraction.get_extractor_config()
def process_config():
"""Update the collection of extractors."""