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

[replaymobile][replay][msu_mfsd] improve interfaces to make files independent

parent 006b0c9e
Branches
Tags
1 merge request!104Improve high level database interfaces
#!/usr/bin/env python2 #!/usr/bin/env python2
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#==============================================================================
# Used in ReplayMobilePadFile class
from bob.bio.video import FrameSelector, FrameContainer from bob.bio.video import FrameSelector, FrameContainer
from bob.pad.face.database import VideoPadFile # Used in MsuMfsdPadFile class from bob.pad.face.database import VideoPadFile # Used in MsuMfsdPadFile class
from bob.pad.base.database import PadDatabase from bob.pad.base.database import PadDatabase
from bob.extension import rc
import os import os
import numpy as np import numpy as np
#==============================================================================
class MsuMfsdPadFile(VideoPadFile): class MsuMfsdPadFile(VideoPadFile):
""" """
A high level implementation of the File class for the MSU MFSD database. A high level implementation of the File class for the MSU MFSD database.
...@@ -40,18 +34,20 @@ class MsuMfsdPadFile(VideoPadFile): ...@@ -40,18 +34,20 @@ class MsuMfsdPadFile(VideoPadFile):
if f.is_real(): if f.is_real():
attack_type = None attack_type = None
else: else:
attack_type = 'attack' attack_type = "attack"
# attack_type is a string and I decided to make it like this for this # attack_type is a string and I decided to make it like this for this
# particular database. You can do whatever you want for your own database. # particular database. You can do whatever you want for your own database.
super(MsuMfsdPadFile, self).__init__( super(MsuMfsdPadFile, self).__init__(
client_id=f.client_id, client_id=f.client_id, path=f.path, attack_type=attack_type, file_id=f.id
path=f.path, )
attack_type=attack_type,
file_id=f.id) def load(
self,
#========================================================================== directory=None,
def load(self, directory=None, extension=None, frame_selector=FrameSelector(selection_style='all')): extension=None,
frame_selector=FrameSelector(selection_style="all"),
):
""" """
Overridden version of the load method defined in the ``VideoPadFile``. Overridden version of the load method defined in the ``VideoPadFile``.
...@@ -76,26 +72,27 @@ class MsuMfsdPadFile(VideoPadFile): ...@@ -76,26 +72,27 @@ class MsuMfsdPadFile(VideoPadFile):
for further details. for further details.
""" """
_, extension = os.path.splitext( _, extension = os.path.splitext(self.f.videofile()) # get file extension
self.f.videofile()) # get file extension
video_data_array = self.f.load( video_data_array = self.f.load(directory=directory, extension=extension)
directory=directory, extension=extension)
return frame_selector(video_data_array) return frame_selector(video_data_array)
#==============================================================================
class MsuMfsdPadDatabase(PadDatabase): class MsuMfsdPadDatabase(PadDatabase):
""" """
A high level implementation of the Database class for the MSU MFSD database. A high level implementation of the Database class for the MSU MFSD database.
""" """
def __init__( def __init__(
self, self,
protocol='grandtest', # grandtest is the default protocol for this database protocol="grandtest", # grandtest is the default protocol for this database
original_directory=None, original_directory=None,
original_extension=None, original_extension=None,
**kwargs): annotation_directory=None,
annotation_extension='.json',
annotation_type='json',
**kwargs
):
""" """
**Parameters:** **Parameters:**
...@@ -119,19 +116,24 @@ class MsuMfsdPadDatabase(PadDatabase): ...@@ -119,19 +116,24 @@ class MsuMfsdPadDatabase(PadDatabase):
# Since the high level API expects different group names than what the low # Since the high level API expects different group names than what the low
# level API offers, you need to convert them when necessary # level API offers, you need to convert them when necessary
self.low_level_group_names = ( self.low_level_group_names = (
'train', 'devel', "train",
'test') # group names in the low-level database interface "devel",
"test",
) # group names in the low-level database interface
self.high_level_group_names = ( self.high_level_group_names = (
'train', 'dev', "train",
'eval') # names are expected to be like that in objects() function "dev",
"eval",
) # names are expected to be like that in objects() function
# Always use super to call parent class methods. # Always use super to call parent class methods.
super(MsuMfsdPadDatabase, self).__init__( super(MsuMfsdPadDatabase, self).__init__(
name='msu-mfsd', name="msu-mfsd",
protocol=protocol, protocol=protocol,
original_directory=original_directory, original_directory=original_directory,
original_extension=original_extension, original_extension=original_extension,
**kwargs) **kwargs
)
@property @property
def original_directory(self): def original_directory(self):
...@@ -141,13 +143,9 @@ class MsuMfsdPadDatabase(PadDatabase): ...@@ -141,13 +143,9 @@ class MsuMfsdPadDatabase(PadDatabase):
def original_directory(self, value): def original_directory(self, value):
self.db.original_directory = value self.db.original_directory = value
#========================================================================== def objects(
def objects(self, self, groups=None, protocol=None, purposes=None, model_ids=None, **kwargs
groups=None, ):
protocol=None,
purposes=None,
model_ids=None,
**kwargs):
""" """
This function returns lists of MsuMfsdPadFile objects, which fulfill the given restrictions. This function returns lists of MsuMfsdPadFile objects, which fulfill the given restrictions.
...@@ -179,16 +177,21 @@ class MsuMfsdPadDatabase(PadDatabase): ...@@ -179,16 +177,21 @@ class MsuMfsdPadDatabase(PadDatabase):
# Convert group names to low-level group names here. # Convert group names to low-level group names here.
groups = self.convert_names_to_lowlevel( groups = self.convert_names_to_lowlevel(
groups, self.low_level_group_names, self.high_level_group_names) groups, self.low_level_group_names, self.high_level_group_names
)
# Since this database was designed for PAD experiments, nothing special # Since this database was designed for PAD experiments, nothing special
# needs to be done here. # needs to be done here.
files = self.db.objects(group=groups, cls=purposes, **kwargs) files = self.db.objects(group=groups, cls=purposes, **kwargs)
files = [MsuMfsdPadFile(f) for f in files] files = [MsuMfsdPadFile(f) for f in files]
for f in files:
f.original_directory = self.original_directory
f.annotation_directory = self.annotation_directory
f.annotation_extension = self.annotation_extension
f.annotation_type = self.annotation_type
return files return files
#==========================================================================
def annotations(self, f): def annotations(self, f):
""" """
Return annotations for a given file object ``f``, which is an instance Return annotations for a given file object ``f``, which is an instance
...@@ -220,12 +223,14 @@ class MsuMfsdPadDatabase(PadDatabase): ...@@ -220,12 +223,14 @@ class MsuMfsdPadDatabase(PadDatabase):
for frame_annots in annots: for frame_annots in annots:
topleft = (np.int(frame_annots[2]), np.int(frame_annots[1])) topleft = (np.int(frame_annots[2]), np.int(frame_annots[1]))
bottomright = (np.int(frame_annots[2] + frame_annots[4]), bottomright = (
np.int(frame_annots[1] + frame_annots[3])) np.int(frame_annots[2] + frame_annots[4]),
np.int(frame_annots[1] + frame_annots[3]),
)
annotations[str(np.int(frame_annots[0]))] = { annotations[str(np.int(frame_annots[0]))] = {
'topleft': topleft, "topleft": topleft,
'bottomright': bottomright "bottomright": bottomright,
} }
return annotations return annotations
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Used in ReplayMobilePadFile class from bob.pad.base.database import PadDatabase # Used in ReplayMobilePadFile class
from bob.pad.base.database import PadDatabase
from bob.pad.face.database import VideoPadFile # Used in ReplayPadFile class from bob.pad.face.database import VideoPadFile # Used in ReplayPadFile class
from bob.pad.face.utils import frames, number_of_frames
from bob.extension import rc from bob.extension import rc
from bob.ip.facedetect import expected_eye_positions, BoundingBox
from bob.db.base.annotations import read_annotation_file
REPLAY_ATTACK_FRAME_SHAPE = (3, 240, 320)
class ReplayPadFile(VideoPadFile): class ReplayPadFile(VideoPadFile):
...@@ -35,16 +37,73 @@ class ReplayPadFile(VideoPadFile): ...@@ -35,16 +37,73 @@ class ReplayPadFile(VideoPadFile):
if f.is_real(): if f.is_real():
attack_type = None attack_type = None
else: else:
attack_type = 'attack' attack_type = "attack"
# attack_type is a string and I decided to make it like this for this # attack_type is a string and I decided to make it like this for this
# particular database. You can do whatever you want for your own # particular database. You can do whatever you want for your own
# database. # database.
super(ReplayPadFile, self).__init__( super(ReplayPadFile, self).__init__(
client_id=f.client_id, client_id=f.client_id, path=f.path, attack_type=attack_type, file_id=f.id
path=f.path, )
attack_type=attack_type,
file_id=f.id) @property
def frame_shape(self):
"""Returns the size of each frame in this database.
Returns
-------
(int, int, int)
The (#Channels, Height, Width) which is (3, 240, 320).
"""
return REPLAY_ATTACK_FRAME_SHAPE
@property
def annotations(self):
"""
Return annotations as a dictionary of dictionaries.
If the file object has an attribute of annotation_directory, it will read
annotations from there instead of loading annotations that are shipped with the
database.
Returns
-------
annotations : :py:class:`dict`
A dictionary containing the annotations for each frame in the
video. Dictionary structure:
``annotations = {'1': frame1_dict, '2': frame1_dict, ...}``.Where
``frameN_dict = {'topleft': (row, col), 'bottomright': (row, col)}``
is the dictionary defining the coordinates of the face bounding box
in frame N.
"""
if (
hasattr(self, "annotation_directory")
and self.annotation_directory is not None
):
path = self.make_path(self.annotation_directory, extension=".json")
return read_annotation_file(path, annotation_type="json")
# numpy array containing the face bounding box data for each video
# frame, returned data format described in the f.bbx() method of the
# low level interface
annots = self.f.bbx(directory=self.original_directory)
annotations = {} # dictionary to return
for fn, frame_annots in enumerate(annots):
topleft = (frame_annots[2], frame_annots[1])
bottomright = (
frame_annots[2] + frame_annots[4],
frame_annots[1] + frame_annots[3],
)
annotations[str(fn)] = {"topleft": topleft, "bottomright": bottomright}
size = (bottomright[0] - topleft[0], bottomright[1] - topleft[1])
bounding_box = BoundingBox(topleft, size)
annotations[str(fn)].update(expected_eye_positions(bounding_box))
return annotations
class ReplayPadDatabase(PadDatabase): class ReplayPadDatabase(PadDatabase):
...@@ -54,12 +113,14 @@ class ReplayPadDatabase(PadDatabase): ...@@ -54,12 +113,14 @@ class ReplayPadDatabase(PadDatabase):
""" """
def __init__( def __init__(
self, self,
# grandtest is the default protocol for this database # grandtest is the default protocol for this database
protocol='grandtest', protocol="grandtest",
original_directory=rc['bob.db.replay.directory'], original_directory=rc["bob.db.replay.directory"],
original_extension='.mov', original_extension=".mov",
**kwargs): annotation_directory=None,
**kwargs
):
""" """
Parameters Parameters
---------- ----------
...@@ -86,19 +147,25 @@ class ReplayPadDatabase(PadDatabase): ...@@ -86,19 +147,25 @@ class ReplayPadDatabase(PadDatabase):
# Since the high level API expects different group names than what the # Since the high level API expects different group names than what the
# low level API offers, you need to convert them when necessary # low level API offers, you need to convert them when necessary
self.low_level_group_names = ( self.low_level_group_names = (
'train', 'devel', "train",
'test') # group names in the low-level database interface "devel",
"test",
) # group names in the low-level database interface
self.high_level_group_names = ( self.high_level_group_names = (
'train', 'dev', "train",
'eval') # names are expected to be like that in objects() function "dev",
"eval",
) # names are expected to be like that in objects() function
# Always use super to call parent class methods. # Always use super to call parent class methods.
super(ReplayPadDatabase, self).__init__( super(ReplayPadDatabase, self).__init__(
name='replay', name="replay",
protocol=protocol, protocol=protocol,
original_directory=original_directory, original_directory=original_directory,
original_extension=original_extension, original_extension=original_extension,
**kwargs) annotation_directory=annotation_directory,
**kwargs
)
@property @property
def original_directory(self): def original_directory(self):
...@@ -108,12 +175,9 @@ class ReplayPadDatabase(PadDatabase): ...@@ -108,12 +175,9 @@ class ReplayPadDatabase(PadDatabase):
def original_directory(self, value): def original_directory(self, value):
self.db.original_directory = value self.db.original_directory = value
def objects(self, def objects(
groups=None, self, groups=None, protocol=None, purposes=None, model_ids=None, **kwargs
protocol=None, ):
purposes=None,
model_ids=None,
**kwargs):
""" """
This function returns lists of ReplayPadFile objects, which fulfill the This function returns lists of ReplayPadFile objects, which fulfill the
given restrictions. given restrictions.
...@@ -146,12 +210,19 @@ class ReplayPadDatabase(PadDatabase): ...@@ -146,12 +210,19 @@ class ReplayPadDatabase(PadDatabase):
# Convert group names to low-level group names here. # Convert group names to low-level group names here.
groups = self.convert_names_to_lowlevel( groups = self.convert_names_to_lowlevel(
groups, self.low_level_group_names, self.high_level_group_names) groups, self.low_level_group_names, self.high_level_group_names
)
# Since this database was designed for PAD experiments, nothing special # Since this database was designed for PAD experiments, nothing special
# needs to be done here. # needs to be done here.
files = self.db.objects( files = self.db.objects(
protocol=protocol, groups=groups, cls=purposes, **kwargs) protocol=protocol, groups=groups, cls=purposes, **kwargs
)
files = [ReplayPadFile(f) for f in files] files = [ReplayPadFile(f) for f in files]
# set the attributes
for f in files:
f.original_directory = self.original_directory
f.original_extension = self.original_extension
f.annotation_directory = self.annotation_directory
return files return files
def annotations(self, f): def annotations(self, f):
...@@ -178,26 +249,7 @@ class ReplayPadDatabase(PadDatabase): ...@@ -178,26 +249,7 @@ class ReplayPadDatabase(PadDatabase):
is the dictionary defining the coordinates of the face bounding box is the dictionary defining the coordinates of the face bounding box
in frame N. in frame N.
""" """
return f.annotations
# numpy array containing the face bounding box data for each video
# frame, returned data format described in the f.bbx() method of the
# low level interface
annots = f.f.bbx(directory=self.original_directory)
annotations = {} # dictionary to return
for fn, frame_annots in enumerate(annots):
topleft = (frame_annots[2], frame_annots[1])
bottomright = (frame_annots[2] + frame_annots[4],
frame_annots[1] + frame_annots[3])
annotations[str(fn)] = {
'topleft': topleft,
'bottomright': bottomright
}
return annotations
def frames(self, padfile): def frames(self, padfile):
"""Yields the frames of the padfile one by one. """Yields the frames of the padfile one by one.
...@@ -212,11 +264,7 @@ class ReplayPadDatabase(PadDatabase): ...@@ -212,11 +264,7 @@ class ReplayPadDatabase(PadDatabase):
:any:`numpy.array` :any:`numpy.array`
A frame of the video. The size is (3, 240, 320). A frame of the video. The size is (3, 240, 320).
""" """
vfilename = padfile.make_path( return padfile.frames
directory=self.original_directory,
extension=self.original_extension)
for retval in frames(vfilename):
yield retval
def number_of_frames(self, padfile): def number_of_frames(self, padfile):
"""Returns the number of frames in a video file. """Returns the number of frames in a video file.
...@@ -231,10 +279,7 @@ class ReplayPadDatabase(PadDatabase): ...@@ -231,10 +279,7 @@ class ReplayPadDatabase(PadDatabase):
int int
The number of frames. The number of frames.
""" """
vfilename = padfile.make_path( return padfile.number_of_frames
directory=self.original_directory,
extension=self.original_extension)
return number_of_frames(vfilename)
@property @property
def frame_shape(self): def frame_shape(self):
...@@ -245,4 +290,4 @@ class ReplayPadDatabase(PadDatabase): ...@@ -245,4 +290,4 @@ class ReplayPadDatabase(PadDatabase):
(int, int, int) (int, int, int)
The (#Channels, Height, Width) which is (3, 240, 320). The (#Channels, Height, Width) which is (3, 240, 320).
""" """
return (3, 240, 320) return REPLAY_ATTACK_FRAME_SHAPE
...@@ -5,48 +5,13 @@ ...@@ -5,48 +5,13 @@
from bob.bio.video import FrameSelector from bob.bio.video import FrameSelector
from bob.pad.base.database import PadDatabase from bob.pad.base.database import PadDatabase
from bob.pad.face.database import VideoPadFile from bob.pad.face.database import VideoPadFile
from bob.pad.face.utils import frames, number_of_frames from bob.pad.face.utils import number_of_frames
from bob.db.base.annotations import read_annotation_file from bob.db.base.annotations import read_annotation_file
import numpy
from bob.extension import rc from bob.extension import rc
REPLAYMOBILE_FRAME_SHAPE = (3, 1280, 720) REPLAYMOBILE_FRAME_SHAPE = (3, 1280, 720)
def replaymobile_annotations(lowlevelfile, original_directory):
# numpy array containing the face bounding box data for each video
# frame, returned data format described in the f.bbx() method of the
# low level interface
annots = lowlevelfile.bbx(directory=original_directory)
annotations = {} # dictionary to return
for fn, frame_annots in enumerate(annots):
topleft = (frame_annots[1], frame_annots[0])
bottomright = (frame_annots[1] + frame_annots[3],
frame_annots[0] + frame_annots[2])
annotations[str(fn)] = {
'topleft': topleft,
'bottomright': bottomright
}
return annotations
def replaymobile_frames(lowlevelfile, original_directory):
vfilename = lowlevelfile.make_path(
directory=original_directory,
extension='.mov')
is_not_tablet = not lowlevelfile.is_tablet()
for frame in frames(vfilename):
frame = numpy.rollaxis(frame, 2, 1)
if is_not_tablet:
frame = frame[:, ::-1, :]
yield frame
class ReplayMobilePadFile(VideoPadFile): class ReplayMobilePadFile(VideoPadFile):
""" """
A high level implementation of the File class for the Replay-Mobile A high level implementation of the File class for the Replay-Mobile
...@@ -116,6 +81,7 @@ class ReplayMobilePadFile(VideoPadFile): ...@@ -116,6 +81,7 @@ class ReplayMobilePadFile(VideoPadFile):
@property @property
def annotations(self): def annotations(self):
from bob.db.replaymobile.models import replaymobile_annotations
if self.annotation_directory is not None: if self.annotation_directory is not None:
# return the external annotations # return the external annotations
annotations = read_annotation_file( annotations = read_annotation_file(
...@@ -129,6 +95,7 @@ class ReplayMobilePadFile(VideoPadFile): ...@@ -129,6 +95,7 @@ class ReplayMobilePadFile(VideoPadFile):
@property @property
def frames(self): def frames(self):
from bob.db.replaymobile.models import replaymobile_frames
return replaymobile_frames(self.f, self.original_directory) return replaymobile_frames(self.f, self.original_directory)
@property @property
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment