[
  {
    "path": ".gitignore",
    "content": "# Compiled Lua sources\nluac.out\n\n# luarocks build files\n*.src.rock\n*.zip\n*.tar.gz\n\n# Object files\n*.o\n*.os\n*.ko\n*.obj\n*.elf\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n*.def\n*.exp\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\n"
  },
  {
    "path": "LICENSE",
    "content": "This pipeline is build around the ImageNet training code avaialable at <https://github.com/facebook/fb.resnet.torch> and HourGlass(HG) code available at https://github.com/anewell/pose-hg-train\nCopyright (c) 2016, Facebook, Inc. \nCopyright (c) 2016, University of Michigan\n\nFor the rest of the code and models:\nCopyright (c) 2017, University of Nottingham\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1.Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n2.Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n3.Neither the name of the paper nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# How far are we from solving the 2D \\& 3D Face Alignment problem? (and a dataset of 230,000 3D facial landmarks)\n\nThis is the training code for 2D-FAN and 3D-FAN decribed in \"How far are we from solving the 2D \\& 3D Face Alignment problem? (and a dataset of 230,000 3D facial landmarks)\" paper. Please visit [our](https://www.adrianbulat.com) webpage or read bellow for instructions on how to run the code.\n\nPretrained models are available on our page.\n\n**Demo code: <https://www.github.com/1adrianb/2D-and-3D-face-alignment>**\n\nNote: If you are interested in a binarized version, capable of running on devices with limited resources please also check <https://github.com/1adrianb/binary-face-alignment> for a demo.\n\n## Requirments\n\n- Install the latest [Torch7](http://torch.ch/docs/getting-started.html) version (for Windows, please follow the instructions available [here](https://github.com/torch/distro/blob/master/win-files/README.md))\n\n### Packages\n\n- [cutorch](https://github.com/torch/cutorch)\n- [nn](https://github.com/torch/nn)\n- [nngraph](https://github.com/torch/nngraph)\n- [cudnn](https://github.com/soumith/cudnn.torch)\n- [xlua](https://github.com/torch/xlua)\n- [image](https://github.com/torch/image)\n- [paths](https://github.com/torch/paths)\n- [matio](https://github.com/soumith/matio-ffi.torch)\n\n## Setup\n\n1. Clone the github repository and install all the dependencies mentiones above.\n\n```bash\n\ngit  clone https://github.com/1adrianb/face-alignment-training\ncd face-alignment-training\n```\n\n2. Download the 300W-LP dataset from the authors webpage. In order to train on your own data the dataloader.lua file needs to be adapted.\n\n3. Download the 300W-LP annotations converted to t7 format from [here](https://www.adrianbulat.com/downloads/FaceAlignment/landmarks.zip), extract it and move the ```landmarks``` folder to the root of the 300W-LP dataset.\n\n## Usage\n\nIn order to run the demo please download the required models available bellow and the associated data.\n\n```bash\nth main.lua -data path_to_300W_LP_dataset\n```\n\nIn order to see all the available options please run:\n\n```bash\nth main.lua --help\n```\n\n## Citation\n\n```\n@inproceedings{bulat2017far,\n  title={How far are we from solving the 2D \\& 3D Face Alignment problem? (and a dataset of 230,000 3D facial landmarks)},\n  author={Bulat, Adrian and Tzimiropoulos, Georgios},\n  booktitle={International Conference on Computer Vision},\n  year={2017}\n}\n```\n\n## Acknowledgements\n\nThis pipeline is build around the ImageNet training code avaialable at <https://github.com/facebook/fb.resnet.torch> and HourGlass(HG) code available at https://github.com/anewell/pose-hg-train\n"
  },
  {
    "path": "checkpoints.lua",
    "content": "local checkpoint = {}\n\nfunction checkpoint.latest(opt)\n   if opt.resume == 'none' then\n      return nil\n   end\n\n   local latestPath = paths.concat(opt.resume, 'latest.t7')\n   if not paths.filep(latestPath) then\n      return nil\n   end\n\n   print('=> Loading checkpoint ' .. latestPath)\n   local latest = torch.load(latestPath)\n   local optimState = torch.load(paths.concat(opt.resume, latest.optimFile))\n   return latest, optimState\nend\n\nfunction checkpoint.save(epoch, model, optimState)\n   -- Don't save the DataParallelTable for easier loading on other machines\n   if torch.type(model) == 'nn.DataParallelTable' then\n      model = model:get(1)\n   end\n\n   local modelFile = 'model_' .. epoch .. '.t7'\n   local optimFile = 'optimState_' .. epoch .. '.t7'\n\n   torch.save(modelFile, model)\n   torch.save(optimFile, optimState)\n   torch.save('latest.t7', {\n      epoch = epoch,\n      modelFile = modelFile,\n      optimFile = optimFile,\n   })\nend\n\nreturn checkpoint\n\n"
  },
  {
    "path": "dataloader.lua",
    "content": "local datasets = require 'dataset-init'\nlocal Threads = require 'threads'\nThreads.serialization('threads.sharedserialize')\n\nlocal M = {}\nlocal DataLoader = torch.class('DataLoader', M)\n\nfunction DataLoader.getDataFaces(opt, split)\n    print('=> Building dataset...')\n    base_dir = opt.data..'landmarks/'\n    dirs = paths.dir(base_dir)\n    lines = {}\n\n    for i=1,#dirs do\n        if string.sub(dirs[i],1,1) ~= '.' then\n            for f in paths.files(base_dir..dirs[i],'.mat') do\n                if not string.find(f, \"test\") then              \n                        lines[#lines+1] = f\n                end\n            end\n        end\n    end\n    print('=> Dataset built. '..#lines..' images were found.')\n    return lines\nend\n\nfunction DataLoader.create(opt,split)\n    local _dataset = nil\n    local dataAnnot = DataLoader.getDataFaces(opt,split) \n\n    return M.DataLoader(_dataset,opt,split,dataAnnot)\nend\n\nfunction DataLoader:__init(_dataset, opt, split, dataAnnot)\n    local manualSeed = opt.manualSeed\n\n    local function init()\n        local datasets = require 'dataset-init'\n\n        trainLoader, valLoader = datasets.create(opt,split,dataAnnot)\n    end\n\n    local function main(idx)\n        if manualSeed ~= 0 then\n            torch.manualSeed(manualSeed + idx)\n        end\n        torch.setnumthreads(1)\n        _G.dataset = trainLoader\n        return trainLoader:size()\n    end\n\n    local threads, sizes = Threads(opt.nThreads,init, main)\n    self.nCrops = 1\n    self.threads = threads\n    self.__size = sizes[1][1]\n    self.batchSize = math.floor(opt.batchSize / self.nCrops)\n    self.opt = opt\n    self.split = split\n    self.dataAnnot = dataAnnot\nend\n\nfunction DataLoader:size()\n        return math.ceil(self.__size/self.batchSize)\nend\n\nfunction DataLoader:annot()\n        trainLoader = datasets.create(self.opt,self.split,self.dataAnnot)\n        return  trainLoader.annot\nend\n\nfunction DataLoader:run()\n    local threads = self.threads\n    local size, batchSize = self.__size, self.batchSize\n    local perm = torch.randperm(size)\n\n    local idx, sample = 1, nil\n    local function enqueue()\n        while idx <= size and threads:acceptsjob() do\n            local indices = perm:narrow(1,idx,math.min(batchSize,size-idx+1))\n            threads:addjob(\n                function(indices,nCrops)\n                    local sz = indices:size(1)\n                    local batch, imageSize\n                    local target\n                    local indicesCopy = indices\n                    for i,idx in ipairs(indices:totable()) do\n                        local sample, label = _G.dataset:get(false,idx)\n                        local input, label = _G.dataset:preprocess(sample, label)\n                        if not batch then\n                            imageSize = input:size():totable()\n                            if nCrops > 1 then table.remove(imageSize,1) end\n                                batch = torch.FloatTensor(sz,nCrops, table.unpack(imageSize))\n                            end\n                            if not target then\n                                targetSize = label:size():totable()\n                                target = torch.FloatTensor(sz,nCrops, table.unpack(targetSize))\n                            end\n                            batch[i]:copy(input)\n                            target[i]:copy(label)\n                        end\n                        collectgarbage()\n                        return {\n                            input = batch:view(sz*nCrops,table.unpack(imageSize)),\n                            label = target:view(sz*nCrops,table.unpack(targetSize)),\n                            indx = indicesCopy ,\n                        }\n                        end,\n                        function(_sample_)\n                            sample = _sample_\n                            end,\n                            indices,\n                            self.nCrops\n                        )\n                        idx = idx + batchSize\n                end\n        end\n    local n = 0\n    local function loop()\n        enqueue()\n        if not threads:hasjob() then\n            return nil\n        end\n        threads:dojob()\n        if threads:haserror() then\n            threads:synchronize()\n        end\n        enqueue()\n        n = n+1\n        return n, sample\n    end\n\n    return loop\nend\nreturn M.DataLoader\n"
  },
  {
    "path": "dataset-images.lua",
    "content": "local image = require('image')\nrequire 'utils'\n\nlocal M = {}\nlocal DatasetImages = torch.class('DatasetImages', M)\n\nfunction DatasetImages:__init( opt, split, annot )\n    self.total = #annot\n    self.nParts = 68\n    self.annot = annot\n    self.opt = opt\n    self.typeOfData = split\nend\n\nfunction DatasetImages:generateSampleFace(idx)\n    local main_pts = torch.load(self.opt.data..'landmarks/'..self.annot[idx]:split('_')[1]..'/'..string.sub(self.annot[idx],1,#self.annot[idx]-4)..'.t7')\n    local pts = main_pts[1] --- 2:3D\n    local c = torch.Tensor{450/2,450/2+50}\n    local s = 1.8\n\n    local img = image.load(self.opt.data..self.annot[idx]:split('_')[1]..'/'..string.sub(self.annot[idx],1,#self.annot[idx]-8)..'.jpg')\n    local inp = crop(img, c, s, 0, 256)\n    local out = torch.zeros(self.nParts, 64, 64)\n    for i = 1, self.nParts do\n        if pts[i][1] > 0 then -- Checks that there is a ground truth annotation\n            drawGaussian(out[i], transform(torch.add(pts[i],1), c, s, 0, 64), 1)\n        end\n    end\n\n    return inp,out,pts,c,s\nend\n\nfunction DatasetImages:get(shuffle,i)\n    local inp, out, pts, c, s = self:generateSampleFace(i)\n    self.pts, self.c, self.s = pts,c,s\n    return inp, out\nend\n\nfunction DatasetImages:size()\n    return self.total\nend\n\nfunction DatasetImages:preprocess(input, label)\n    if self.typeOfData == 'train'  then\n        local s = torch.randn(1):mul(self.opt.scaleFactor):add(1):clamp(1-self.opt.scaleFactor,1+self.opt.scaleFactor)[1]\n        local r = torch.randn(1):mul(self.opt.rotFactor):clamp(-2*self.opt.rotFactor,2*self.opt.rotFactor)[1]\n\n        -- Scale/rotation\n        if torch.uniform() <= .6 then r = 0 end\n        local inp,out = 256, 64\n        local divideBy = 200\n\n        input = crop(input, {(inp+1)/2,(inp+1)/2}, inp*s/divideBy, r, inp)\n        label = crop(label, {(out+1)/2,(out+1)/2}, out*s/divideBy, r, out)\n\n        -- Emulate row resolution\n        if torch.uniform()<=.2 and false then --.35\n            input = image.scale(input,96,96)\n            input = image.scale(input,256,256)\n        end\n\n        -- Add jpeg artefacts\n        --[[\n        if torch.uniform()<=.2 and false then\n            local onlyImg = input[{{1,3},{},{}}]\n            onlyImg = image.compressJPG(onlyImg,30)\n            onlyImg = image.decompressJPG(onlyImg)\n            input[{{1,3},{},{}}] = onlyImg\n        end\n        ]]--\n\n        -- Add random translation\n        --[[\n        wh_t = torch.Tensor(2):random(0,80)-40\n        input = image.translate(input,wh_t[1],wh_t[2])\n        label = image.translate(label,wh_t[1]/4.0,wh_t[2]/4.0)\n        ]]--\n\n        -- Add some gaussian blue\n        --[[\n        if torch.uniform()<.4 and false  then\n            gauss_s = torch.Tensor(1):random(10,30):int()\n            local kernel_gauss = image.gaussian(gauss_s[1])\n            input = image.convolve(input, kernel_gauss, 'same')/255.0\n        end\n        ]]--\n\n        local flip_ = customFlip or flip\n\n        local shuffleLR_ = customShuffleLR or shuffleLR\n        if torch.uniform() <= .5 then\n            input = flip_(input)\n            label = flip_(shuffleLR_(label))\n        end\n\n        -- Color augumentation\n        input[{1, {}, {}}]:mul(torch.uniform(0.7, 1.3)):clamp(0, 1)\n        input[{2, {}, {}}]:mul(torch.uniform(0.7, 1.3)):clamp(0, 1)\n        input[{3, {}, {}}]:mul(torch.uniform(0.7, 1.3)):clamp(0, 1)\n    end\n    return input, label\nend\n\nreturn M.DatasetImages\n"
  },
  {
    "path": "dataset-init.lua",
    "content": "local M = {}\n\nfunction M.create(opt, split, annot)\n   local Dataset = require('dataset-images')\n   return Dataset(opt, split, annot)\nend\n\nreturn M\n\n\n\n\n\n"
  },
  {
    "path": "main.lua",
    "content": "require 'torch'\nrequire 'cutorch'\nrequire 'paths'\nrequire 'nn'\nrequire 'nngraph'\n\nlocal DataLoader = require 'dataloader'\nlocal checkpoints = require 'checkpoints'\nlocal models = require 'models/init'\nlocal Trainer = require 'train'\nlocal opts = require 'opts'\n\nlocal opt = opts.parse(arg)\n\ntorch.setdefaulttensortype('torch.FloatTensor')\ntorch.setnumthreads(1)\ncutorch.setDevice(1)\ntorch.setheaptracking(true)\n\ntorch.manualSeed(opt.manualSeed)\ncutorch.manualSeed(opt.manualSeed)\n\n--Load previous checkpoints, if it exists\nlocal checkpoint, optimState = checkpoints.latest(opt)\nlocal optimState = checkpoint and torch.load(checkpoint.optimFile) or nil\n\n--Create model\nlocal model, criterion = models.setup(opt, checkpoint, true)\n\nprint('=> Model size: ', model:getParameters():size(1))\n\n--Data loading\nlocal trainLoader = DataLoader.create(opt,'train')\n\nlocal trainer  = Trainer(model, criterion, opt, optimState, netLogger)\n\nlocal startEpoch = checkpoint and checkpoint.epoch + 1 or opt.epochNumber\n\nfor epoch = startEpoch, opt.nEpochs do\n        -- Train for a single epoch\n        local trainLoss, trainAcc = trainer:train(epoch, trainLoader)\n        print(string.format(' *Results loss: %6.6f acc: %6.6f ',trainLoss, trainAcc))\n\n        if opt.snapshot ~= 0 and epoch % opt.snapshot == 0 then\n                checkpoints.save(epoch, model:clearState(), trainer.optimState, bestModel)\n        end\nend\n\n\n"
  },
  {
    "path": "models/fan.lua",
    "content": "-- Face Alignment Network\n--\n-- How far are we from solving the 2D \\& 3D Face Alignment problem? (and a dataset of 230,000 3D facial landmarks)\n-- Adrian Bulat and Georgios Tzimiropoulos\n-- ICCV 2017\n--\n\nlocal cudnn = require 'cudnn'\n\n-- Define some short names\nlocal conv = cudnn.SpatialConvolution\nlocal batchnorm = nn.SpatialBatchNormalization\nlocal relu = cudnn.ReLU\nlocal upsample = nn.SpatialUpSamplingNearest\n\n-- Opts\nlocal nModules = 1\nlocal nFeats = 256\nlocal nStack = 8\n\n\nlocal function convBlock(numIn, numOut, order)\n    local cnet = nn.Sequential()\n        :add(batchnorm(numIn,1e-5,false))\n        :add(relu(true))\n        :add(conv(numIn,numOut/2,3,3,1,1,1,1):noBias())\n        :add(nn.ConcatTable()\n            :add(nn.Identity())\n            :add(nn.Sequential()\n                :add(nn.Sequential()\n                    :add(batchnorm(numOut/2,1e-5,false))\n                    :add(relu(true))\n                    :add(conv(numOut/2,numOut/4,3,3,1,1,1,1):noBias())\n                )\n                :add(nn.ConcatTable()\n                    :add(nn.Identity())\n                    :add(nn.Sequential()\n                        :add(batchnorm(numOut/4,1e-5,false))\n                        :add(relu(true))\n                        :add(conv(numOut/4,numOut/4,3,3,1,1,1,1):noBias())\n                    )\n                )\n                :add(nn.JoinTable(2))\n            )\n        )\n        :add(nn.JoinTable(2))\n    return cnet\nend\n\n-- Skip layer\nlocal function skipLayer(numIn,numOut)\n    if numIn == numOut  then\n        return nn.Identity()\n    else\n        return nn.Sequential()\n            :add(batchnorm(numIn,1e-5,false))\n            :add(relu(true))\n            :add(conv(numIn,numOut,1,1):noBias())\n    end\nend\n\n-- Residual block\nlocal function Residual(numIn,numOut)\n    return nn.Sequential()\n        :add(nn.ConcatTable()\n            :add(convBlock(numIn,numOut))\n            :add(skipLayer(numIn,numOut)))\n        :add(nn.CAddTable(true))\nend\n\nlocal function lin(numIn,numOut,inp)\n    -- Apply 1x1 convolution, stride 1, no padding\n    local l = conv(numIn,numOut,1,1,1,1,0,0)(inp)\n    return relu(true)(batchnorm(numOut)(l))\nend\n\nlocal function hourglass(n, f)\n        local model = nn.Sequential()\n\n        local branch = nn.ConcatTable()\n        local b1 = nn.Sequential()\n        local b2 = nn.Sequential()\n\n        for i = 1,nModules do b1:add(Residual(f,f)) end\n        b2:add(nn.SpatialMaxPooling(2,2,2,2))\n\n        if n>1 then\n                for i = 1,nModules do b2:add(Residual(f,f)) end\n        else\n                for i = 1,nModules do b2:add(Residual(f,f)) end\n        end\n\n        if n>1 then\n                b2:add(hourglass(n-1,f))\n        else\n                for i = 1,nModules do b2:add(Residual(f,f)) end\n        end\n\n        if n>1 then\n                for i = 1,nModules do b2:add(Residual(f,f)) end\n        else\n                for i=1,nModules do b2:add(Residual(f,f)) end\n        end\n        b2:add(upsample(2))\n\n        branch:add(b1):add(b2)\n        model:add(branch)\n\n        return model:add(nn.CAddTable())\nend\n\nlocal function hourglass(n, f, inp)\n    -- Upper branch\n    local up1 = inp\n    for i = 1,nModules do up1 = Residual(f,f)(up1) end\n\n    -- Lower branch\n    local low1 = cudnn.SpatialMaxPooling(2,2,2,2)(inp)\n    for i = 1,nModules do low1 = Residual(f,f)(low1) end\n    local low2\n\n    if n > 1 then low2 = hourglass(n-1,f,low1)\n    else\n        low2 = low1\n        for i = 1,nModules do low2 = Residual(f,f)(low2) end\n    end\n\n    local low3 = low2\n    for i = 1,nModules do low3 = Residual(f,f)(low3) end\n    local up2 = nn.SpatialUpSamplingNearest(2)(low3)\n\n    -- Bring two branches together\n    return nn.CAddTable()({up1,up2})\nend\n\n\n\nfunction createModel(opt)\n    nModules = opt.nModules\n    nFeats = opt.nFeats\n    nStack = opt.nStacks\n\n    local inp = nn.Identity()()\n\n    -- Initial processing of the image\n    local cnv1_ = conv(3,64,7,7,2,2,3,3)(inp)           -- 128\n    local cnv1 = relu(true)(batchnorm(64)(cnv1_))\n    local r1 = Residual(64,128)(cnv1)\n    local pool = nn.SpatialMaxPooling(2,2,2,2)(r1)                       -- 64\n    local r4 = Residual(128,128)(pool)\n    local r5 = Residual(128,nFeats)(r4)\n\n    local out = {}\n    local inter = r5\n\n    for i = 1,nStack do\n        local hg = hourglass(4,nFeats,inter)\n\n        -- Residual layers at output resolution\n        local ll = hg\n        for j = 1,nModules do ll = Residual(nFeats,nFeats)(ll) end\n        -- Linear layer to produce first set of predictions\n        ll = lin(nFeats,nFeats,ll)\n\n        -- Predicted heatmaps\n        local tmpOut = conv(nFeats,68,1,1,1,1,0,0)(ll)\n        table.insert(out,tmpOut)\n\n        -- Add predictions back\n        if i < nStack then\n            local ll_ = conv(nFeats,nFeats,1,1,1,1,0,0)(ll)\n            local tmpOut_ = conv(68,nFeats,1,1,1,1,0,0)(tmpOut)\n            inter = nn.CAddTable()({inter, ll_, tmpOut_})\n        end\n    end\n\n    -- Final model\n    local model = nn.gModule({inp}, out)\n\n    return model\n\nend\n\nreturn createModel\n\n\n\n"
  },
  {
    "path": "models/init.lua",
    "content": "local M = {}\n\nfunction  M.setup(opt, checkpoint)\n    local model \n    if checkpoint then\n        local modelPath = paths.concat(opt.resume, checkpoint.modelFile)\n        assert(paths.filep(modelPath), 'Saved model not found: ' .. modelPath)\n        print('=> Resuming model from ' .. modelPath)\n        model = torch.load(modelPath)\n    elseif opt.retrain ~= 'none' then\n        local modelPath = paths.concat(opt.resume, checkpoint.modelFile)\n        assert(paths.filep(modelPath), 'Saved model not found: ' .. modelPath)\n        print('=> Resuming model from ' .. modelPath)\n        model = torch.load(modelPath)\n        if preprocess == false then\n            return model, nil\n        end\n    else\n        print('=> Creating model from file: models/' .. opt.netType .. '.lua')\n        model = require('models/' .. opt.netType)(opt)\n    end\n\n    if torch.type(model) == 'nn.DataParallelTable' then\n        model = model:get(1)\n    end\n\n    -- Set the CUDNN flags\n    if opt.cudnn == 'fastest' then\n        cudnn.fastest = true\n        cudnn.benchmark = true\n    elseif opt.cudnn == 'deterministic' then\n        -- Use a deterministic convolution implementation\n        model:apply(function(m)\n            if m.setMode then m:setMode(1, 1, 1) end\n        end)\n    end\n\n    if opt.nGPU > 1 then\n        local gpus = torch.range(1, opt.nGPU):totable()\n        local fastest, benchmark = cudnn.fastest, cudnn.benchmark\n\n        local dpt = nn.DataParallelTable(1, true, true)\n            :add(model, gpus)\n            :threads(function()\n                local cudnn = require 'cudnn'\n                require 'nngraph'\n                require 'newLayers.BinActiveZ'\n                cudnn.fastest, cudnn.benchmark = fastest, benchmark\n            end)\n        dpt.gradInput = nil\n\n        model = dpt:cuda()\n    end\n\n    local criterion\n    if opt.nStacks>1 then\n        criterion = nn.ParallelCriterion()\n        for i=1,opt.nStacks do\n            criterion:add(nn.MSECriterion())\n        end\n    else\n        criterion = nn.MSECriterion()\n    end\n\n    return model:cuda(), criterion:cuda()\nend\n\nreturn M"
  },
  {
    "path": "opts.lua",
    "content": "local M = { }\n\nfunction M.parse(arg)\n   local cmd = torch.CmdLine()\n   cmd:text()\n   cmd:text('2D-FAN and 3D-FAN Training script')\n   cmd:text('Visit https://www.adrianbulat.com for more details')\n   cmd:text()\n   cmd:text('Options:')\n    ------------ General options --------------------\n   cmd:option('-data',       'dataset/300W-LP/',         'Path to dataset')\n   cmd:option('-manualSeed', 0,          'Manually set RNG seed')\n   cmd:option('-nGPU',       1,          'Number of GPUs to use by default')\n   cmd:option('-backend',    'cudnn',    'Options: cudnn | cunn')\n   cmd:option('-cudnn',      'fastest',  'Options: fastest | default | deterministic')\n   cmd:option('-gen',        'gen',      'Path to save generated files')\n   cmd:option('-snapshot',    3, 'save a snapshot every n epochs')\n   ------------- Data options ------------------------\n   cmd:option('-nThreads',        2, 'number of data loading threads')\n   ------------- Training options --------------------\n   cmd:option('-nEpochs',         100,       'Number of total epochs to run')\n   cmd:option('-epochNumber',     1,       'Manual epoch number (useful on restarts)')\n   cmd:option('-batchSize',       10,      'mini-batch size (1 = pure stochastic)')\n   ------------- Checkpointing options ---------------\n   cmd:option('-save',            'checkpoints', 'Directory in which to save checkpoints')\n   cmd:option('-resume',          'none',        'Resume from the latest checkpoint in this directory')\n   ---------- Optimization options ----------------------\n   cmd:option('-LR',              0.00025,   'initial learning rate')\n   cmd:option('-momentum',        0.0,   'momentum')\n   cmd:option('-weightDecay',     0.0,  'weight decay')\n   ---------- Model options ----------------------------------\n   cmd:option('-netType',      'fan', 'Options: fan')\n   cmd:option('-nModules',       1,       'Number of modues per level')\n   cmd:option('-nStacks',         4,       'Number of stacked networks')\n   cmd:option('-nFeats',         256,     'BLock width (# channels)')\n\n   cmd:option('-retrain',      'none',   'Path to model to retrain with')\n   cmd:option('-optimState',   'none',   'Path to an optimState to reload from')\n   ---------- Augumentation options ----------------------------------\n   cmd:option('-scaleFactor',        0.3,   'scaling factor')\n   cmd:option('-rotFactor',        30,   'rotation factor (in degrees)')\n\n   cmd:text()\n\n   local opt = cmd:parse(arg or {})\n\n   if not paths.dirp(opt.save) and not paths.mkdir(opt.save) then\n      cmd:error('error: unable to create checkpoint directory: ' .. opt.save .. '\\n')\n   end\n\n   return opt\nend\n\nreturn M"
  },
  {
    "path": "train.lua",
    "content": "require 'cunn'\nlocal optim = require 'optim'\n\nlocal lr_policy = {\n    {0,50,2.5e-4},\n    {50,70,1e-4},\n    {70,90,5e-5},\n    {90,100,1e-5},\n    {100,110,5e-6}\n}\n\nlocal M = {}\nlocal Trainer = torch.class('Trainer', M)\n\nfunction Trainer:__init(model,criterion,opt,optimState)\n        self.model = model\n        self.criterion = criterion\n        self.optimState = optimState or {\n                learningRate = opt.LR,\n                learningRateDecay = 0.0,\n                momentum = opt.momentum,\n                epsilon = 1e-8,\n                weightDecay = opt.weightDecay,\n        }\n\n        self.opt = opt\n\n        self.params, self.gradParams = model:getParameters()\nend\n\nfunction Trainer:train(epoch, dataloader)\n        local avgLoss, avgAcc = 0.0, 0.0\n        self.optimState.learningRate = self:learningRate(epoch)\n\n        local timer = torch.Timer()\n        local dataTimer = torch.Timer()\n\n        local function feval()\n                return self.criterion.output, self.gradParams\n        end\n\n        local trainSize = dataloader:size()\n        local N = 0\n\n        print('=> Training epoch # '..epoch)\n\n        self.model:training()\n\n        for n, sample in dataloader:run() do\n                local dataTime = dataTimer:time().real\n                self:copyInputs(sample)\n\n                self.model:zeroGradParameters()\n                local output = self.model:forward(self.input)\n\n                local loss = self.criterion:forward(output, self.label)\n\n                self.criterion:backward(self.model.output, self.label)\n\n                self.model:backward(self.input,self.criterion.gradInput)\n\n                optim.rmsprop(feval, self.params, self.optimState)\n                \n                avgLoss = avgLoss + loss\n                N = N + 1\n\n                print((' | Epoch: [%d][%d/%d]    Time %.3f  Data %.3f  Err %1.4f'):format(\n                        epoch, n, trainSize, timer:time().real, dataTime, loss))\n\n                -- check that the storage didn't get changed do to an unfortunate getParameters call\n                assert(self.params:storage() == self.model:parameters()[1]:storage())\n                collectgarbage()\n                timer:reset()\n                dataTimer:reset()\n        end\n\n        return avgLoss / N, avgAcc / N\nend\n\nfunction Trainer:learningRate(epoch)\n        local decay = 0\n        for i=1, #lr_policy do\n                if (epoch>lr_policy[i][1]) and (lr_policy[i][2]>=epoch) then\n                        print(string.format('Using lr_rate: %f',lr_policy[i][3]))\n                        return lr_policy[i][3]\n                end\n        end\nend\n\nfunction Trainer:copyInputs(sample)\n    -- Copies the input to a CUDA tensor, if using 1 GPU, or to pinned memory,\n    -- if using DataParallelTable. The target is always copied to a CUDA tensor\n    self.input = self.input or (self.opt.nGPU == 1\n      and torch.CudaTensor()\n      or cutorch.createCudaHostTensor())\n    label = label or torch.CudaTensor()\n\n    self.input:resize(sample.input[{{},{},{},{}}]:size()):copy(sample.input[{{},{},{},{}}])\n    label:resize(sample.label:size()):copy(sample.label)\n\n    -- Adjust the input accordingly to the network arhitecture \n    if self.opt.nStacks>1 then\n        local tempLabel = {}\n        for i=1,self.opt.nStacks do\n            table.insert(tempLabel, label)\n        end\n\n        self.label = tempLabel\n    else \n        self.label = label\n    end\nend\n\nreturn M.Trainer\n\n\n\n"
  },
  {
    "path": "utils.lua",
    "content": "-------------------------------------------------------------------------------\n-- Coordinate transformation\n-------------------------------------------------------------------------------\nfunction getTransform(center, scale, rot, res)\n    local h = 200 * scale\n    local t = torch.eye(3)\n\n    -- Scaling\n    t[1][1] = res / h\n    t[2][2] = res / h\n\n    -- Translation\n    t[1][3] = res * (-center[1] / h + .5)\n    t[2][3] = res * (-center[2] / h + .5)\n\n    -- Rotation\n    if rot ~= 0 then\n        rot = -rot\n        local r = torch.eye(3)\n        local ang = rot * math.pi / 180\n        local s = math.sin(ang)\n        local c = math.cos(ang)\n        r[1][1] = c\n        r[1][2] = -s\n        r[2][1] = s\n        r[2][2] = c\n        -- Need to make sure rotation is around center\n        local t_ = torch.eye(3)\n        t_[1][3] = -res/2\n        t_[2][3] = -res/2\n        local t_inv = torch.eye(3)\n        t_inv[1][3] = res/2\n        t_inv[2][3] = res/2\n        t = t_inv * r * t_ * t\n    end\n\n    return t\nend\n\nfunction transform(pt, center, scale, rot, res, invert)\n    local pt_ = torch.ones(3)\n    pt_[1],pt_[2] = pt[1]-1,pt[2]-1\n\n    local t = getTransform(center, scale, rot, res)\n    if invert then\n        t = torch.inverse(t)\n    end\n    local new_point = (t*pt_):sub(1,2):add(1e-4)\n\n    return new_point:int():add(1)\nend\n\nfunction crop(img, center, scale, rot, res)\n    local ul = transform({1,1}, center, scale, 0, res, true)\n    local br = transform({res+1,res+1}, center, scale, 0, res, true)\n\n    local pad = math.floor(torch.norm((ul - br):float())/2 - (br[1]-ul[1])/2)\n    if rot ~= 0 then\n        ul = ul - pad\n        br = br + pad\n    end\n\n    local newDim,newImg,ht,wd\n\n    if img:size():size() > 2 then\n        newDim = torch.IntTensor({img:size(1), br[2] - ul[2], br[1] - ul[1]})\n        newImg = torch.zeros(newDim[1],newDim[2],newDim[3])\n        ht = img:size(2)\n        wd = img:size(3)\n    else\n        newDim = torch.IntTensor({br[2] - ul[2], br[1] - ul[1]})\n        newImg = torch.zeros(newDim[1],newDim[2])\n        ht = img:size(1)\n        wd = img:size(2)\n    end\n\n    local newX = torch.Tensor({math.max(1, -ul[1] + 2), math.min(br[1], wd+1) - ul[1]})\n    local newY = torch.Tensor({math.max(1, -ul[2] + 2), math.min(br[2], ht+1) - ul[2]})\n    local oldX = torch.Tensor({math.max(1, ul[1]), math.min(br[1], wd+1) - 1})\n    local oldY = torch.Tensor({math.max(1, ul[2]), math.min(br[2], ht+1) - 1})\n\n    if newDim:size(1) > 2 then\n        newImg:sub(1,newDim[1],newY[1],newY[2],newX[1],newX[2]):copy(img:sub(1,newDim[1],oldY[1],oldY[2],oldX[1],oldX[2]))\n    else\n        newImg:sub(newY[1],newY[2],newX[1],newX[2]):copy(img:sub(oldY[1],oldY[2],oldX[1],oldX[2]))\n    end\n\n    if rot ~= 0 then\n        newImg = image.rotate(newImg, rot * math.pi / 180, 'bilinear')\n        if newDim:size(1) > 2 then\n            newImg = newImg:sub(1,newDim[1],pad,newDim[2]-pad,pad,newDim[3]-pad)\n        else\n            newImg = newImg:sub(pad,newDim[1]-pad,pad,newDim[2]-pad)\n        end\n    end\n\n    newImg = image.scale(newImg,res,res)\n    return newImg\nend\n\nlocal magic_gaussian = image.gaussian(7)\nfunction drawGaussian(img, pt, sigma)\n    -- Check if the gaussian is in-bounds\n    local ul = {math.floor(pt[1] - 3 * sigma), math.floor(pt[2] - 3 * sigma)}\n    local br = {math.floor(pt[1] + 3 * sigma), math.floor(pt[2] + 3 * sigma)}\n    -- return the image otherwise \n    if (ul[1] > img:size(2) or ul[2] > img:size(1) or br[1] < 1 or br[2] < 1) then return img end\n    -- Generate gaussian\n    local size = 6 * sigma + 1\n    -- Avoid the need of generating the gaussian for each sample\n    local g = magic_gaussian:clone()--image.gaussian(size) -- , 1 / size, 1)\n    \n    -- Usable gaussian range\n    local g_x = {math.max(1, -ul[1]), math.min(br[1], img:size(2)) - math.max(1, ul[1]) + math.max(1, -ul[1])}\n    local g_y = {math.max(1, -ul[2]), math.min(br[2], img:size(1)) - math.max(1, ul[2]) + math.max(1, -ul[2])}\n    -- Image range\n    local img_x = {math.max(1, ul[1]), math.min(br[1], img:size(2))}\n    local img_y = {math.max(1, ul[2]), math.min(br[2], img:size(1))}\n    assert(g_x[1] > 0 and g_y[1] > 0)\n    img:sub(img_y[1], img_y[2], img_x[1], img_x[2]):add(g:sub(g_y[1], g_y[2], g_x[1], g_x[2]))\n    img[img:gt(1)] = 1\n    return img\nend\n\nfunction shuffleLR(x)\n    local dim\n    if x:nDimension() == 4 then\n        dim = 2\n    else\n        assert(x:nDimension() == 3)\n        dim = 1\n    end\n\n    -- Keypoints pairs for 300W_LP, 300VW, 300W and LS3D-W datasets\n    local matchedParts = {\n\t\t\t{1,17},   {2,16},   {3,15},\n\t\t\t{4,14}, {5,13}, {6,12}, {7,11}, {8,10},\n\t\t\t{18,27},{19,26},{20,25},{21,24},{22,23},\n\t\t\t{37,46},{38,45},{39,44},{40,43},\n\t\t\t{42,47},{41,48},\n\t\t\t{32,36},{33,35},\n\t\t\t{51,53},{50,54},{49,55},{62,64},{61,65},{68,66},{60,56},\n\t\t\t{59,57}\n    }\n\n    for i = 1,#matchedParts do\n        local idx1, idx2 = unpack(matchedParts[i])\n        local tmp = x:narrow(dim, idx1, 1):clone()\n        x:narrow(dim, idx1, 1):copy(x:narrow(dim, idx2, 1))\n        x:narrow(dim, idx2, 1):copy(tmp)\n    end\n\n    return x\nend\n\nfunction flip(x)\n    require 'image'\n    local y = torch.FloatTensor(x:size())\n    for i = 1, x:size(1) do\n        image.hflip(y[i], x[i]:float())\n    end\n    return y:typeAs(x)\nend"
  }
]