Skip to content
Snippets Groups Projects
Commit 870a416b authored by Amir MOHAMMADI's avatar Amir MOHAMMADI
Browse files

Add annotator support

parent ccb59c92
No related branches found
No related tags found
1 merge request!131Add annotator support
......@@ -5,6 +5,7 @@ from . import extractor
from . import algorithm
from . import tools
from . import grid # only one file, not complete directory
from . import annotator
from . import script
from . import test
......
from bob.bio.base import read_original_data as base_read
import numpy # for documentation
class Base(object):
"""Base class for all annotators. This class is meant to be used in
conjunction with the bob bio annotate script.
Attributes
----------
read_original_data : callable
A function that loads the samples. The syntax is like
:any:`bob.bio.base.read_original_data`.
"""
def __init__(self, read_original_data=None, **kwargs):
super(Base, self).__init__(**kwargs)
self.read_original_data = read_original_data or base_read
def annotate(self, sample, **kwargs):
"""Annotates a sample and returns annotations in a dictionary.
Parameters
----------
sample : numpy.ndarray
The sample that is being annotated.
**kwargs
The extra arguments that may be passed.
Returns
-------
dict
A dictionary containing the annotations of the biometric sample. If
the program fails to annotate the sample, it should return an empty
dictionary.
"""
raise NotImplementedError
# Alisa call to annotate
def __call__(self, sample, **kwargs):
return self.annotate(sample, **kwargs)
from .Base import Base
class Callable(Base):
"""A class that wraps a callable object that annotates a sample into a
bob.bio.annotator object."""
def __init__(self, callable, **kwargs):
super(Callable, self).__init__(**kwargs)
self.callable = callable
def annotate(self, sample, **kwargs):
return self.callable(sample, **kwargs)
import logging
from . import Base
logger = logging.getLogger(__name__)
class FailSafe(Base):
"""A fail-safe annotator.
This annotator takes a list of annotator and tries them until you get your
annotations.
The annotations of previous annotator is passed to the next one.
Attributes
----------
annotators : list
A list of annotators to try
required_keys : list
A list of keys that should be available in annotations to stop trying
different annotators.
"""
def __init__(self, annotators, required_keys, **kwargs):
super(FailSafe, self).__init__(**kwargs)
self.annotators = list(annotators)
self.required_keys = list(required_keys)
def annotate(self, sample, **kwargs):
if 'annotations' not in kwargs or kwargs['annotations'] is None:
kwargs['annotations'] = {}
for annotator in self.annotators:
try:
annotations = annotator(sample, **kwargs)
except Exception:
logger.debug(
"The annotator `%s' failed to annotate!", annotator,
exc_info=True)
annotations = {}
if not annotations:
logger.debug(
"Annotator `%s' returned empty annotations.", annotator)
kwargs['annotations'].update(annotations)
# check if we have all the required annotations
if all(key in kwargs['annotations'] for key in self.required_keys):
break
return kwargs['annotations']
from .Base import Base
from .FailSafe import FailSafe
from .Callable import Callable
# gets sphinx autodoc done right - don't remove it
def __appropriate__(*args):
"""Says object was actually declared here, and not in the import module.
Fixing sphinx warnings of not being able to find classes, when path is
shortened.
Parameters
----------
*args
An iterable of objects to modify
Resolves `Sphinx referencing issues
<https://github.com/sphinx-doc/sphinx/issues/3048>`
"""
for obj in args:
obj.__module__ = __name__
__appropriate__(
Base,
FailSafe,
Callable,
)
__all__ = [_ for _ in dir() if not _.startswith('_')]
"""A script to help annotate databases.
"""
import logging
import json
import click
from os.path import dirname, isfile
from bob.extension.scripts.click_helper import (
verbosity_option, Command, Option)
from bob.io.base import create_directories_safe
from bob.bio.base.tools.grid import indices
logger = logging.getLogger(__name__)
@click.command(entry_point_group='bob.bio.config', cls=Command)
@click.option('--database', '-d', required=True, cls=Option,
entry_point_group='bob.bio.database')
@click.option('--annotator', '-a', required=True, cls=Option,
entry_point_group='bob.bio.annotator')
@click.option('--output-dir', '-o', required=True, cls=Option)
@click.option('--force', '-f', is_flag=True, cls=Option)
@click.option('--array', type=click.INT, default=1,)
@verbosity_option(cls=Option)
def annotate(database, annotator, output_dir, force, array, **kwargs):
"""Annotates a database.
The annotations are written in text file (json) format which can be read
back using :any:`bob.db.base.read_annotation_file` (annotation_type='json')
\b
Parameters
----------
database : :any:`bob.bio.database`
The database that you want to annotate. Can be a ``bob.bio.database``
entry point or a path to a Python file which contains a variable
named `database`.
annotator : callable
A function that takes the database and a sample (biofile) of the
database and returns the annotations in a dictionary. Can be a
``bob.bio.annotator`` entry point or a path to a Python file which
contains a variable named `annotator`.
output_dir : str
The directory to save the annotations.
force : bool, optional
Wether to overwrite existing annotations.
array : int, optional
Use this option alongside gridtk to submit this script as an array job.
verbose : int, optional
Increases verbosity (see help for --verbose).
\b
[CONFIG]... Configuration files. It is possible to pass one or
several Python files (or names of ``bob.bio.config``
entry points) which contain the parameters listed
above as Python variables. The options through the
command-line (see below) will override the values of
configuration files.
"""
logger.debug('database: %s', database)
logger.debug('annotator: %s', annotator)
logger.debug('force: %s', force)
logger.debug('output_dir: %s', output_dir)
logger.debug('array: %s', array)
logger.debug('kwargs: %s', kwargs)
biofiles = database.objects(groups=None, protocol=database.protocol)
biofiles = sorted(biofiles)
if array > 1:
start, end = indices(biofiles, array)
biofiles = biofiles[start:end]
total = len(biofiles)
logger.info("Saving annotations in %s", output_dir)
logger.info("Annotating %d samples ...", total)
for i, biofile in enumerate(biofiles):
outpath = biofile.make_path(output_dir, '.json')
if isfile(outpath):
if force:
logger.debug("Overwriting the annotations file `%s'", outpath)
else:
logger.debug("The annotation `%s' already exists", outpath)
continue
logger.info(
"Extracting annotations for sample %d out of %d", i + 1, total)
data = annotator.read_original_data(
biofile, database.original_directory, database.original_extension)
annot = annotator(data)
create_directories_safe(dirname(outpath))
with open(outpath, 'w') as f:
json.dump(annot, f, indent=1, allow_nan=False)
from bob.bio.base.annotator import FailSafe, Callable
def simple_annotator(image, **kwargs):
return {
'topleft': (0, 0),
'bottomright': image.shape,
}
def fail_annotator(image, **kwargs):
return {}
annotator = FailSafe(
[Callable(fail_annotator),
Callable(simple_annotator)],
required_keys=['topleft', 'bottomright'],
)
import tempfile
import os
import shutil
from click.testing import CliRunner
from bob.bio.base.script.annotate import annotate
from bob.db.base import read_annotation_file
def test_annotate():
try:
tmp_dir = tempfile.mkdtemp(prefix="bobtest_")
runner = CliRunner()
result = runner.invoke(annotate, args=(
'-d', 'dummy', '-a', 'dummy', '-o', tmp_dir))
assert result.exit_code == 0, result.output
# test if annotations exist
for dirpath, dirnames, filenames in os.walk(tmp_dir):
for filename in filenames:
path = os.path.join(dirpath, filename)
annot = read_annotation_file(path, 'json')
assert annot['topleft'] == [0, 0]
# size of atnt images
assert annot['bottomright'] == [112, 92]
finally:
shutil.rmtree(tmp_dir)
.. _bob.bio.base.annotations:
Annotating biometric databases
==============================
It is often required to annotate the biometric samples before running
experiments. This often happens in face biometrics where each face is detected
and location of landmarks on the face is saved prior to running experiments.
To facilitate the process of annotating a new database, this package provides
a command-line script:
.. code-block:: sh
$ bob bio annotate --help
This script accepts two main parameters a database object that inherits from
:any:`bob.bio.base.database.BioDatabase` and an annotator object that inherits
from :any:`bob.bio.base.annotator.Base`. Please see the help message of the
script for more information.
The script can also be run in parallel using :ref:`gridtk`:
.. code-block:: sh
$ jman submit --array 64 -- bob bio annotate /path/to/config.py --array 64
The number that is given to the ``--array`` options should match.
python
numpy
bob.bio.face
bob.bio.gmm
bob.bio.video
......
.. _bob.bio.base.implemented:
=================================
Tools implemented in bob.bio.base
=================================
......@@ -15,6 +14,7 @@ Base Classes
bob.bio.base.extractor.Extractor
bob.bio.base.algorithm.Algorithm
bob.bio.base.grid.Grid
bob.bio.base.annotator.Base
Implementations
......@@ -38,7 +38,8 @@ Implementations
bob.bio.base.database.BioDatabase
bob.bio.base.database.ZTBioDatabase
bob.bio.base.database.FileListBioDatabase
bob.bio.base.annotator.FailSafe
bob.bio.base.annotator.Callable
Preprocessors
-------------
......@@ -72,5 +73,10 @@ Grid Configuration
.. adapted from http://stackoverflow.com/a/29789910/3301902 to ge a nice dictionary content view
Annotators
----------
.. automodule:: bob.bio.base.annotator
.. include:: links.rst
......@@ -4,9 +4,9 @@
.. _bob.bio.base:
===========================================
*******************************************
Running Biometric Recognition Experiments
===========================================
*******************************************
The ``bob.bio`` packages provide open source tools to run comparable and reproducible biometric recognition experiments.
To design a biometric recognition experiment, you must choose:
......@@ -64,7 +64,6 @@ If you run biometric recognition experiments using the bob.bio framework, please
}
===========
Users Guide
===========
......@@ -77,8 +76,9 @@ Users Guide
implementation
filelist-guide
more
annotations
================
Reference Manual
================
......@@ -89,7 +89,7 @@ Reference Manual
py_api
==========
References
==========
......@@ -101,7 +101,6 @@ References
.. [GW09] *M. Günther and R.P. Würtz*. **Face detection and recognition using maximum likelihood classifiers on Gabor graphs**. International Journal of Pattern Recognition and Artificial Intelligence, 23(3):433-461, 2009.
=========
ToDo-List
=========
......@@ -111,7 +110,7 @@ Here is a list of things that needs to be done:
.. todolist::
==================
Indices and tables
==================
......
===========================
Python API for bob.bio.base
===========================
......
......@@ -9,5 +9,8 @@ bob.learn.linear
bob.math
bob.measure
bob.sp
click
click-plugins
numpy
scipy
setuptools
......@@ -123,14 +123,25 @@ setup(
'demanding = bob.bio.base.config.grid.demanding:grid',
'gpu = bob.bio.base.config.grid.gpu:grid',
],
# declare database to bob
'bob.db': [
'bio_filelist = bob.bio.base.database.filelist.driver:Interface',
],
# main entry for bob cli
# main entry for bob bio cli
'bob.cli': [
'bio = bob.bio.base.script.bio:bio',
],
# bob bio scripts
'bob.bio.cli': [
'annotate = bob.bio.base.script.annotate:annotate',
],
# annotators
'bob.bio.annotator': [
'dummy = bob.bio.base.test.dummy.annotator:annotator',
],
},
# Classifiers are important if you plan to distribute this package through
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment