summaryrefslogtreecommitdiff
path: root/megapixels/commands/datasets/feret.py
blob: 906b4e37b95c7999f418bbc88810bd5c290f0df6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import bz2
import io

import click
from PIL import Image

from app.settings import types
from app.utils import click_utils
from app.settings import app_cfg as cfg
from app.utils.logger_utils import Logger

log = Logger.getLogger()

pose_choices = {
'fa':0, 'fb':0, 'hl':67.5, 'hr':-67.5, 'pl':90, 'pr':-90, 
'ql':22.5, 'qr':-22.5, 'ra':45, 'rb':15, 'rc':-15, 'rd':-45, 're':-75}

poses_left = ['hl', 'ql', 'pl', 'ra', 'rb']
poses_right = ['hr', 'qr', 'pr', 'rc', 're', 're']

@click.command()
@click.option('-i', '--input', 'opt_fp_in', required=True,
  help='Input directory')
@click.option('-o', '--output', 'opt_fp_out', required=True,
  help='Output directory')
@click.option('-a', '--angle', 'opt_angle', type=(float, float), default=(0,0),
  help='Min/max face angles')
@click.option('-t', '--threads', 'opt_threads', default=8,
  help='Number of threads')
@click.option('--flip', 'opt_flip', type=click.Choice(['r', 'l']), 
  help='Flip profile images to the R or L')
@click.pass_context
def cli(ctx, opt_fp_in, opt_fp_out, opt_angle, opt_threads, opt_flip):
  """Extracts FERET images"""
  
  from glob import glob
  from os.path import join
  from pathlib import Path
  import time
  from tqdm import tqdm
  from multiprocessing.dummy import Pool as ThreadPool
  from functools import partial

  from PIL import ImageOps
  from app.utils import file_utils

  # filter angles
  poses = [k for k, v in pose_choices.items() if \
    abs(v) >= opt_angle[0] and abs(v) <= opt_angle[1]]
  
  # glob images dir for all *ppm.bz2
  fp_ims = []
  for pose in poses:
    log.info('globbing pose: {}'.format(pose))
    fp_ims += glob(join(opt_fp_in, '**/*_{}.ppm.bz2').format(pose))
  log.info('Processing: {:,} files'.format(len(fp_ims)))
  
  # convert bz2 to png
  def pool_func(fp_im, opt_fp_out, opt_flip):
    try:
      pbar.update(1)
      im_pil = bz2_to_pil(fp_im)
      fpp_im = Path(fp_im)
      fp_out = join(opt_fp_out, '{}.png'.format(fpp_im.stem))
      fp_out = fp_out.replace('.ppm','')  # remove ppm
      if opt_flip:
        pose_code = fpp_im.stem.split('_')[-1][:2]
        # log.debug('opt_flip: {}, found: {}'.format(opt_flip, pose_code))
        if opt_flip == 'r' and pose_code in poses_right \
          or opt_flip == 'l' and pose_code in poses_left:
            im_pil = ImageOps.mirror(im_pil)
      im_pil.save(fp_out)
      return True
    except Exception as e:
      log.error('Error processing: {}, error: {}'.format(fp_im, e))
      return False

  # make output directory
  file_utils.mkdirs(opt_fp_out)

  # setup multithreading
  pbar = tqdm(total=len(fp_ims))
  pool_resize = partial(pool_func, opt_fp_out=opt_fp_out, opt_flip=opt_flip)
  pool = ThreadPool(opt_threads) 
  with tqdm(total=len(fp_ims)) as pbar:
    results = pool.map(pool_resize, fp_ims)
  pbar.close()

  # results
  log.info('Converted: {} / {} images'.format(results.count(True), len(fp_ims)))

  
# ------------------------------------------------------------------
# local utils

def bz2_to_pil(fp_src):
  with open(fp_src, 'rb') as fp:
    im_raw = bz2.decompress(fp.read())
  im_pil = Image.open(io.BytesIO(im_raw))
  return im_pil



"""

A breakdown of the images by pose is:
  Pose Angle   Images Subjects
  fa       0     1364      994
  fb       0     1358      993
  hl   +67.5     1267      917
  hr   -67.5     1320      953
  pl     +90     1312      960
  pr     -90     1363      994
  ql   +22.5      761      501
  qr   -22.5      761      501
  ra     +45      321      261
  rb     +15      321      261
  rc     -15      610      423
  rd     -45      290      236
  re     -75      290      236

  There are 13 different poses. (The orientation "right" means
facing the photographer's right.)
  fa  regular frontal image
  fb  alternative frontal image, taken shortly after the
      corresponding fa image
  pl  profile left
  hl  half left - head turned about 67.5 degrees left
  ql  quarter left - head turned about 22.5 degrees left
  pr  profile right
  hr  half right - head turned about 67.5 degrees right
  qr  quarter right - head turned about 22.5 degrees right
  ra  random image - head turned about 45 degree left
  rb  random image - head turned about 15 degree left
  rc  random image - head turned about 15 degree right
  rd  random image - head turned about 45 degree right
  re  random image - head turned about 75 degree right

"""