Many fixes, now generates a single image per frame.
[dyncnn.git] / img.lua
diff --git a/img.lua b/img.lua
new file mode 100755 (executable)
index 0000000..afed4e0
--- /dev/null
+++ b/img.lua
@@ -0,0 +1,204 @@
+
+--[[
+
+   dyncnn is a deep-learning algorithm for the prediction of
+   interacting object dynamics
+
+   Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
+   Written by Francois Fleuret <francois.fleuret@idiap.ch>
+
+   This file is part of dyncnn.
+
+   dyncnn is free software: you can redistribute it and/or modify it
+   under the terms of the GNU General Public License version 3 as
+   published by the Free Software Foundation.
+
+   dyncnn is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with dyncnn.  If not, see <http://www.gnu.org/licenses/>.
+
+]]--
+
+require 'torch'
+
+--[[
+
+The combineImage function takes as input a parameter c which is the
+value to use for the background of the resulting image (padding and
+such), and t which is either a 2d tensor, a 3d tensor, or a table.
+
+ * If t is a 3d tensor, it is returned unchanged.
+
+ * If t is a 2d tensor [r x c], it is reshaped to [1 x r x c] and
+   returned.
+
+ * If t is a table, combineImage first calls itself recursively on
+   t[1], t[2], etc.
+
+   It then creates a new tensor by concatenating the results
+   horizontally if t.vertical is nil, vertically otherwise.
+
+   It adds a padding of t.pad pixels if this field is set.
+
+ * Example
+
+   x = torch.Tensor(64, 64):fill(0.5)
+   y = torch.Tensor(100, 30):fill(0.85)
+
+   i = combineImages(1.0,
+      {
+         pad = 1,
+         vertical = true,
+         { pad = 1, x },
+         {
+            y,
+            { pad = 4, torch.Tensor(32, 16):fill(0.25) },
+            { pad = 1, torch.Tensor(45, 54):uniform(0.25, 0.9) },
+         }
+      }
+   )
+
+   image.save('example.png', i)
+
+]]--
+
+function combineImages(c, t)
+
+   if torch.isTensor(t) then
+
+      if t:dim() == 3 then
+         return t
+      elseif t:dim() == 2 then
+         return torch.Tensor(1, t:size(1), t:size(2)):copy(t)
+      else
+         error('can only deal with [height x width] or [channel x height x width] tensors.')
+      end
+
+   else
+
+      local subImages = {} -- The subimages
+      local nc = 0 -- Nb of columns
+      local nr = 0 -- Nb of rows
+
+      for i, x in ipairs(t) do
+         subImages[i] = combineImages(c, x)
+         if t.vertical then
+            nr = nr + subImages[i]:size(2)
+            nc = math.max(nc, subImages[i]:size(3))
+         else
+            nr = math.max(nr, subImages[i]:size(2))
+            nc = nc + subImages[i]:size(3)
+         end
+      end
+
+      local pad = t.pad or 0
+      local result = torch.Tensor(subImages[1]:size(1), nr + 2 * pad, nc + 2 * pad):fill(c)
+      local co = 1 + pad -- Origin column
+      local ro = 1 + pad -- Origin row
+
+      for i in ipairs(t) do
+
+         result:sub(1, subImages[1]:size(1),
+                    ro, ro + subImages[i]:size(2) - 1,
+                    co, co + subImages[i]:size(3) - 1):copy(subImages[i])
+
+         if t.vertical then
+            ro = ro + subImages[i]:size(2)
+         else
+            co = co + subImages[i]:size(3)
+         end
+
+      end
+
+      return result
+
+   end
+
+end
+
+--[[
+
+The imageFromTensors function gets as input a list of tensors of
+arbitrary dimensions each, but whose two last dimensions stand for
+height x width. It creates an image tensor (2d, one channel) with each
+argument tensor unfolded per row.
+
+]]--
+
+function imageFromTensors(bt, signed)
+   local gap = 1
+   local tgap = -1
+   local width = 0
+   local height = gap
+
+   for _, t in pairs(bt) do
+      local d = t:dim()
+      local h, w = t:size(d - 1), t:size(d)
+      local n = t:nElement() / (w * h)
+      width = math.max(width, gap + n * (gap + w))
+      height = height + gap + tgap + gap + h
+   end
+
+   local e = torch.Tensor(3, height, width):fill(1.0)
+   local y0 = 1 + gap
+
+   for _, t in pairs(bt) do
+      local d = t:dim()
+      local h, w = t:size(d - 1), t:size(d)
+      local n = t:nElement() / (w * h)
+      local z = t:norm() / math.sqrt(t:nElement())
+
+      local x0 = 1 + gap + math.floor( (width - n * (w + gap)) /2 )
+      local u = torch.Tensor(t:size()):copy(t):resize(n, h, w)
+      for m = 1, n do
+
+         for c = 1, 3 do
+            for y = 0, h+1 do
+               e[c][y0 + y - 1][x0     - 1] = 0.0
+               e[c][y0 + y - 1][x0 + w    ] = 0.0
+            end
+            for x = 0, w+1 do
+               e[c][y0     - 1][x0 + x - 1] = 0.0
+               e[c][y0 + h    ][x0 + x - 1] = 0.0
+            end
+         end
+
+         for y = 1, h do
+            for x = 1, w do
+               local v = u[m][y][x] / z
+               local r, g, b
+               if signed then
+                  if v < -1 then
+                     r, g, b = 0.0, 0.0, 1.0
+                  elseif v > 1 then
+                     r, g, b = 1.0, 0.0, 0.0
+                  elseif v >= 0 then
+                     r, g, b = 1.0, 1.0 - v, 1.0 - v
+                  else
+                     r, g, b = 1.0 + v, 1.0 + v, 1.0
+                  end
+               else
+                  if v <= 0 then
+                     r, g, b = 1.0, 1.0, 1.0
+                  elseif v > 1 then
+                     r, g, b = 0.0, 0.0, 0.0
+                  else
+                     r, g, b = 1.0 - v, 1.0 - v, 1.0 - v
+                  end
+               end
+               e[1][y0 + y - 1][x0 + x - 1] = r
+               e[2][y0 + y - 1][x0 + x - 1] = g
+               e[3][y0 + y - 1][x0 + x - 1] = b
+            end
+         end
+         x0 = x0 + w + gap
+      end
+      y0 = y0 + h + gap + tgap + gap
+   end
+
+   return e
+end