From bb4554d6c4631e0a01c4f8eafd0a04bee3e852b0 Mon Sep 17 00:00:00 2001 From: pdollar Date: Thu, 28 Apr 2016 17:37:25 -0700 Subject: [PATCH] LuaAPI/: omg omg (initial commit of MaskApi.lua) --- LuaAPI/MaskApi.lua | 193 ++++++++++++++++++++++++++++++++++++++++ common/libmaskApi.dylib | Bin 0 -> 17724 bytes common/libmaskApi.so | Bin 0 -> 17429 bytes 3 files changed, 193 insertions(+) create mode 100644 LuaAPI/MaskApi.lua create mode 100755 common/libmaskApi.dylib create mode 100755 common/libmaskApi.so diff --git a/LuaAPI/MaskApi.lua b/LuaAPI/MaskApi.lua new file mode 100644 index 00000000..d91e9335 --- /dev/null +++ b/LuaAPI/MaskApi.lua @@ -0,0 +1,193 @@ +--[[---------------------------------------------------------------------------- + +Interface for manipulating masks stored in RLE format. +For more details please see http://mscoco.org/dataset/#download. +More detailed information on RLE can be found in the Matlab MaskApi here: +https://github.com/pdollar/coco/blob/master/MatlabAPI/MaskApi.m. + +The following API functions are defined: + encode - Encode binary masks using RLE. + decode - Decode binary masks encoded via RLE. + merge - Compute union or intersection of encoded masks. + iou - Compute intersection over union between masks. + area - Compute area of encoded masks. + toBbox - Get bounding boxes surrounding encoded masks. + frBbox - Convert bounding boxes to encoded masks. + frPoly - Convert polygon to encoded mask. + +Usage: + maskApi = MaskApi() + Rs = maskApi:encode( masks ) + masks = maskApi:decode( Rs ) + R = maskApi:merge( Rs, [intersect=false] ) + o = maskApi:iou( dt, gt, [iscrowd=false] ) + a = maskApi:area( Rs ) + bbs = maskApi:toBbox( Rs ) + Rs = maskApi:frBbox( bbs, h, w ) + R = maskApi:frPoly( poly, h, w ) + +In the API the following formats are used: + R,Rs - [table] Run-length encoding of binary mask(s) + masks - [nxhxw] Binary mask(s) + bbs - [nx4] Bounding box(es) stored as [x y w h] + poly - Polygon stored as {[x1 y1 x2 y2...],[x1 y1 ...],...} + dt,gt - May be either bounding boxes or encoded masks +Both poly and bbs are 0-indexed (bbox=[0 0 1 1] encloses first pixel). + +To compile use the following (some precompiled binaries are included): + cd coco/common/ + gcc -shared -fPIC -std=c99 -Wall -o libmaskApi.so maskApi.c #LINUX + gcc -dynamiclib -fPIC -std=c99 -Wall -o libmaskApi.dylib maskApi.c #OSX +Please do not contact us for help with compiling. + +Common Object in COntext (COCO) Toolbox. version 3.0 +Data, paper, and tutorials available at: http://mscoco.org/ +Code written by Pedro O. Pinheiro and Piotr Dollar, 2016. +Licensed under the Simplified BSD License [see coco/license.txt] + +------------------------------------------------------------------------------]] + +local ffi = require 'ffi' +local M = {} +local MaskApi = torch.class('MaskApi', M) + +-------------------------------------------------------------------------------- +-- main functions + +function MaskApi:__init() + local d = paths.dirname(paths.thisfile()) + ffi.cdef(assert(io.open(d..'/../common/maskApi.h','r')):read('*all')) + ffi.cdef('void free(void *ptr)') + local ext = ffi.os=='OSX' and 'dylib' or 'so' + self.mask = ffi.load(d..'/../common/libmaskApi.'..ext) +end + +function MaskApi:encode( masks ) + local n, h, w = masks:size(1), masks:size(2), masks:size(3) + masks = masks:type('torch.ByteTensor'):transpose(2,3) + local data = masks:contiguous():data() + local Qs = self:rlesInit(n) + self.mask.rleEncode(Qs[0],data,h,w,n) + return self:rlesToLua(Qs,n) +end + +function MaskApi:decode( Rs ) + local Qs, n, h, w = self:rlesFrLua(Rs) + local masks = torch.ByteTensor(n,w,h):zero():contiguous() + self.mask.rleDecode(Qs,masks:data(),n) + self:rlesFree(Qs,n) + return masks:transpose(2,3) +end + +function MaskApi:merge( Rs, intersect ) + intersect = intersect or 0 + local Qs, n, h, w = self:rlesFrLua(Rs) + local Q = self:rlesInit(1) + self.mask.rleMerge(Qs,Q,n,intersect) + self:rlesFree(Qs,n) + return self:rlesToLua(Q,1) +end + +function MaskApi:iou( dt, gt, iscrowd ) + if not iscrowd then iscrowd = NULL else + iscrowd = iscrowd:type('torch.ByteTensor'):contiguous():data() + end + if torch.isTensor(gt) and torch.isTensor(dt) then + local nDt, k = dt:size(1), dt:size(2); assert(k==4) + local nGt, k = gt:size(1), gt:size(2); assert(k==4) + local dDt = dt:type('torch.DoubleTensor'):contiguous():data() + local dGt = gt:type('torch.DoubleTensor'):contiguous():data() + local o = torch.DoubleTensor(nGt,nDt):contiguous() + self.mask.bbIou(dDt,dGt,nDt,nGt,iscrowd,o:data()) + return o:transpose(1,2) + else + local qDt, nDt = self:rlesFrLua(dt) + local qGt, nGt = self:rlesFrLua(gt) + local o = torch.DoubleTensor(nGt,nDt):contiguous() + self.mask.rleIou(qDt,qGt,nDt,nGt,iscrowd,o:data()) + self:rlesFree(qDt,nDt); self:rlesFree(qGt,nGt) + return o:transpose(1,2) + end +end + +function MaskApi:area( Rs ) + local Qs, n, h, w = self:rlesFrLua(Rs) + local a = torch.IntTensor(n):contiguous() + self.mask.rleArea(Qs,n,a:data()) + self:rlesFree(Qs,n) + return a +end + +function MaskApi:toBbox( Rs ) + local Qs, n, h, w = self:rlesFrLua(Rs) + local bb = torch.DoubleTensor(n,4):contiguous() + self.mask.rleToBbox(Qs,bb:data(),n) + self:rlesFree(Qs,n) + return bb +end + +function MaskApi:frBbox( bbs, h, w ) + local n, k = bbs:size(1), bbs:size(2); assert(k==4) + local data = bbs:type('torch.DoubleTensor'):contiguous():data() + local Qs = self:rlesInit(n) + self.mask.rleFrBbox(Qs[0],data,h,w,n) + return self:rlesToLua(Qs,n) +end + +function MaskApi:frPoly( poly, h, w ) + local n = #poly + local Qs, Q = self:rlesInit(n), self:rlesInit(1) + for i,p in pairs(poly) do + local xy = p:type('torch.DoubleTensor'):contiguous():data() + self.mask.rleFrPoly(Qs[i-1],xy,p:size(1)/2,h,w) + end + self.mask.rleMerge(Qs,Q[0],n,0) + self:rlesFree(Qs,n) + return self:rlesToLua(Q,1) +end + +-------------------------------------------------------------------------------- +-- private helper functions + +function MaskApi:rlesToLua( Qs, n ) + local h, w, Rs = tonumber(Qs[0].h), tonumber(Qs[0].w), {} + for i=1,n do Rs[i]={size={h,w}, counts={}} end + for i=1,n do + local s = self.mask.rleToString(Qs[i-1]) + Rs[i].counts=ffi.string(s) + ffi.C.free(s) + end + self:rlesFree(Qs,n) + return Rs +end + +function MaskApi:rlesFrLua( Rs ) + if #Rs==0 then Rs={Rs} end + local n, h, w = #Rs, Rs[1].size[1], Rs[1].size[2] + local Qs = self:rlesInit(n) + for i=1,n do + local c = Rs[i].counts + if( torch.type(c)=='string' ) then + local s=ffi.new("char[?]",#c+1); ffi.copy(s,c) + self.mask.rleFrString(Qs[i-1],s,h,w) + elseif( torch.type(c)=='torch.IntTensor' ) then + self.mask.rleInit(Qs[i-1],h,w,c:size(1),c:contiguous():data()) + else + assert(false,"invalid RLE") + end + end + return Qs, n, h, w +end + +function MaskApi:rlesInit( n ) + local Qs = ffi.new("RLE[?]",n) + for i=1,n do self.mask.rleInit(Qs[i-1],0,0,0,NULL) end + return Qs +end + +function MaskApi:rlesFree( Qs, n ) + for i=1,n do self.mask.rleFree(Qs[i-1]) end +end + +-------------------------------------------------------------------------------- +return M.MaskApi diff --git a/common/libmaskApi.dylib b/common/libmaskApi.dylib new file mode 100755 index 0000000000000000000000000000000000000000..ef259cad4b944e3fe71784dbd5509373086eb64a GIT binary patch literal 17724 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 Vq