create.py 11.1 KB
Newer Older
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
1
2
3
4
5
6
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

import os
from .models import *

7
8
from bob.db.base.driver import Interface as BaseInterface

Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
9
10
11
12
import bob.core
logger = bob.core.log.setup('bob.db.fargo')


13
def add_clients(session, imagesdir):
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
14
15
  """Add clients

16
  This function adds clients to the database. 
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
17
18
19

  Parameters
  ----------
20
21
  session:
    The session to the SQLite database 
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
22
23
24
25
26
27
  imagesdir : :py:obj:str
    The directory where the images have been extracted
  verbose : bool
    Print some information

  """
28
29
30
  for d in os.listdir(imagesdir):
    client_id = int(d)
    if client_id <= 25:
31
      group = 'world'
32
33
34
35
36
37
38
39
    elif client_id <= 50:
      group = 'dev'
    else:
      group = 'eval'
    logger.info("Adding client {} in group {}".format(client_id, group))
    session.add(Client(client_id, group))

def add_files(session, imagesdir, extension='.png'):
40
41
42
43
44
45
46
47
48
49
50
51
  """ Add face images files.

  This function adds the face image files to the database.

  Parameters
  ----------
  session:
    The session to the SQLite database 
  imagesdir : :py:obj:str
    The directory where the images have been extracted
  extension: :py:obj:str
    The extension of the image file.
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

  """
  for root, dirs, files in os.walk(imagesdir, topdown=False):
    for name in files:
      image_filename = os.path.join(root, name)

      # just to make sure that nothing weird will be added
      if os.path.splitext(image_filename)[1] == extension:

        # get all the info, base on the file path
        image_info = image_filename.replace(imagesdir, '')
        infos = image_info.split('/')
        
        client_id = int(infos[0])
        light = infos[1]
        device = infos[2].replace('SR300-', '')
        recording = infos[3]
        stream = infos[4]
        if stream == 'color': modality = 'rgb'
        if stream == 'ir': modality = 'nir'
        if stream == 'depth': modality = 'depth'
        
        # default pose is frontal
        pose = 'frontal'
        if 'yaw' in image_filename:
          pose = 'yaw'
        if 'pitch' in image_filename:
          pose = 'pitch'
        
        stem = image_info[0:-len(extension)]
82

83
        logger.info("Adding file {}".format(stem))
84
        o = File(client_id=client_id, path=stem, light=light, device=device, pose=pose, modality=modality, recording=recording)
85
        session.add(o)
86
87
88
89
90


def add_protocols(session):
  """ Adds the different protocols of the FARGO database

91
92
93
94
95
96
97
98
99
100
  This function creates and adds protocols and protocol purposes for this database

  There are various protocols availables, addressing several tasks:
  
   - Frontal Face Verification using different modalities (RGB, NIR and depth)
     * MC (matched controlled): training, enrollment and probes are acquired in controlled conditions.
     * UD (unmatched degraded): training, enrollment are acquired in controlled conditions, probes with very low-light illumination
     * UO (unmatched outdoor): training, enrollment are acquired in controlled conditions, probes are acquired outdoor.
     These protocols are named in the following way: {mc, ud, uo}-{rgb,nir,depth}

101

102
103
104
105
106
107
108
109
110
111
   - Pose-varying Face Verification:
     * 'pos-yaw': training, enrollment are acquired in (frontal) controlled conditions, probes with varying yaw
     * 'pos-pitch': training, enrollment are acquired in (frontal) controlled conditions, probes with varying pitch
      
     
   - Heterogeneous Face Verification:
     The same 3 protocols (MC, UD, UO) have been investigated in mismatched modalities too. Basically, starting
     from a model trained using RGB, it is then adapted with the training set of the target modality. Enrollment
     image are RGB, and probes are coming from the target modality (i.e. either NIR or depth).
     These protocols are named in the following way: {mc, ud, uo}-rgb2{nir, depth}
112

113
114
115
116
117
118
  Parameters
  ----------
  session:
    The session to the SQLite database 
  """
  from sqlalchemy import and_
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

  # enrollment images are also the same for all protocols
  recordings_enroll = ['0']
 
  # now probes may change depending on the protocol
  protocol_probes = {}
  
  # this is constant across protocols
  device_probe = ['laptop', 'mobile']

  ##########
  ### MC ###
  ##########
  # matched controlled -> probes are controlled
  session_probe = 'controlled'
  recordings_probe = ['1']

  # add the modalities
  protocol_probes['mc-rgb'] = ['rgb', session_probe, device_probe, recordings_probe]
  protocol_probes['mc-nir'] = ['nir', session_probe, device_probe, recordings_probe]
  protocol_probes['mc-depth'] = ['depth', session_probe, device_probe, recordings_probe]
140
141
142
  # heterogeneous
  protocol_probes['mc-rgb2nir'] = [None , session_probe, device_probe, recordings_probe]
  protocol_probes['mc-rgb2depth'] = [None, session_probe, device_probe, recordings_probe]
143
144
145
146
147
148
149
150
151
152
153
154

  ##########
  ### UD ###
  ##########
  # unmatched degraded -> probes are dark
  session_probe = 'dark'
  recordings_probe = ['0', '1']

  # add the modalities
  protocol_probes['ud-rgb'] = ['rgb', session_probe, device_probe, recordings_probe]
  protocol_probes['ud-nir'] = ['nir', session_probe, device_probe, recordings_probe]
  protocol_probes['ud-depth'] = ['depth', session_probe, device_probe, recordings_probe]
155
156
157
  # heterogeneous
  protocol_probes['ud-rgb2nir'] = [None , session_probe, device_probe, recordings_probe]
  protocol_probes['ud-rgb2depth'] = [None, session_probe, device_probe, recordings_probe]
158
159
160
161
162
163
164
165
166
167
168
169

  ##########
  ### UO ###
  ##########
  # unmatched outdoor -> probes are outdoor
  session_probe = 'outdoor'
  recordings_probe = ['0', '1']

  # add the modalities
  protocol_probes['uo-rgb'] = ['rgb', session_probe, device_probe, recordings_probe]
  protocol_probes['uo-nir'] = ['nir', session_probe, device_probe, recordings_probe]
  protocol_probes['uo-depth'] = ['depth', session_probe, device_probe, recordings_probe]
170
171
172
  # heterogeneous
  protocol_probes['uo-rgb2nir'] = [None , session_probe, device_probe, recordings_probe]
  protocol_probes['uo-rgb2depth'] = [None, session_probe, device_probe, recordings_probe]
173

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
  ###############
  ### POS-YAW ###
  ###############
  # unmatched pose -> probes are with varying yaw
  session_probe = 'controlled'
  recordings_probe = ['0', '1']
  protocol_probes['pos-yaw'] = ['rgb', session_probe, device_probe, recordings_probe]
  
  #################
  ### POS-PITCH ###
  #################
  # unmatched pose -> probes are with varying pitch
  session_probe = 'controlled'
  recordings_probe = ['0', '1']
  protocol_probes['pos-pitch'] = ['rgb', session_probe, device_probe, recordings_probe]
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
189
 
190
191
192
193
194
195
196
197
198
199
200
201
202
203
  # the purpose list 
  group_purpose_list = [('world', 'train'), ('dev', 'enroll'), ('dev', 'probe'), ('eval', 'enroll'), ('eval', 'probe')]
  
  # add each protocol
  for protocol_name in protocol_probes:
  
    p = Protocol(protocol_name)
    logger.info("Adding protocol {}...".format(protocol_name))
    session.add(p)
    session.flush()
    session.refresh(p)

    modality = protocol_probes[protocol_name][0]
    
204
205
    # heterogeneous case
    if modality is None:
Guillaume Heusch's avatar
Guillaume Heusch committed
206
      if 'rgb2nir' in protocol_name:
207
        target_modality = 'nir'
Guillaume Heusch's avatar
Guillaume Heusch committed
208
      if 'rgb2depth' in protocol_name:
209
210
        target_modality = 'depth'
    
211
212
213
214
215
216
217
218
219
220
221
222
223
    # add protocol purposes
    for group_purpose in group_purpose_list:
     
      group = group_purpose[0]
      purpose = group_purpose[1]
      pu = ProtocolPurpose(p.id, group, purpose)
      
      logger.info("  Adding protocol purpose ({}, {})...".format(group, purpose))
      session.add(pu)
      session.flush()
      session.refresh(pu)

      # first retrieve all files for the group 
224
      q = session.query(File).join(Client).filter(Client.group == group).order_by(File.id)
225

226
227
      # if purpose is train, we have controlled frontal images in any cases
      if purpose == 'train':
228
        q = q.filter(and_(File.light == 'controlled', File.pose == 'frontal'))
229
        # now get the right modality
230

231
232
233
234
235
236
237
238
239
240
241
242
        # if HETEROGENEOUS get 2 modalities for the WORLD set
        if "rgb2nir" in p.name or "rgb2depth" in p.name:
          if modality is not None:
            q = q.filter(File.modality.in_([modality,"rgb"]))
          else:
            q = q.filter(File.modality.in_(["rgb", target_modality]))
        else:
          if modality is not None:
            q = q.filter(File.modality.in_([modality]))
          else:
            q = q.filter(File.modality.in_([target_modality]))
 
243
      # for enroll, we have controlled frontal images, for the first recording for each device
244
      if purpose == 'enroll':
245
246
247
248
249
250
251
        q = q.filter(and_(File.light == 'controlled', File.pose == 'frontal', File.recording.in_(recordings_enroll)))
        # now filter modality:
        if modality is not None:
          q = q.filter(File.modality == modality)
        else:
          q = q.filter(File.modality == 'rgb') # always rgb in heterogeneous case

252
253
254
255
256
257
258
259
260
261
262
263
264
      # now the probes 
      if purpose == 'probe':
        # for probes, the number of recording depends on the protocol
        q = q.filter(File.recording.in_(protocol_probes[protocol_name][3]))
        # the light condition as well
        q = q.filter(File.light == protocol_probes[protocol_name][1])
        # the pose
        if 'yaw' in protocol_name:
          q = q.filter(File.pose == 'yaw')
        elif 'pitch' in protocol_name:
          q = q.filter(File.pose == 'pitch')
        else:
          q = q.filter(File.pose == 'frontal')
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
265
        # the modality
266
267
268
269
        if modality is not None:
          q = q.filter(File.modality == modality)
        else:
          q = q.filter(File.modality == target_modality) # target modality in heterogeneous case
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
270
271
272
273
274
275

      # now add the files
      for k in q:
        pu.files.append(k)
      logger.info("added {} files".format(len(list(q))))

Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292

def create_tables(args):
    """Creates all necessary tables (only to be used at the first time)"""

    from bob.db.base.utils import create_engine_try_nolock
    engine = create_engine_try_nolock(args.type, args.files[0], echo=(args.verbose > 2))
    Base.metadata.create_all(engine)


# Driver API
# ==========

def create(args):
  """Creates or re-creates this database"""

  from bob.db.base.utils import session_try_nolock

293
  print(args)
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
294
295
296
297
298
299
300
301
302
303
304
  dbfile = args.files[0]

  if args.recreate:
    if args.verbose and os.path.exists(dbfile):
      print(('unlinking %s...' % dbfile))
    if os.path.exists(dbfile):
      os.unlink(dbfile)

  if not os.path.exists(os.path.dirname(dbfile)):
    os.makedirs(os.path.dirname(dbfile))

305
306
  bob.core.log.set_verbosity_level(logger, args.verbose)

Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
307
308
  # the real work...
  create_tables(args)
309
  s = session_try_nolock(args.type, args.files[0], echo=False)
310
311
  add_clients(s, args.imagesdir)
  add_files(s, args.imagesdir)
312
  add_protocols(s)
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
  s.commit()
  s.close()

  return 0


def add_command(subparsers):
  """Add specific subcommands that the action "create" can use"""

  parser = subparsers.add_parser('create', help=create.__doc__)

  parser.add_argument('-R', '--recreate', action='store_true', default=False,
                      help="If set, I'll first erase the current database")
  parser.add_argument('-v', '--verbose', action='count', default=0,
                      help="Do SQL operations in a verbose way")
328
329
  parser.add_argument('imagesdir', action='store', metavar='DIR',
                      help="The path to the extracted images of the FARGO database")
Guillaume HEUSCH's avatar
Guillaume HEUSCH committed
330
331

  parser.set_defaults(func=create)  # action