├── .gitignore ├── .luacheckrc ├── CMakeLists.txt ├── CONTRIBUTING.md ├── IOHandler.lua ├── LICENSE ├── PATENTS ├── Plot.lua ├── README.md ├── _env.lua ├── bokeh.lua ├── completer.lua ├── custom.css ├── custom.js ├── gfx.lua ├── iTorch_Demo.ipynb ├── init.lua ├── ipynblogo.png ├── itorch ├── itorch-scm-1.rockspec ├── itorch_launcher ├── kernelspec ├── kernel.json └── logo-64x64.png ├── main.lua ├── screenshots ├── audio.png ├── autocomplete.png ├── filters.png ├── help.png ├── hist.png ├── html.png ├── image.png ├── quiver.png ├── scatter.png └── video.png ├── small.mp4 ├── static ├── bokeh-0.7.0.min.css └── bokeh-0.7.0.min.js ├── test.lua ├── util.lua └── volkswagen.mp3 /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *~ -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | -- -*- mode: lua; -*- 2 | std = "luajit" 3 | 4 | globals = { 5 | "torch", 6 | "include", 7 | "image", 8 | "ifx", 9 | "itorch", 10 | "jit", 11 | } 12 | 13 | files["init.lua"].redefined = false 14 | files["test.lua"].redefined = false 15 | unused_args = false 16 | allow_defined = true 17 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 2.6 FATAL_ERROR) 2 | CMAKE_POLICY(VERSION 2.6) 3 | IF(LUAROCKS_PREFIX) 4 | MESSAGE(STATUS "Installing iTorch through Luarocks") 5 | STRING(REGEX REPLACE "(.*)lib/luarocks/rocks.*" "\\1" CMAKE_INSTALL_PREFIX "${LUAROCKS_PREFIX}") 6 | MESSAGE(STATUS "Prefix inferred from Luarocks: ${CMAKE_INSTALL_PREFIX}") 7 | ENDIF() 8 | FIND_PACKAGE(Torch REQUIRED) 9 | 10 | FILE(GLOB luasrc *.lua static/jquery-1.11.2.min.js static/bokeh-0.7.0.min.js static/bokeh-0.7.0.min.css) 11 | SET(src "") 12 | ADD_TORCH_PACKAGE(itorch "${src}" "${luasrc}" "iTorch") 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to iTorch 2 | We want to make contributing to this project as easy and transparent as 3 | possible. 4 | 5 | ## Our Development Process 6 | iTorch is not big enough that we have a standard development process. 7 | We follow the [Torch-7](https://github.com/torch/torch7/blob/master/CONTRIBUTING.md) development process loosely. 8 | 9 | ## Pull Requests 10 | We actively welcome your pull requests. 11 | 12 | 1. Fork the repo and create your branch from `master`. 13 | 2. If you've added code that should be tested, add tests 14 | 3. If you've changed APIs, update the documentation. 15 | 4. Ensure the test suite passes. 16 | 5. Make sure your code lints. 17 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 18 | 19 | ## Contributor License Agreement ("CLA") 20 | In order to accept your pull request, we need you to submit a CLA. You only need 21 | to do this once to work on any of Facebook's open source projects. 22 | 23 | Complete your CLA here: 24 | 25 | ## Issues 26 | We use GitHub issues to track public bugs. Please ensure your description is 27 | clear and has sufficient instructions to be able to reproduce the issue. 28 | 29 | ## Coding Style 30 | * 2 spaces for indentation rather than tabs 31 | * 80 character line length 32 | 33 | ## License 34 | By contributing to iTorch, you agree that your contributions will be licensed 35 | under its BSD license. -------------------------------------------------------------------------------- /IOHandler.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | ]]-- 10 | require 'torch' 11 | require 'env' 12 | local zmq = require 'lzmq' 13 | local zloop = require 'lzmq.loop' 14 | local zassert = zmq.assert 15 | local json=require 'cjson' 16 | local uuid = require 'uuid' 17 | local ffi = require 'ffi' 18 | local util = require 'itorch.util' 19 | local context = zmq.context() 20 | local tablex = require 'pl.tablex' 21 | local kvstore = {} -- this stores exclusive key-values passed in by main.lua 22 | ------------------------------------------ 23 | -- load and decode json config 24 | local ipyfile = assert(io.open(arg[1], "rb"), "Could not open iPython config") 25 | local ipyjson = ipyfile:read("*all") 26 | ipyfile:close() 27 | local ipycfg = json.decode(ipyjson) 28 | -------------------------------------------------------------- 29 | -- set session key 30 | util.setSessionKey(ipycfg.key) 31 | -------------------------------------------------------------- 32 | --- The libc functions used by this process (for non-blocking IO) 33 | ffi.cdef[[ 34 | int open(const char* pathname, int flags); 35 | int close(int fd); 36 | int read(int fd, void* buf, size_t count); 37 | ]] 38 | local O_NONBLOCK = 0x0004 39 | local chunk_size = 4096 40 | local buffer = ffi.new('uint8_t[?]',chunk_size) 41 | local io_stdo = ffi.C.open(arg[2], O_NONBLOCK) 42 | 43 | local ip = ipycfg.transport .. '://' .. ipycfg.ip .. ':' 44 | local heartbeat, err = context:socket{zmq.REP, bind = ip .. ipycfg.hb_port} 45 | zassert(heartbeat, err) 46 | local iopub, err = context:socket{zmq.PUB, bind = ip .. ipycfg.iopub_port} 47 | zassert(iopub, err) 48 | ------------------------------------------------------------------------------ 49 | local rawpub_port=0 50 | do 51 | -- wait till the main process writes the port, and the use it 52 | while rawpub_port == 0 do 53 | local portnum_f = torch.DiskFile(arg[3],'r') 54 | portnum_f:quiet() 55 | rawpub_port = portnum_f:readInt() 56 | if portnum_f:hasError() then rawpub_port = 0 end 57 | portnum_f:close() 58 | end 59 | end 60 | local rawpub, err = context:socket{zmq.PAIR, connect = ip .. rawpub_port} 61 | zassert(rawpub, err) 62 | ------------------------------------------------------------------------------ 63 | local function handleHeartbeat(sock) 64 | local m = zassert(sock:recv_all()); 65 | zassert(sock:send_all(m)) 66 | end 67 | 68 | function handleSTDO(ev) 69 | local nbytes = ffi.C.read(io_stdo,buffer,chunk_size) 70 | if nbytes > 0 then 71 | local output = ffi.string(buffer, nbytes) 72 | if kvstore.current_msg then 73 | local o = util.msg('pyout', kvstore.current_msg) 74 | o.content = { 75 | data = {}, 76 | metadata = {}, 77 | execution_count = kvstore.exec_count 78 | } 79 | o.content.data['text/plain'] = output 80 | util.ipyEncodeAndSend(iopub, o) 81 | else 82 | print(output) 83 | end 84 | end 85 | ev:set_interval(1) 86 | end 87 | 88 | function handleRawPub(sock) 89 | local m = zassert(sock:recv_all()) 90 | -- if this message is a key-value from main.lua 91 | if m[1] == 'private_msg' then 92 | if m[2] == 'current_msg' then 93 | kvstore[m[2]] = json.decode(m[3]) 94 | elseif m[2] == 'exec_count' then 95 | kvstore[m[2]] = tonumber(m[3]) 96 | elseif m[2] == 'shutdown' then 97 | sock:send('ACK') 98 | loop:stop() 99 | iopub:close() 100 | rawpub:close() 101 | heartbeat:close() 102 | ffi.C.close(io_stdo) 103 | os.execute('rm -f ' .. arg[2]) -- cleanup files 104 | os.execute('rm -f ' .. arg[3]) 105 | print('Shutting down iTorch') 106 | os.exit() 107 | end 108 | sock:send('ACK') 109 | return 110 | end 111 | -- else, just pass it over to iopub 112 | zassert(iopub:send_all(m)) 113 | end 114 | 115 | local function handleIOPub(sock) 116 | print('Error: IOPub is a Publisher, it cant have incoming requests') 117 | end 118 | 119 | 120 | loop = zloop.new(1, context) 121 | loop:add_socket(heartbeat, handleHeartbeat) 122 | loop:add_socket(rawpub, handleRawPub) 123 | loop:add_socket(iopub, handleIOPub) 124 | loop:add_interval(1, handleSTDO) 125 | loop:start() 126 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For iTorch software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the iTorch software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Facebook’s rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Facebook or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Facebook or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Facebook or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Facebook that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. -------------------------------------------------------------------------------- /Plot.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | ]]-- 10 | local uuid = require 'uuid' 11 | local json = require 'cjson' 12 | require 'pl.text'.format_operator() 13 | local Plot = {} 14 | 15 | setmetatable(Plot, { 16 | __call = function(self,...) 17 | return self.new(...) 18 | end 19 | }); 20 | 21 | -- constructor 22 | function Plot.new() 23 | local plot = {} 24 | for k,v in pairs(Plot) do plot[k] = v end 25 | return plot 26 | end 27 | 28 | --[[ 29 | Bare essential functions needed: 30 | data (x,y,color,legend,marker) 31 | title 32 | scale 33 | draw 34 | redraw 35 | tohtml 36 | ]]-- 37 | 38 | local function tensorValidate(x) 39 | assert(x and (torch.isTensor(x) and x:dim() == 1) 40 | or (torch.type(x) == 'table'), 41 | 'input needs to be a 1D tensor/table of N elements') 42 | if torch.type(x) == 'table' then 43 | x = torch.DoubleTensor(x) 44 | elseif torch.isTensor(x) then 45 | x = torch.DoubleTensor(x:size()):copy(x) 46 | end 47 | return x 48 | end 49 | 50 | local function _simpleGlyph(name) 51 | return function(self, x, y, color, legend, arbitrary_data) -- TODO: marker 52 | --[[ [optional] color is one of: 53 | red,blue,green or an html color string (like #FF8932) 54 | color can either be a single value, 55 | or N values (one value per (x,y) point) 56 | if no color is specified, it is defaulted to red for all points. 57 | TODO do color argcheck 58 | ]]-- 59 | color = color or 'red' 60 | legend = legend or 'unnamed' 61 | arbitrary_data = arbitrary_data or {} 62 | 63 | -- x and y are [a 1D tensor of N elements or a table of N elements] 64 | x = tensorValidate(x) 65 | y = tensorValidate(y) 66 | -- check if x and y are same number of elements 67 | assert(x:nElement() == y:nElement(), 68 | 'x and y have to have same number of elements') 69 | assert(type(color) == 'string' or type(color) == 'table', 70 | 'color must be a string or a table') 71 | assert(type(arbitrary_data) == 'table', 'arbitrary_data must be a table') 72 | 73 | self._data = self._data or {} 74 | local _d = {} 75 | _d.type = name 76 | _d.x = x 77 | _d.y = y 78 | _d.fill_color = color 79 | _d.line_color = color 80 | _d.legend = legend 81 | for k, v in pairs(arbitrary_data) do 82 | _d[k] = v 83 | end 84 | table.insert(self._data, _d) 85 | return self 86 | end 87 | end 88 | 89 | Plot.circle = _simpleGlyph('Circle') 90 | Plot.line = _simpleGlyph('Line') 91 | Plot.triangle = _simpleGlyph('Triangle') 92 | 93 | function Plot:text(x, y, text, angle, 94 | text_color, text_font_size, text_align, text_baseline) 95 | self._data = self._data or {} 96 | local _d = {} 97 | _d.type = 'Text' 98 | _d.x = x 99 | _d.y = y 100 | _d.text = text 101 | if angle then _d.angle = angle end 102 | if text_color then _d.text_color = text_color end 103 | if text_font_size then _d.text_font_size = text_font_size end 104 | if text_align then _d.text_align = text_align end 105 | if text_baseline then _d.text_baseline = text_baseline end 106 | table.insert(self._data, _d) 107 | return self 108 | end 109 | 110 | function Plot:gscatter(x,y,labels,legend) 111 | -- defaults and input checking: 112 | local legend = legend 113 | if legend == nil then legend = true end 114 | if labels == nil then legend = false end 115 | local x = tensorValidate(x) 116 | local y = tensorValidate(y) 117 | local labels = labels or torch.LongTensor(x:nElement()):zero() 118 | labels = tensorValidate(labels) 119 | assert(x:nElement() == y:nElement()) 120 | assert(x:nElement() == labels:nElement()) 121 | 122 | -- make list of labels: 123 | local numClasses = 0 124 | local classes = {} 125 | for n = 1,labels:nElement() do 126 | if classes[ labels[n] ] == nil then 127 | numClasses = numClasses + 1 128 | classes[ labels[n] ] = numClasses 129 | end 130 | end 131 | 132 | -- redo labeling: 133 | local classNames = {} 134 | local normLabels = torch.LongTensor(labels:nElement()) 135 | for n = 1,labels:nElement() do 136 | normLabels[n] = classes[ labels[n] ] 137 | classNames[ normLabels[n] ] = labels[n] 138 | end 139 | 140 | -- plot each class separately: 141 | local colors = {'red', 'blue', 'green', 'yellow', 'black', 'brown', 'orange', 142 | 'pink', 'magenta', 'purple'} 143 | for k = 1,numClasses do 144 | self:circle(x[torch.eq(normLabels, k)], y[torch.eq(normLabels, k)], 145 | colors[1 + (k - 1) % #colors], 'Class ' .. classNames[k]) 146 | end 147 | self:legend(legend) 148 | return self 149 | end 150 | 151 | function Plot:segment(x0,y0,x1,y1,color,legend) 152 | -- x and y are [a 1D tensor of N elements or a table of N elements] 153 | x0 = tensorValidate(x0) 154 | x1 = tensorValidate(x1) 155 | y0 = tensorValidate(y0) 156 | y1 = tensorValidate(y1) 157 | -- check if x and y are same number of elements 158 | assert(x0:nElement() == y0:nElement(), 159 | 'x0 and y0 should have same number of elements') 160 | assert(x0:nElement() == x1:nElement(), 161 | 'x0 and x1 should have same number of elements') 162 | assert(x0:nElement() == y1:nElement(), 163 | 'x0 and y1 should have same number of elements') 164 | 165 | color = color or 'red' 166 | legend = legend or 'unnamed' 167 | 168 | self._data = self._data or {} 169 | local _d = {} 170 | _d.type = 'Segment' 171 | _d.x0 = x0 172 | _d.y0 = y0 173 | _d.x1 = x1 174 | _d.y1 = y1 175 | _d.fill_color = color 176 | _d.line_color = color 177 | _d.legend = legend 178 | table.insert(self._data, _d) 179 | return self 180 | end 181 | 182 | function Plot:quiver(U,V,color,legend,scaling) 183 | assert(U:dim() == 2 and V:dim() == 2 184 | and U:size(1) == V:size(1) and U:size(2) == V:size(2), 185 | 'U and V should be 2D and of same size') 186 | local xx = torch.linspace(1,U:size(1), U:size(1)):typeAs(U) 187 | local yy = torch.linspace(1,U:size(2), U:size(2)):typeAs(V) 188 | local function meshgrid(x,y) 189 | local xx = torch.repeatTensor(x, y:size(1),1) 190 | local yy = torch.repeatTensor(y:view(-1,1), 1, x:size(1)) 191 | return xx, yy 192 | end 193 | local Y, X = meshgrid(xx, yy) 194 | X = X:view(-1) 195 | Y = Y:view(-1) 196 | U = U:view(-1) 197 | V = V:view(-1) 198 | scaling = scaling or 40 199 | U = U / scaling 200 | V = V / scaling 201 | local x0 = X 202 | local y0 = Y 203 | local x1 = X + U 204 | local y1 = Y + V 205 | self:segment(x0, y0, x1,y1, color,legend) 206 | ------------------------------------------------------------------ 207 | -- calculate and plot arrow-head 208 | local ll = (x1 - x0) 209 | local ll2 = (y1 - y0) 210 | local len = torch.sqrt(torch.cmul(ll,ll) + torch.cmul(ll2,ll2)) 211 | local h = len / 10 -- arrow length 212 | local w = len / 20 -- arrow width 213 | local Ux = torch.cdiv(ll,len) 214 | local Uy = torch.cdiv(ll2,len) 215 | -- zero the nans in Ux and Uy 216 | Ux[Ux:ne(Ux)] = 0 217 | Uy[Uy:ne(Uy)] = 0 218 | local Vx = -Uy 219 | local Vy = Ux 220 | local v1x = x1 - torch.cmul(Ux,h) + torch.cmul(Vx,w); 221 | local v1y = y1 - torch.cmul(Uy,h) + torch.cmul(Vy,w); 222 | 223 | local v2x = x1 - torch.cmul(Ux,h) - torch.cmul(Vx,w); 224 | local v2y = y1 - torch.cmul(Uy,h) - torch.cmul(Vy,w); 225 | self:segment(v1x,v1y,v2x,v2y,color) 226 | self:segment(v1x,v1y,x1,y1,color) 227 | self:segment(v2x,v2y,x1,y1,color) 228 | return self 229 | end 230 | 231 | function Plot:quad(x0,y0,x1,y1,color,legend) 232 | -- x and y are [a 1D tensor of N elements or a table of N elements] 233 | x0 = tensorValidate(x0) 234 | x1 = tensorValidate(x1) 235 | y0 = tensorValidate(y0) 236 | y1 = tensorValidate(y1) 237 | -- check if x and y are same number of elements 238 | assert(x0:nElement() == y0:nElement(), 239 | 'x0 and y0 should have same number of elements') 240 | assert(x0:nElement() == x1:nElement(), 241 | 'x0 and x1 should have same number of elements') 242 | assert(x0:nElement() == y1:nElement(), 243 | 'x0 and y1 should have same number of elements') 244 | 245 | color = color or 'red' 246 | legend = legend or 'unnamed' 247 | 248 | self._data = self._data or {} 249 | local _d = {} 250 | _d.type = 'Quad' 251 | _d.x0 = x0 252 | _d.y0 = y0 253 | _d.x1 = x1 254 | _d.y1 = y1 255 | _d.fill_color = color 256 | _d.line_color = color 257 | _d.legend = legend 258 | table.insert(self._data, _d) 259 | return self 260 | end 261 | 262 | function Plot:histogram(x, nBins, min, max, color, legend) 263 | min = min or x:min() 264 | max = max or x:max() 265 | nBins = nBins or 100 266 | if min ~= min or max ~= max then -- nans 267 | print('input has nans, please remove nans. min: ' .. min .. ' max: ' .. max) 268 | return 269 | end 270 | 271 | local hist = torch.histc(x, nBins, min, max) 272 | nBins = hist:size(1) 273 | local x0 = torch.linspace(min, max, nBins) 274 | local x1 = x0 + (max-min)/nBins 275 | self:quad(x0, torch.zeros(nBins), x1, hist, color, legend) 276 | return self 277 | end 278 | 279 | Plot.hist = Plot.histogram 280 | 281 | function Plot:title(t) 282 | if t then self._title = t end 283 | return self 284 | end 285 | 286 | function Plot:xaxis(t) 287 | if t then self._xaxis = t end 288 | return self 289 | end 290 | function Plot:yaxis(t) 291 | if t then self._yaxis = t end 292 | return self 293 | end 294 | 295 | function Plot:legend(bool) 296 | if bool then self._legend = bool end 297 | return self 298 | end 299 | 300 | function Plot:hover_tool(tooltips) 301 | assert(type(tooltips) == 'table', 'tooltips must be a table') 302 | for i, x in ipairs(tooltips) do 303 | assert(type(x) == 'table', 'tooltips must be a table of table') 304 | end 305 | 306 | local _d = {} 307 | _d.type = 'HoverTool' 308 | _d.tooltips = tooltips 309 | 310 | self._tools = self._tools or {} 311 | table.insert(self._tools, _d) 312 | return self 313 | end 314 | 315 | -- merges multiple tables into one 316 | local function combineTable(x) 317 | local y = {} 318 | for i=1,#x do 319 | local xx = x[i] 320 | local limit 321 | if torch.isTensor(xx) then 322 | limit = xx:size(1) 323 | else 324 | limit = #xx 325 | end 326 | for j=1,limit do 327 | table.insert(y, xx[j]) 328 | end 329 | end 330 | return y 331 | end 332 | 333 | local function newElem(name, docid) 334 | local c = {} 335 | c.id = uuid.new() 336 | c.type = name 337 | c.attributes = {} 338 | c.attributes.id = c.id 339 | c.attributes.doc = docid 340 | c.attributes.tags = {} 341 | return c 342 | end 343 | 344 | local function set_attr_from_data(attributes, data, k, expected_type) 345 | expected_type = expected_type or 'string' 346 | local new_attr = {} 347 | if type(data[k]) == expected_type then 348 | new_attr.value = data[k] 349 | else 350 | new_attr.units = 'data' 351 | new_attr.field = k 352 | end 353 | attributes[k] = new_attr 354 | end 355 | 356 | local createGlyph = {} 357 | local function createSimpleGlyph(docid, data, name) 358 | local glyph = newElem(name, docid) 359 | glyph.attributes.x = {} 360 | glyph.attributes.x.units = 'data' 361 | glyph.attributes.x.field = 'x' 362 | glyph.attributes.y = {} 363 | glyph.attributes.y.units = 'data' 364 | glyph.attributes.y.field = 'y' 365 | set_attr_from_data(glyph.attributes, data, 'line_color') 366 | glyph.attributes.line_alpha = {} 367 | glyph.attributes.line_alpha.units = 'data' 368 | glyph.attributes.line_alpha.value = 1.0 369 | set_attr_from_data(glyph.attributes, data, 'fill_color') 370 | glyph.attributes.fill_alpha = {} 371 | glyph.attributes.fill_alpha.units = 'data' 372 | glyph.attributes.fill_alpha.value = 0.2 373 | 374 | glyph.attributes.size = {} 375 | glyph.attributes.size.units = 'screen' 376 | glyph.attributes.size.value = 10 377 | glyph.attributes.tags = {} 378 | return glyph 379 | end 380 | createGlyph['Circle'] = function(docid, data) 381 | return createSimpleGlyph(docid, data, 'Circle') 382 | end 383 | 384 | createGlyph['Line'] = function(docid, data) 385 | return createSimpleGlyph(docid, data, 'Line') 386 | end 387 | 388 | createGlyph['Triangle'] = function(docid, data) 389 | return createSimpleGlyph(docid, data, 'Triangle') 390 | end 391 | 392 | createGlyph['Text'] = function(docid, data) 393 | local glyph = newElem('Text', docid) 394 | set_attr_from_data(glyph.attributes, data, 'x', 'number') 395 | set_attr_from_data(glyph.attributes, data, 'y', 'number') 396 | set_attr_from_data(glyph.attributes, data, 'text') 397 | set_attr_from_data(glyph.attributes, data, 'text_color') 398 | set_attr_from_data(glyph.attributes, data, 'text_font_size') 399 | set_attr_from_data(glyph.attributes, data, 'text_align') 400 | set_attr_from_data(glyph.attributes, data, 'text_baseline') 401 | set_attr_from_data(glyph.attributes, data, 'angle', 'number') 402 | 403 | return glyph 404 | end 405 | 406 | local function addunit(t,f,f2) 407 | f2 = f2 or f 408 | t[f] = {} 409 | t[f].units = 'data' 410 | t[f].field = f2 411 | end 412 | 413 | createGlyph['Segment'] = function(docid, data) 414 | local glyph = newElem('Segment', docid) 415 | addunit(glyph.attributes, 'x0') 416 | addunit(glyph.attributes, 'x1') 417 | addunit(glyph.attributes, 'y0') 418 | addunit(glyph.attributes, 'y1') 419 | set_attr_from_data(glyph.attributes, data, 'line_color') 420 | glyph.attributes.line_alpha = {} 421 | glyph.attributes.line_alpha.units = 'data' 422 | glyph.attributes.line_alpha.value = 1.0 423 | 424 | glyph.attributes.line_width = {} 425 | glyph.attributes.line_width.units = 'data' 426 | glyph.attributes.line_width.value = 2 427 | 428 | glyph.attributes.size = {} 429 | glyph.attributes.size.units = 'screen' 430 | glyph.attributes.size.value = 10 431 | glyph.attributes.tags = {} 432 | return glyph 433 | end 434 | 435 | createGlyph['Quad'] = function(docid, data) 436 | local glyph = newElem('Quad', docid) 437 | addunit(glyph.attributes, 'left', 'x0') 438 | addunit(glyph.attributes, 'right', 'x1') 439 | addunit(glyph.attributes, 'bottom', 'y0') 440 | addunit(glyph.attributes, 'top', 'y1') 441 | set_attr_from_data(glyph.attributes, data, 'line_color') 442 | glyph.attributes.line_alpha = {} 443 | glyph.attributes.line_alpha.units = 'data' 444 | glyph.attributes.line_alpha.value = 1.0 445 | 446 | set_attr_from_data(glyph.attributes, data, 'fill_color') 447 | glyph.attributes.fill_alpha = {} 448 | glyph.attributes.fill_alpha.units = 'data' 449 | glyph.attributes.fill_alpha.value = 0.7 450 | 451 | glyph.attributes.tags = {} 452 | return glyph 453 | end 454 | 455 | local function createDataRange1d(docid, cds, col) 456 | local drx = newElem('DataRange1d', docid) 457 | drx.attributes.sources = {} 458 | for i=1,#cds do 459 | drx.attributes.sources[i] = {} 460 | drx.attributes.sources[i].source = {} 461 | drx.attributes.sources[i].source.id = cds[i].id 462 | drx.attributes.sources[i].source.type = cds[i].type 463 | local c = cds[i] 464 | drx.attributes.sources[i].columns = {} 465 | for k,cname in ipairs(c.attributes.column_names) do 466 | if cname:sub(1,1) == col then 467 | table.insert(drx.attributes.sources[i].columns, cname) 468 | end 469 | end 470 | end 471 | return drx 472 | end 473 | 474 | local function createLinearAxis(docid, plotid, axis_label, tfid, btid) 475 | local linearAxis1 = newElem('LinearAxis', docid) 476 | linearAxis1.attributes.plot = {} 477 | linearAxis1.attributes.plot.subtype = 'Figure' 478 | linearAxis1.attributes.plot.type = 'Plot' 479 | linearAxis1.attributes.plot.id = plotid 480 | linearAxis1.attributes.axis_label = axis_label 481 | linearAxis1.attributes.formatter = {} 482 | linearAxis1.attributes.formatter.type = 'BasicTickFormatter' 483 | linearAxis1.attributes.formatter.id = tfid 484 | linearAxis1.attributes.ticker = {} 485 | linearAxis1.attributes.ticker.type = 'BasicTicker' 486 | linearAxis1.attributes.ticker.id = btid 487 | return linearAxis1 488 | end 489 | 490 | local function createGrid(docid, plotid, dimension, btid) 491 | local grid1 = newElem('Grid', docid) 492 | grid1.attributes.plot = {} 493 | grid1.attributes.plot.subtype = 'Figure' 494 | grid1.attributes.plot.type = 'Plot' 495 | grid1.attributes.plot.id = plotid 496 | grid1.attributes.dimension = dimension 497 | grid1.attributes.ticker = {} 498 | grid1.attributes.ticker.type = 'BasicTicker' 499 | grid1.attributes.ticker.id = btid 500 | return grid1 501 | end 502 | 503 | local function createTool(docid, name, plotid, dimensions) 504 | local t = newElem(name, docid) 505 | t.attributes.plot = {} 506 | t.attributes.plot.subtype = 'Figure' 507 | t.attributes.plot.type = 'Plot' 508 | t.attributes.plot.id = plotid 509 | if dimensions then t.attributes.dimensions = dimensions end 510 | return t 511 | end 512 | 513 | local function createLegend(docid, plotid, data, grs) 514 | local l = newElem('Legend', docid) 515 | l.attributes.plot = {} 516 | l.attributes.plot.subtype = 'Figure' 517 | l.attributes.plot.type = 'Plot' 518 | l.attributes.plot.id = plotid 519 | l.attributes.legends = {} 520 | local index = 1 521 | for i=1,#data do 522 | if data[i].legend ~= nil then 523 | l.attributes.legends[index] = {} 524 | l.attributes.legends[index][1] = data[i].legend 525 | l.attributes.legends[index][2] = {{}} 526 | l.attributes.legends[index][2][1].type = 'GlyphRenderer' 527 | l.attributes.legends[index][2][1].id = grs[i].id 528 | index = index + 1 529 | end 530 | end 531 | return l 532 | end 533 | 534 | local function createColumnDataSource(docid, data) 535 | local cds = newElem('ColumnDataSource', docid) 536 | cds.attributes.selected = {} 537 | cds.attributes.cont_ranges = {} 538 | cds.attributes.discrete_ranges = {} 539 | cds.attributes.column_names = {} 540 | cds.attributes.data = {} 541 | for k,v in pairs(data) do 542 | if k ~= 'legend' and k ~= 'type' and type(v) ~= 'string' then 543 | table.insert(cds.attributes.column_names, k) 544 | if torch.isTensor(v) then v = v:contiguous():storage():totable() end 545 | cds.attributes.data[k] = v 546 | end 547 | end 548 | return cds 549 | end 550 | 551 | 552 | function Plot:_toAllModels() 553 | self._docid = json.null -- self._docid or uuid.new() 554 | local all_models = {} 555 | 556 | local plot = newElem('Plot', self._docid) 557 | local renderers = {} 558 | 559 | local cdss = {} 560 | local grs = {} 561 | for i=1,#self._data do 562 | local d = self._data[i] 563 | local gltype = d.type 564 | 565 | -- convert data to ColumnDataSource 566 | local cds = createColumnDataSource(self._docid, d) 567 | table.insert(all_models, cds) 568 | cdss[#cdss+1] = cds 569 | 570 | -- create Glyph 571 | local sglyph = createGlyph[gltype](self._docid, d) 572 | local nsglyph = createGlyph[gltype](self._docid, d) 573 | table.insert(all_models, sglyph) 574 | table.insert(all_models, nsglyph) 575 | 576 | -- GlyphRenderer 577 | local gr = newElem('GlyphRenderer', self._docid) 578 | gr.attributes.nonselection_glyph = {} 579 | gr.attributes.nonselection_glyph.type = gltype 580 | gr.attributes.nonselection_glyph.id = nsglyph.id 581 | gr.attributes.data_source = {} 582 | gr.attributes.data_source.type = 'ColumnDataSource' 583 | gr.attributes.data_source.id = cds.id 584 | gr.attributes.name = json.null 585 | gr.attributes.server_data_source = json.null 586 | gr.attributes.selection_glyph = json.null 587 | gr.attributes.glyph = {} 588 | gr.attributes.glyph.type = gltype 589 | gr.attributes.glyph.id = sglyph.id 590 | renderers[#renderers+1] = gr 591 | table.insert(all_models, gr) 592 | grs[#grs+1] = gr 593 | end 594 | 595 | -- create DataRange1d for x and y 596 | local drx = createDataRange1d(self._docid, cdss, 'x') 597 | local dry = createDataRange1d(self._docid, cdss, 'y') 598 | table.insert(all_models, drx) 599 | table.insert(all_models, dry) 600 | 601 | -- ToolEvents 602 | local toolEvents = newElem('ToolEvents', self._docid) 603 | toolEvents.attributes.geometries = {} 604 | table.insert(all_models, toolEvents) 605 | 606 | local tf1 = newElem('BasicTickFormatter', self._docid) 607 | local bt1 = newElem('BasicTicker', self._docid) 608 | bt1.attributes.num_minor_ticks = 5 609 | local linearAxis1 = createLinearAxis(self._docid, plot.id, 610 | self._xaxis or json.null, tf1.id, bt1.id) 611 | renderers[#renderers+1] = linearAxis1 612 | local grid1 = createGrid(self._docid, plot.id, 0, bt1.id) 613 | renderers[#renderers+1] = grid1 614 | table.insert(all_models, tf1) 615 | table.insert(all_models, bt1) 616 | table.insert(all_models, linearAxis1) 617 | table.insert(all_models, grid1) 618 | 619 | local tf2 = newElem('BasicTickFormatter', self._docid) 620 | local bt2 = newElem('BasicTicker', self._docid) 621 | bt2.attributes.num_minor_ticks = 5 622 | local linearAxis2 = createLinearAxis(self._docid, plot.id, 623 | self._yaxis or json.null, tf2.id, bt2.id) 624 | renderers[#renderers+1] = linearAxis2 625 | local grid2 = createGrid(self._docid, plot.id, 1, bt2.id) 626 | renderers[#renderers+1] = grid2 627 | table.insert(all_models, tf2) 628 | table.insert(all_models, bt2) 629 | table.insert(all_models, linearAxis2) 630 | table.insert(all_models, grid2) 631 | 632 | local tools = {} 633 | tools[1] = createTool(self._docid, 'PanTool', plot.id, {'width', 'height'}) 634 | tools[2] = createTool(self._docid, 'WheelZoomTool', plot.id, 635 | {'width', 'height'}) 636 | tools[3] = createTool(self._docid, 'BoxZoomTool', plot.id, nil) 637 | tools[4] = createTool(self._docid, 'PreviewSaveTool', plot.id, nil) 638 | tools[5] = createTool(self._docid, 'ResizeTool', plot.id, nil) 639 | tools[6] = createTool(self._docid, 'ResetTool', plot.id, nil) 640 | for i, x in ipairs(self._tools or {}) do 641 | local t = createTool(self._docid, x.type, plot.id, nil) 642 | for k, v in pairs(x) do 643 | if k ~= 'type' then 644 | t.attributes[k] = v 645 | end 646 | end 647 | table.insert(tools, t) 648 | end 649 | for i=1,#tools do table.insert(all_models, tools[i]) end 650 | 651 | if self._legend then 652 | local legend = createLegend(self._docid, plot.id, self._data, grs) 653 | renderers[#renderers+1] = legend 654 | table.insert(all_models, legend) 655 | end 656 | 657 | -- Plot 658 | plot.attributes.x_range = {} 659 | plot.attributes.x_range.type = 'DataRange1d' 660 | plot.attributes.x_range.id = drx.id 661 | plot.attributes.extra_x_ranges = {} 662 | plot.attributes.y_range = {} 663 | plot.attributes.y_range.type = 'DataRange1d' 664 | plot.attributes.y_range.id = dry.id 665 | plot.attributes.extra_y_ranges = {} 666 | plot.attributes.right = {} 667 | plot.attributes.above = {} 668 | plot.attributes.below = {{}} 669 | plot.attributes.below[1].type = 'LinearAxis' 670 | plot.attributes.below[1].id = linearAxis1.id 671 | plot.attributes.left = {{}} 672 | plot.attributes.left[1].type = 'LinearAxis' 673 | plot.attributes.left[1].id = linearAxis2.id 674 | plot.attributes.title = self._title or 'Untitled Plot' 675 | plot.attributes.tools = {} 676 | for i=1,#tools do 677 | plot.attributes.tools[i] = {} 678 | plot.attributes.tools[i].type = tools[i].type 679 | plot.attributes.tools[i].id = tools[i].id 680 | end 681 | plot.attributes.renderers = {} 682 | for i=1,#renderers do 683 | plot.attributes.renderers[i] = {} 684 | plot.attributes.renderers[i].type = renderers[i].type 685 | plot.attributes.renderers[i].id = renderers[i].id 686 | end 687 | plot.attributes.tool_events = {} 688 | plot.attributes.tool_events.type = 'ToolEvents' 689 | plot.attributes.tool_events.id = toolEvents.id 690 | table.insert(all_models, plot) 691 | 692 | return all_models 693 | end 694 | 695 | local function encodeAllModels(m) 696 | local s = json.encode(m) 697 | local w = {'selected', 'above', 'geometries', 'right', 'tags'} 698 | for i=1,#w do 699 | local before = '"' .. w[i] .. '":{}' 700 | local after = '"' .. w[i] .. '":[]' 701 | s=string.gsub(s, before, after) 702 | end 703 | return s 704 | end 705 | 706 | local base_template = [[ 707 | 765 | ]] 766 | 767 | local embed_template = base_template .. [[ 768 |
769 | ]] 770 | 771 | local html_template = [[ 772 | 773 | 774 | 775 | 776 | 777 | 778 | ]] .. base_template .. [[ 779 | 780 | 781 |
782 | 783 | 784 | ]] 785 | 786 | function Plot:toTemplate(template, window_id) 787 | local allmodels = self:_toAllModels() 788 | local div_id = uuid.new() 789 | local window_id = window_id or div_id 790 | self._winid = window_id 791 | -- find model_id 792 | local model_id 793 | for k,v in ipairs(allmodels) do 794 | if v.type == 'Plot' then 795 | model_id = v.id 796 | end 797 | end 798 | assert(model_id, "Could not find Plot element in input allmodels"); 799 | local html = template % { 800 | window_id = window_id, 801 | div_id = div_id, 802 | all_models = encodeAllModels(allmodels), 803 | model_id = model_id 804 | }; 805 | return html 806 | end 807 | 808 | function Plot:toHTML() 809 | return self:toTemplate(html_template) 810 | end 811 | 812 | function Plot:draw(window_id) 813 | if not itorch then return self end 814 | local util = require 'itorch.util' 815 | local content = {} 816 | content.source = 'itorch' 817 | content.data = {} 818 | content.data['text/html'] = self:toTemplate(embed_template, window_id) 819 | content.metadata = {} 820 | local m = util.msg('display_data', itorch._msg) 821 | m.content = content 822 | util.ipyEncodeAndSend(itorch._iopub, m) 823 | return self 824 | end 825 | 826 | function Plot:redraw() 827 | self:draw(self._winid) 828 | return self 829 | end 830 | 831 | 832 | 833 | function Plot:save(filename) 834 | assert(filename and not paths.dirp(filename), 835 | 'filename has to be provided and should not be a directory') 836 | local html = self:toHTML() 837 | local f = assert(io.open(filename, 'w'), 838 | 'filename cannot be opened in write mode') 839 | f:write(html) 840 | f:close() 841 | return self 842 | end 843 | 844 | return Plot 845 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iTorch 2 | iTorch is an [IPython](http://ipython.org) Kernel for Torch, with plotting (using [Bokeh.js](http://bokeh.pydata.org/docs/gallery.html) plots) and visualization of images, video and audio 3 | 4 | ## Features 5 | iTorch in notebook mode works like any other IPython notebook. 6 | It provides useful **inline auto-complete**. Whenever you need auto-complete, use the **TAB key**. 7 | ![screenshot](screenshots/autocomplete.png "") 8 | 9 | It also provides **inline help** using the ? symbol. 10 | For example, `?torch.cmul` 11 | ![screenshot](screenshots/help.png "") 12 | 13 | In addition, we introduce visualization functions for images, video, audio, html and plots. 14 | 15 | **itorch.image(img)** - You can pass in a 3-D tensor (for a single image), or a table of 3D tensors (image collages). 16 | ```lua 17 | itorch.image({image.lena(), image.lena(), image.lena()}) 18 | ``` 19 | ![screenshot](screenshots/image.png "") 20 | 21 | **itorch.audio(path)** - You can pass in a filename of an audio file. Formats supported are mp3, ogg, aac 22 | ```lua 23 | itorch.audio('example.mp3') 24 | ``` 25 | ![screenshot](screenshots/audio.png "") 26 | 27 | **itorch.video(path)** - You can pass in a filename of a video file. Formats supported are mp4, ogv, mpeg 28 | ```lua 29 | itorch.video('example.mp4') 30 | ``` 31 | ![screenshot](screenshots/video.png "") 32 | 33 | **[window-id] = itorch.html(htmlstring, [window-id])** - Raw HTML string that is passed is rendered. A window handle is returned, that can be reused to replace the HTML with something else. 34 | ```lua 35 | itorch.html('

Hi there! this is arbitrary HTML

') 36 | window_id = itorch.html('

This text will be replaced in 2 seconds

') 37 | os.execute('sleep 2') 38 | itorch.html('

magic!

', window_id) 39 | ``` 40 | ![screenshot](screenshots/html.png "") 41 | 42 | ###Plotting 43 | iTorch can plot to screen in notebook mode, or save the plot to disk as a html file. 44 | 45 | A Plot object is introduced, that can plot different kinds of plots such as scatter, line, segment, quiver plots. 46 | ```lua 47 | Plot = require 'itorch.Plot' 48 | ``` 49 | The plotting can be extended to more kinds of plots, as it uses [Bokeh.js](http://bokeh.pydata.org/en/latest/docs/dev_guide/bokehjs.html) as its backend. 50 | ```lua 51 | x1 = torch.randn(40):mul(100) 52 | y1 = torch.randn(40):mul(100) 53 | x2 = torch.randn(40):mul(100) 54 | y2 = torch.randn(40):mul(100) 55 | x3 = torch.randn(40):mul(200) 56 | y3 = torch.randn(40):mul(200) 57 | 58 | 59 | -- scatter plots 60 | plot = Plot():circle(x1, y1, 'red', 'hi'):circle(x2, y2, 'blue', 'bye'):draw() 61 | plot:circle(x3,y3,'green', 'yolo'):redraw() 62 | plot:title('Scatter Plot Demo'):redraw() 63 | plot:xaxis('length'):yaxis('width'):redraw() 64 | plot:legend(true) 65 | plot:redraw() 66 | -- print(plot:toHTML()) 67 | plot:save('out.html') 68 | ``` 69 | ![screenshot](screenshots/scatter.png "") 70 | 71 | ```lua 72 | -- line plots 73 | plot = Plot():line(x1, y1,'red','example'):legend(true):title('Line Plot Demo'):draw() 74 | ``` 75 | 76 | ```lua 77 | -- segment plots 78 | plot = Plot():segment(x1, y1, x1+10,y1+10, 'red','demo'):title('Segment Plot Demo'):draw() 79 | ``` 80 | 81 | ```lua 82 | -- quiver plots 83 | U = torch.randn(3,3):mul(100) 84 | V = torch.randn(3,3):mul(100) 85 | plot = Plot():quiver(U,V,'red',''):title('Quiver Plot Demo'):draw() 86 | ``` 87 | ![screenshot](screenshots/quiver.png "") 88 | 89 | ```lua 90 | -- quads/rectangles 91 | x1=torch.randn(10) 92 | y1=torch.randn(10) 93 | plot = Plot():quad(x1,y1,x1+1,y1+1,'red',''):draw() 94 | ``` 95 | 96 | ```lua 97 | -- histogram 98 | plot = Plot():histogram(torch.randn(10000)):draw() 99 | ``` 100 | ![screenshot](screenshots/hist.png "") 101 | 102 | #### Hover Tool in Plotting 103 | ```lua 104 | local t = torch.Tensor 105 | local y = t(10) 106 | local x = t(y:size()):zero() 107 | local labels = {} 108 | for i = 1, 10 do 109 | y[i] = i 110 | labels[i] = tostring(i) 111 | end 112 | 113 | itorch.Plot() 114 | :circle(x, y, 'red', nil, {foo=labels}) 115 | :hover_tool({{'xy', '@x @y'}, {'foo', '@foo'}}) 116 | :draw() 117 | ``` 118 | 119 | #### Text method to plotting 120 | ```lua 121 | local t = torch.Tensor 122 | local y = t(10) 123 | local x = t(y:size()):zero() 124 | local labels = {} 125 | for i = 1, 10 do 126 | y[i] = i 127 | labels[i] = tostring(i) 128 | end 129 | 130 | itorch.Plot():gscatter(x, y) 131 | :text(x, y, labels, y, 'black') 132 | :triangle(x, y, 'blue') 133 | :draw() 134 | ``` 135 | 136 | #### Group Scatter plot 137 | Run the following in itorch to produce plots: 138 | ```lua 139 | x = torch.randn(200); y = torch.randn(200); x:narrow(1, 1, 100):add(2); 140 | labels = torch.LongTensor(200):zero(); labels:add(1); labels:narrow(1, 1, 100):add(1) 141 | itorch.Plot():gscatter(x, y):title('Scatter plot without labels'):draw() 142 | itorch.Plot():gscatter(x, y, labels):title('Scatter plot with labels and legend #1'):legend(true):draw() 143 | itorch.Plot():gscatter(x, y, labels, true):title('Scatter plot with labels and legend #2'):draw() 144 | itorch.Plot():gscatter(x, y, labels, false):title('Scatter plot with labels and no legend'):draw() 145 | ``` 146 | 147 | ## Requirements 148 | iTorch requires or works with 149 | * Mac OS X or Linux (tested in Ubuntu 14.04 and Arch Linux) 150 | * [Torch-7](https://github.com/torch/torch7/wiki/Cheatsheet#installing-and-running-torch) 151 | * [IPython](http://ipython.org/install.html) version 2.2 or above (you can check your version of ipython using ipython --version) 152 | * ZeroMQ 153 | ```bash 154 | # OSX 155 | brew install zeromq 156 | brew install openssl 157 | luarocks install luacrypto OPENSSL_DIR=/usr/local/opt/openssl/ 158 | 159 | # Ubuntu 160 | sudo apt-get install libzmq3-dev libssl-dev python-zmq 161 | 162 | # Ubuntu 16 163 | luarocks install lzmq 164 | ``` 165 | 166 | ## Installing iTorch 167 | ```bash 168 | git clone https://github.com/facebook/iTorch.git 169 | cd iTorch 170 | luarocks make 171 | ``` 172 | 173 | If you have to use sudo for some reason (if you globally installed torch on Linux for example), use these commands: 174 | ```bash 175 | sudo env "PATH=$PATH" luarocks make 176 | sudo chown -R $USER $(dirname $(ipython locate profile)) 177 | ``` 178 | 179 | ## Docker Images 180 | Ubuntu 14.04 + iTorch notebook + Miniconda: see docker hub [repo](https://hub.docker.com/r/dhunter/itorch-notebook/). 181 | 182 | ```bash 183 | docker pull dhunter/itorch-notebook 184 | ``` 185 | 186 | ## How iTorch works 187 | Start iTorch at command-line using the following command: 188 | ```bash 189 | itorch notebook # notebook mode 190 | OR 191 | itorch # console mode 192 | OR 193 | itorch qtconsole # Qt mode 194 | ``` 195 | 196 | In notebook mode, you can use the "New" button to create a new notebook. 197 | 198 | ## Examples 199 | Demo iTorch notebook: http://nbviewer.ipython.org/github/facebook/iTorch/blob/master/iTorch_Demo.ipynb 200 | 201 | ## Join the Torch community 202 | See the CONTRIBUTING file for how to help out. 203 | 204 | ## License 205 | iTorch is BSD-licensed. We also provide an additional patent grant. 206 | 207 | -------------------------------------------------------------------------------- /_env.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | ]]-- 10 | local itorch = {} 11 | return itorch 12 | -------------------------------------------------------------------------------- /bokeh.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | ]]-- 10 | local uuid = require 'uuid' 11 | local json = require 'cjson' 12 | local tablex = require 'pl.tablex' 13 | require 'pl.text'.format_operator() 14 | require 'image' 15 | local itorch = require 'itorch._env' 16 | local util = require 'itorch.util' 17 | 18 | -- 2D charts 19 | --* scatter 20 | -- bar (grouped and stacked) 21 | -- pie 22 | -- histogram 23 | -- area-chart (http://bokeh.pydata.org/docs/gallery/brewer.html) 24 | -- categorical heatmap 25 | -- timeseries 26 | -- confusion matrix 27 | -- image_rgba 28 | -- candlestick 29 | --* vectors 30 | ------------------ 31 | -- 2D plots 32 | --* line plot 33 | -- log-scale plots 34 | -- semilog-scale plots 35 | -- error-bar / candle-stick plot 36 | -- contour plots 37 | -- polar plots / angle-histogram plot / compass plot (arrowed histogram) 38 | --* vector fields (feather plot, quiver plot, compass plot, 3D quiver plot) 39 | ------------------------- 40 | -- 3D plots 41 | -- line plot 42 | -- scatter-3D ************** (important) 43 | -- contour-3D 44 | -- 3D shaded surface plot (surf/surfc) 45 | -- surface normals 46 | -- mesh plot 47 | -- ribbon plot (for fun) 48 | 49 | -- create a torch.peaks (useful) 50 | -------------------------------------------------------------------- 51 | function itorch.demo(window_id) 52 | require 'itorch.test' 53 | end 54 | 55 | return itorch; 56 | -------------------------------------------------------------------------------- /completer.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | ]]-- 10 | --- Completion engine 11 | -- Compute possible matches for input text. 12 | -- Based on [lua-rlcompleter](https://github.com/rrthomas/lua-rlcompleter) by 13 | -- Patrick Rapin and Reuben Thomas. 14 | -- @alias M 15 | 16 | local lfs = pcall(require, "lfs") and require"lfs" 17 | local cowrap, coyield = coroutine.wrap, coroutine.yield 18 | local loadstring = loadstring or load 19 | 20 | local M = { } 21 | 22 | --- The list of Lua keywords 23 | M.keywords = { 24 | 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 25 | 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 26 | 'return', 'then', 'true', 'until', 'while' 27 | } 28 | 29 | --- Callback function to set final character. 30 | -- Called to set the final chracter if a single match occur. Typically, it is used 31 | -- to close a string if completion occurs inside an open string. It is intended 32 | -- to be redefined as default function does nothing. 33 | -- @param[type=string] char Either an empty string or a string of length 1. 34 | M.final_char_setter = function(char) end 35 | 36 | --- References all completion generators (completers). 37 | -- Completion is context sensitive and there is 2 *contexts* : values 38 | -- (@{completers.value}) and strings (@{completers.string}). Some arguments are 39 | -- given to the completer the first time it's called. 40 | -- 41 | -- Each context has its corresponding table (sequence) with *completers* inside. 42 | -- These tables works a bit like `package.loaders` : each loader is called as a 43 | -- coroutine and can generate possible matches (by yielding strings). But, unlike 44 | -- `package.loaders`, all completers are always called; even if matches has been 45 | -- generated by previous ones. 46 | -- 47 | -- It it not the completer job to filter matches with current text (this is done 48 | -- by the complettion engine), but just generate all possible matches. 49 | M.completers = { } 50 | 51 | --- Completers for Lua values. 52 | -- Completers are called with two arguments : 53 | -- 54 | -- * `value` to complete (not necessarily a table) 55 | -- * `separator` used to index value (`.`, `:` or `[`) 56 | -- 57 | -- Default completers for values are : 58 | -- 59 | -- 1. Table field completer: searches for fields inside `value` if it's a table. 60 | -- 2. Metatable completer: if `value` has a metatable, calls first completer 61 | -- with that table. 62 | -- 63 | -- @table completers.value 64 | M.completers.value = { } 65 | 66 | --- Completers for strings. 67 | -- Completers are called with the string to complete. 68 | -- If `lfs` is can be loaded, a completer for files and folders is provided, it 69 | -- search for items starting with given string in current directory. 70 | -- @table completers.string 71 | M.completers.string = { } 72 | 73 | -- Table fields completer 74 | table.insert(M.completers.value, function(t, sep) 75 | if type(t) ~= "table" then return end 76 | for k, v in pairs(t) do 77 | if type(k) == "number" and sep == "[" then 78 | coyield(k.."]") 79 | elseif type(k) == "string" and (sep ~= ":" or type(v) == "function") then 80 | coyield(k) 81 | end 82 | end 83 | end) 84 | 85 | -- Metamethod completer 86 | table.insert(M.completers.value, function(t, sep) 87 | local mt = getmetatable(t) 88 | if mt and type(mt.__index) == "table" then 89 | return M.completers.value[1](mt.__index, sep) -- use regular table completer on metatable 90 | end 91 | end) 92 | 93 | -- tensor/storage/torch-classes completer 94 | table.insert(M.completers.value, function(t, sep) 95 | local function enumerate_metatable(typename) 96 | if typename == nil then return end 97 | local metatable = torch.getmetatable(typename) 98 | for k, v in pairs(metatable) do 99 | if type(k) == "number" and sep == "[" then 100 | coyield(k.."]") 101 | elseif type(k) == "string" and (sep ~= ":" or type(v) == "function") then 102 | coyield(k) 103 | end 104 | end 105 | if torch.typename(metatable) ~= typename then 106 | enumerate_metatable(torch.typename(metatable)) 107 | end 108 | end 109 | enumerate_metatable(torch.typename(t)) 110 | end) 111 | 112 | 113 | -- This function does the same job as the default completion of readline, 114 | -- completing paths and filenames. Rewritten because 115 | -- rl_basic_word_break_characters is different. 116 | -- Uses LuaFileSystem (lfs) module for this task (if present). 117 | if lfs then 118 | table.insert(M.completers.string, function(str) 119 | local path, name = str:match("(.*)[\\/]+(.*)") 120 | path = (path or ".") .. "/" 121 | name = name or str 122 | -- avoid to trigger an error if folder does not exists 123 | if not lfs.attributes(path) then return end 124 | for f in lfs.dir(path) do 125 | if (lfs.attributes(path .. f) or {}).mode == 'directory' then 126 | coyield(f .. "/") 127 | else 128 | coyield(f) 129 | end 130 | end 131 | end) 132 | end 133 | 134 | -- This function is called back by C function do_completion, itself called 135 | -- back by readline library, in order to complete the current input line. 136 | function M.complete(word, line, startpos, endpos) 137 | -- Helper function registering possible completion words, verifying matches. 138 | local matches = {} 139 | local function add(value) 140 | value = tostring(value) 141 | if value:match("^" .. word) then 142 | matches[#matches + 1] = value 143 | end 144 | end 145 | 146 | local function call_completors(completers, ...) 147 | for _, completer in ipairs(completers) do 148 | local coro = cowrap(completer) 149 | local match = coro(...) -- first call => give parameters 150 | if match then 151 | add(match) 152 | -- continue calling to get next matches 153 | for match in coro do add(match) end 154 | end 155 | end 156 | end 157 | 158 | -- This function is called in a context where a keyword or a global 159 | -- variable can be inserted. Local variables cannot be listed! 160 | local function add_globals() 161 | for _, k in ipairs(M.keywords) do 162 | add(k) 163 | end 164 | call_completors(M.completers.value, _G) 165 | end 166 | 167 | -- Main completion function. It evaluates the current sub-expression 168 | -- to determine its type. Currently supports tables fields, global 169 | -- variables and function prototype completion. 170 | local function contextual_list(expr, sep, str) 171 | if str then 172 | M.final_char_setter('"') 173 | return call_completors(M.completers.string, str) 174 | end 175 | M.final_char_setter("") 176 | if expr and expr ~= "" then 177 | local v = loadstring("return " .. expr) 178 | if v then 179 | call_completors(M.completers.value, v(), sep) 180 | end 181 | end 182 | if #matches == 0 then 183 | add_globals() 184 | end 185 | end 186 | 187 | -- This complex function tries to simplify the input line, by removing 188 | -- literal strings, full table constructors and balanced groups of 189 | -- parentheses. Returns the sub-expression preceding the word, the 190 | -- separator item ( '.', ':', '[', '(' ) and the current string in case 191 | -- of an unfinished string literal. 192 | local function simplify_expression(expr) 193 | -- Replace annoying sequences \' and \" inside literal strings 194 | expr = expr:gsub("\\(['\"])", function (c) 195 | return string.format("\\%03d", string.byte(c)) 196 | end) 197 | local curstring 198 | -- Remove (finished and unfinished) literal strings 199 | while true do 200 | local idx1, _, equals = expr:find("%[(=*)%[") 201 | local idx2, _, sign = expr:find("(['\"])") 202 | if idx1 == nil and idx2 == nil then 203 | break 204 | end 205 | local idx, startpat, endpat 206 | if (idx1 or math.huge) < (idx2 or math.huge) then 207 | idx, startpat, endpat = idx1, "%[" .. equals .. "%[", "%]" .. equals .. "%]" 208 | else 209 | idx, startpat, endpat = idx2, sign, sign 210 | end 211 | if expr:sub(idx):find("^" .. startpat .. ".-" .. endpat) then 212 | expr = expr:gsub(startpat .. "(.-)" .. endpat, " STRING ") 213 | else 214 | expr = expr:gsub(startpat .. "(.*)", function (str) 215 | curstring = str 216 | return "(CURSTRING " 217 | end) 218 | end 219 | end 220 | expr = expr:gsub("%b()"," PAREN ") -- Remove groups of parentheses 221 | expr = expr:gsub("%b{}"," TABLE ") -- Remove table constructors 222 | -- Avoid two consecutive words without operator 223 | expr = expr:gsub("(%w)%s+(%w)","%1|%2") 224 | expr = expr:gsub("%s+", "") -- Remove now useless spaces 225 | -- This main regular expression looks for table indexes and function calls. 226 | return curstring, expr:match("([%.%w%[%]_]-)([:%.%[%(])" .. word .. "$") 227 | end 228 | 229 | -- Now call the processing functions and return the list of results. 230 | local str, expr, sep = simplify_expression(line:sub(1, endpos)) 231 | contextual_list(expr, sep, str) 232 | return matches 233 | end 234 | 235 | return M 236 | -------------------------------------------------------------------------------- /custom.css: -------------------------------------------------------------------------------- 1 | /* disable smoothing on images (needed to visualize filters) */ 2 | img { 3 | image-rendering: optimizeSpeed; 4 | image-rendering: -moz-crisp-edges; /* Firefox */ 5 | image-rendering: -o-crisp-edges; /* Opera */ 6 | image-rendering: -webkit-optimize-contrast; /* Chrome (and eventually Safari) */ 7 | image-rendering: optimize-contrast; /* CSS3 Proposed */ 8 | -ms-interpolation-mode: nearest-neighbor; /* IE8+ */ 9 | 10 | } -------------------------------------------------------------------------------- /custom.js: -------------------------------------------------------------------------------- 1 | $([IPython.events]).on('notebook_loaded.Notebook', function(){ 2 | // add here logic that should be run once per **notebook load** 3 | // (!= page load), like restarting a checkpoint 4 | var md = IPython.notebook.metadata 5 | if(md.language){ 6 | console.log('language already defined and is :', md.language); 7 | } else { 8 | md.language = 'lua' ; 9 | console.log('add metadata hint that language is lua'); 10 | } 11 | }); 12 | 13 | // logic per page-refresh 14 | $([IPython.events]).on("app_initialized.NotebookApp", function () { 15 | $('head').append(''); 16 | 17 | 18 | IPython.CodeCell.options_default['cm_config']['mode'] = 'lua'; 19 | 20 | CodeMirror.requireMode('lua', function(){ 21 | IPython.OutputArea.prototype._should_scroll = function(){return false} 22 | cells = IPython.notebook.get_cells(); 23 | for(var i in cells){ 24 | c = cells[i]; 25 | if (c.cell_type === 'code'){ 26 | c.auto_highlight() 27 | } 28 | } 29 | }); 30 | 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /gfx.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local uuid = require 'uuid' 3 | local base64 = require 'base64' 4 | require 'pl.text'.format_operator() 5 | require 'image' 6 | local itorch = require 'itorch._env' 7 | require 'itorch.bokeh' 8 | local util = require 'itorch.util' 9 | 10 | -- Example: require 'image';itorch.image(image.scale(image.lena(),16,16)) 11 | function itorch.image(img, opts) 12 | assert(itorch._iopub,'itorch._iopub socket not set') 13 | assert(itorch._msg,'itorch._msg not set') 14 | if torch.type(img) == 'string' then -- assume that it is path 15 | if img:sub(#img-3) == '.svg' then 16 | return itorch.svg(img) 17 | else 18 | img = image.load(img, 3) -- TODO: revamp this to just directly load the blob, infer file prefix, and send. 19 | end 20 | end 21 | if torch.isTensor(img) or torch.type(img) == 'table' then 22 | opts = opts or {padding=2} 23 | opts.input = img 24 | local imgDisplay = image.toDisplayTensor(opts) 25 | if imgDisplay:dim() == 2 then 26 | imgDisplay = imgDisplay:view(1, imgDisplay:size(1), imgDisplay:size(2)) 27 | end 28 | local tmp = os.tmpname() .. '.png' 29 | image.save(tmp, imgDisplay) 30 | ------------------------------------------------------------- 31 | -- load the image back as binary blob 32 | local f = assert(torch.DiskFile(tmp,'r',true)):binary(); 33 | f:seekEnd(); 34 | local size = f:position()-1 35 | f:seek(1) 36 | local buf = torch.CharStorage(size); 37 | assert(f:readChar(buf) == size, 'wrong number of bytes read') 38 | f:close() 39 | os.execute('rm -f ' .. tmp) 40 | ------------------------------------------------------------ 41 | local content = {} 42 | content.source = 'itorch' 43 | content.data = {} 44 | content.data['text/plain'] = 'Console does not support images' 45 | content.data['image/png'] = base64.encode(ffi.string(torch.data(buf), size)) 46 | content.metadata = { } 47 | content.metadata['image/png'] = {width = imgDisplay:size(3), height = imgDisplay:size(2)} 48 | 49 | local m = util.msg('display_data', itorch._msg) 50 | m.content = content 51 | util.ipyEncodeAndSend(itorch._iopub, m) 52 | else 53 | error('unhandled type in itorch.image:' .. torch.type(img)) 54 | end 55 | end 56 | 57 | local audio_template = [[ 58 |
59 | ]] 60 | -- Example: itorch.audio('hello.mp3') 61 | function itorch.audio(fname) 62 | assert(itorch._iopub,'itorch._iopub socket not set') 63 | assert(itorch._msg,'itorch._msg not set') 64 | 65 | -- get prefix 66 | local pos = fname:reverse():find('%.') 67 | local ext = fname:sub(#fname-pos + 2) 68 | assert(ext == 'mp3' or ext == 'wav' or ext == 'ogg' or ext == 'aac', 69 | 'mp3, wav, ogg, aac files supported. But found extension: ' .. ext) 70 | -- load the audio as binary blob 71 | local f = assert(torch.DiskFile(fname,'r',true), 72 | 'File could not be opened: ' .. fname):binary(); 73 | f:seekEnd(); 74 | local size = f:position()-1 75 | f:seek(1) 76 | local buf = torch.CharStorage(size); 77 | assert(f:readChar(buf) == size, 'wrong number of bytes read') 78 | f:close() 79 | local base64audio = base64.encode(ffi.string(torch.data(buf), size)) 80 | local div_id = uuid.new() 81 | local content = {} 82 | content.source = 'itorch' 83 | content.data = {} 84 | content.data['text/html'] = 85 | audio_template % { 86 | div_id = div_id, 87 | extension = ext, 88 | base64audio = base64audio 89 | }; 90 | content.metadata = {} 91 | local m = util.msg('display_data', itorch._msg) 92 | m.content = content 93 | util.ipyEncodeAndSend(itorch._iopub, m) 94 | return window_id 95 | end 96 | 97 | local video_template = [[ 98 |
99 | ]] 100 | -- Example: itorch.video('hello.mp4') 101 | function itorch.video(fname) 102 | assert(itorch._iopub,'itorch._iopub socket not set') 103 | assert(itorch._msg,'itorch._msg not set') 104 | 105 | -- get prefix 106 | local pos = fname:reverse():find('%.') 107 | local ext = fname:sub(#fname-pos + 2) 108 | if ext == 'ogv' then ext = 'ogg' end 109 | assert(ext == 'mp4' or ext == 'wav' or ext == 'ogg' or ext == 'webm', 110 | 'mp4, ogg, webm files supported. But found extension: ' .. ext) 111 | 112 | -- load the video as binary blob 113 | local f = assert(torch.DiskFile(fname,'r',true), 114 | 'File could not be opened: ' .. fname):binary(); 115 | f:seekEnd(); 116 | local size = f:position()-1 117 | f:seek(1) 118 | local buf = torch.CharStorage(size); 119 | assert(f:readChar(buf) == size, 'wrong number of bytes read') 120 | f:close() 121 | local base64video = base64.encode(ffi.string(torch.data(buf), size)) 122 | local div_id = uuid.new() 123 | local content = {} 124 | content.source = 'itorch' 125 | content.data = {} 126 | content.data['text/html'] = 127 | video_template % { 128 | div_id = div_id, 129 | extension = ext, 130 | base64video = base64video 131 | }; 132 | content.metadata = {} 133 | local m = util.msg('display_data', itorch._msg) 134 | m.content = content 135 | util.ipyEncodeAndSend(itorch._iopub, m) 136 | return window_id 137 | end 138 | 139 | function itorch.lena() 140 | itorch.image(image.lena()) 141 | end 142 | 143 | local html_template = 144 | [[ 145 | 150 |
151 | ]] 152 | 153 | local function escape_js(s) 154 | -- backslash 155 | s = s:gsub("\\","\\\\") 156 | -- single quite 157 | s = s:gsub("'","\'") 158 | -- double quote 159 | s = s:gsub('"','\"') 160 | -- newline 161 | s = s:gsub("\n","\\n") 162 | -- carriage return 163 | s = s:gsub("\r","\\r") 164 | -- tab 165 | s = s:gsub("\t","\\t") 166 | -- backspace 167 | s = s:gsub("\b","\\b") 168 | -- form feed 169 | s = s:gsub("\f","\\f") 170 | return s 171 | end 172 | function itorch.html(html, window_id) 173 | html = escape_js(html) 174 | assert(itorch._iopub,'itorch._iopub socket not set') 175 | assert(itorch._msg,'itorch._msg not set') 176 | 177 | local div_id = uuid.new() 178 | window_id = window_id or div_id 179 | local content = {} 180 | content.source = 'itorch' 181 | content.data = {} 182 | content.data['text/html'] = 183 | html_template % { 184 | html_content = html, 185 | window_id = window_id, 186 | div_id = div_id 187 | }; 188 | content.metadata = {} 189 | 190 | -- send displayData 191 | local m = util.msg('display_data', itorch._msg) 192 | m.content = content 193 | util.ipyEncodeAndSend(itorch._iopub, m) 194 | return window_id 195 | end 196 | 197 | function itorch.svg(filename) 198 | -- first check if file exists. 199 | if paths.filep(filename) then 200 | local f = io.open(filename) 201 | local str = f:read("*all") 202 | f:close() 203 | return itorch.html(str) 204 | else -- try rendering as raw string 205 | return itorch.html(filename) 206 | end 207 | 208 | end 209 | 210 | 211 | local ok,err = pcall(function() require 'xlua' end) 212 | if ok then 213 | local progress_warning = false 214 | local last = 0 215 | local _write = io.write 216 | local _flush = io.flush 217 | local _progress = xlua.progress 218 | xlua.progress = function(i, n) 219 | -- make progress bar really slow in itorch 220 | if os.clock() - last > 1 or i == n then 221 | local outstr = '' 222 | io.write = function(s) 223 | if s ~= '\r' and s ~= '\n' then 224 | outstr = outstr..s 225 | end 226 | end 227 | io.flush = function() end 228 | 229 | _progress(i, n) 230 | 231 | io.write = _write 232 | io.flush = _flush 233 | 234 | itorch._iopub_router.stream(itorch._iopub, itorch._msg, 'stdout', '\r'..outstr) 235 | 236 | -- local m = util.msg('clear_output', itorch._msg) 237 | -- m.content = {} 238 | -- m.content.wait = true 239 | -- m.content.display = true 240 | -- util.ipyEncodeAndSend(itorch._iopub, m) 241 | 242 | last = os.clock() 243 | end 244 | end 245 | end 246 | 247 | return itorch; 248 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | ]]-- 10 | itorch=require 'itorch._env' 11 | require 'itorch.gfx' 12 | require 'itorch.bokeh' 13 | itorch.Plot=require 'itorch.Plot' 14 | 15 | local _class = torch.class 16 | torch.class = function(name, parentName, module) 17 | if name ~= nil then 18 | debug.getregistry()[name] = nil 19 | end 20 | if module ~= nil then 21 | return _class(name, parentName, module) 22 | elseif parentName ~= nil then 23 | return _class(name, parentName) 24 | else 25 | return _class(name) 26 | end 27 | end 28 | 29 | return itorch 30 | 31 | -------------------------------------------------------------------------------- /ipynblogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/ipynblogo.png -------------------------------------------------------------------------------- /itorch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | mode=$1 3 | if [ -z "$mode" ]; then 4 | mode="console" 5 | else 6 | shift 7 | fi 8 | v=$(ipython --version|cut -f1 -d'.') 9 | if [ $v = 2 ]; then 10 | ipython $mode --profile torch $@ 11 | elif [ $v = 3 ]; then 12 | if [ $mode = "console" ]; then 13 | ipython $mode --profile torch $@ 14 | else 15 | ipython $mode --MappingKernelManager.default_kernel_name="itorch" $@ 16 | fi 17 | elif [ $v = 4 ]; then 18 | if [ $mode = "console" ]; then 19 | jupyter console --kernel=itorch $@ 20 | else 21 | ipython $mode --MappingKernelManager.default_kernel_name="itorch" $@ 22 | fi 23 | elif [ $v = 5 ]; then 24 | if [ $mode = "console" ]; then 25 | jupyter console --kernel=itorch $@ 26 | else 27 | ipython $mode --MappingKernelManager.default_kernel_name="itorch" $@ 28 | fi 29 | else 30 | echo "Unsupported ipython version. Only major versions 2.xx, 3.xx, 4.xx or 5.xx (Jupyter) are supported" 31 | fi 32 | -------------------------------------------------------------------------------- /itorch-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "itorch" 2 | version = "scm-1" 3 | 4 | source = { 5 | url = "git://github.com/facebook/iTorch.git", 6 | } 7 | 8 | description = { 9 | summary = "iPython kernel for Lua / Torch", 10 | detailed = [[ 11 | ]], 12 | homepage = "https://github.com/facebook/iTorch", 13 | license = "BSD" 14 | } 15 | 16 | dependencies = { 17 | "torch >= 7.0", 18 | "luafilesystem", 19 | "penlight", 20 | "lua-cjson", 21 | "uuid", 22 | "lbase64", 23 | "env", 24 | "image", 25 | "lzmq >= 0.4.2", 26 | "luacrypto" 27 | } 28 | 29 | build = { 30 | type = "command", 31 | build_command = [[ 32 | ipy=$(which ipython) 33 | if [ -x "$ipy" ] 34 | then 35 | ipybase=$(ipython locate) 36 | rm -rf $ipybase/profile_torch 37 | ipython profile create torch 38 | echo 'c.KernelManager.kernel_cmd = ["$(LUA_BINDIR)/itorch_launcher","{connection_file}"]' >>$ipybase/profile_torch/ipython_config.py 39 | echo "c.Session.key = b''" >>$ipybase/profile_torch/ipython_config.py 40 | echo "c.Session.keyfile = b''" >>$ipybase/profile_torch/ipython_config.py 41 | mkdir -p $ipybase/profile_torch/static/base/images 42 | mkdir -p $ipybase/kernels/itorch 43 | cat kernelspec/kernel.json | sed "s@LUA_BINDIR@$(LUA_BINDIR)@" > $ipybase/kernels/itorch/kernel.json 44 | cp kernelspec/*.png $ipybase/kernels/itorch/ 45 | cp ipynblogo.png $ipybase/profile_torch/static/base/images 46 | mkdir -p $ipybase/profile_torch/static/custom/ 47 | cp custom.js $ipybase/profile_torch/static/custom/ 48 | cp custom.css $ipybase/profile_torch/static/custom/ 49 | cp itorch $(LUA_BINDIR)/ 50 | cp itorch_launcher $(LUA_BINDIR)/ 51 | cp -r ~/.ipython/profile_torch ~/.ipython/profile_itorch 52 | cmake -E make_directory build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$(LUA_BINDIR)/.." -DCMAKE_INSTALL_PREFIX="$(PREFIX)" && $(MAKE) 53 | else 54 | echo "Error: could not find ipython in PATH. Do you have it installed?" 55 | fi 56 | 57 | ]], 58 | install_command = "cd build && $(MAKE) install" 59 | } 60 | -------------------------------------------------------------------------------- /itorch_launcher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | currdir=`dirname $0` 3 | currdir=$(cd "$currdir" && pwd) 4 | 5 | # Support qtlua, if it is available 6 | LUA=$currdir/luajit 7 | if test ! -x "$LUA"; then 8 | LUA=$currdir/lua 9 | fi 10 | 11 | # Pipe for stdout 12 | IO_STDO=$(mktemp -t itorch.stdout.XXXXXXXXXX) 13 | rm $IO_STDO 14 | if [ $(uname) == "Darwin" ]; then 15 | mkfifo $IO_STDO 16 | else 17 | touch $IO_STDO 18 | fi 19 | 20 | # Process 1 writes the port number to communicate to this file 21 | # Process 2 reades from this file and binds to that port 22 | IO_PORTNUM=$(mktemp -t itorch.PORTNUM.XXXXXXXXXX) 23 | "$LUA" -e "arg={'$1','$IO_PORTNUM'};require 'itorch.main'" 2>&1 >$IO_STDO & 24 | "$LUA" -e "arg={'$1','$IO_STDO','$IO_PORTNUM'};require 'itorch.IOHandler'" 25 | 26 | # temporary files $IO_STDO and $IO_PORTNUM are cleaned up on shutdown of itorch.IOHandler 27 | -------------------------------------------------------------------------------- /kernelspec/kernel.json: -------------------------------------------------------------------------------- 1 | { 2 | "argv": [ 3 | "LUA_BINDIR/itorch_launcher", 4 | "{connection_file}" 5 | ], 6 | "display_name": "iTorch", 7 | "language": "lua" 8 | } 9 | -------------------------------------------------------------------------------- /kernelspec/logo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/kernelspec/logo-64x64.png -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | ]]-- 10 | -- external dependencies 11 | local zmq = require 'lzmq' 12 | local zloop = require 'lzmq.loop' 13 | local zassert = zmq.assert 14 | local json = require 'cjson' 15 | local uuid = require 'uuid' 16 | local tablex = require 'pl.tablex' 17 | 18 | -- torch ecosystem dependencies 19 | local completer = require 'itorch.completer' 20 | require 'dok' -- for help(function) to work 21 | require 'env' -- TODO: remove 22 | 23 | -- itorch requires 24 | require 'itorch' 25 | local util = require 'itorch.util' 26 | local loadstring = loadstring or load 27 | unpack = unpack or table.unpack 28 | ----------------------------------------- 29 | local context = zmq.context() 30 | local session = {} 31 | session.create = function(self, uuid) 32 | local s = {} 33 | -- history 34 | s.history = { code = {}, output = {} } 35 | s.exec_count = 0 36 | -- set and return 37 | self[uuid] = s 38 | return self[uuid] 39 | end 40 | ------------------------------------------ 41 | -- load and decode json config 42 | local ipyfile = assert(io.open(arg[1], "rb"), "Could not open iPython config") 43 | local ipyjson = ipyfile:read("*all") 44 | ipyfile:close() 45 | local ipycfg = json.decode(ipyjson) 46 | -------------------------------------------------------------- 47 | -- set session key 48 | util.setSessionKey(ipycfg.key) 49 | -------------------------------------------------------------- 50 | -- bind 0MQ ports: Shell (ROUTER), Control (ROUTER), Stdin (ROUTER), IOPub (PUB) 51 | local ip = ipycfg.transport .. '://' .. ipycfg.ip .. ':' 52 | local shell, err = context:socket{zmq.ROUTER, bind = ip .. ipycfg.shell_port} 53 | zassert(shell, err) 54 | local control, err = context:socket{zmq.ROUTER, bind = ip .. ipycfg.control_port} 55 | zassert(control, err) 56 | local stdin, err = context:socket{zmq.ROUTER, bind = ip .. ipycfg.stdin_port} 57 | zassert(stdin, err) 58 | local iopub, err = context:socket(zmq.PAIR) 59 | zassert(iopub, err) 60 | do 61 | -- find a random open port between 10k and 65k with 1000 attempts. 62 | local port, err = iopub:bind_to_random_port(ipycfg.transport .. '://' .. ipycfg.ip, 63 | 10000,65535,1000) 64 | zassert(port, err) 65 | local portnum_f = torch.DiskFile(arg[2],'w') 66 | portnum_f:writeInt(port) 67 | portnum_f:close() 68 | end 69 | itorch._iopub = iopub -- for the display functions to have access 70 | arg = nil -- forget the command-line args 71 | -------------------------------------------------------------- 72 | -- IOPub router 73 | local iopub_router = {} 74 | -- http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-status 75 | iopub_router.status = function(sock, m, state) 76 | assert(state, 'state string is nil to iopub_router.status'); 77 | local o = util.msg('status', m) 78 | o.uuid = {'status'}; 79 | o.content = { execution_state = state } 80 | util.ipyEncodeAndSend(sock, o); 81 | end 82 | -- http://ipython.org/ipython-doc/dev/development/messaging.html#streams-stdout-stderr-etc 83 | iopub_router.stream = function(sock, m, stream, text) 84 | stream = stream or 'stdout' 85 | local o = util.msg('stream', m) 86 | o.content = { 87 | name = stream, 88 | data = text 89 | } 90 | util.ipyEncodeAndSend(sock, o); 91 | end 92 | itorch._iopub_router = iopub_router -- for the display functions to have access 93 | 94 | --------------------------------------------------------------------------- 95 | -- Shell router 96 | local shell_router = {} 97 | shell_router.connect_request = function (sock, msg) 98 | local reply = util.msg('connect_reply', msg) 99 | reply.content = ipycfg; 100 | util.ipyEncodeAndSend(sock, reply); 101 | end 102 | 103 | shell_router.kernel_info_request = function (sock, msg) 104 | iopub_router.status(sock, msg, 'busy'); 105 | local reply = util.msg('kernel_info_reply', msg) 106 | reply.content = { 107 | protocol_version = {4,0}, 108 | language_version = {tonumber(_VERSION:sub(#_VERSION-2))}, 109 | language = 'lua' 110 | } 111 | util.ipyEncodeAndSend(sock, reply); 112 | iopub_router.status(sock, msg, 'idle'); 113 | end 114 | 115 | shell_router.shutdown_request = function (sock, msg) 116 | iopub_router.status(sock, msg, 'busy'); 117 | local reply = util.msg('shutdown_reply', msg) 118 | util.ipyEncodeAndSend(sock, reply); 119 | iopub_router.status(sock, msg, 'idle'); 120 | -- cleanup 121 | print('Shutting down main') 122 | iopub:send_all({'private_msg', 'shutdown'}) 123 | assert(zassert(iopub:recv()) == 'ACK') 124 | shell:close() 125 | control:close() 126 | stdin:close() 127 | iopub:close() 128 | loop:stop() 129 | os.exit() 130 | end 131 | 132 | shell_router.is_complete_request = function(sock, msg) 133 | local reply = util.msg('is_complete_reply', msg) 134 | reply.content = {} 135 | local line = msg.content.code 136 | if line == '' or line == nil then 137 | reply.content.status = 'incomplete' 138 | reply.content.indent = '' 139 | else 140 | local valid, err 141 | valid, err = loadstring('return ' .. line) 142 | if not valid then 143 | valid, err = loadstring(line) 144 | end 145 | if not valid then 146 | -- now check if incomplete or invalid 147 | if err:sub(#err-4) == '' then 148 | reply.content.status = 'incomplete' 149 | else 150 | reply.content.status = 'invalid' 151 | end 152 | reply.content.indent = '' 153 | else 154 | reply.content.status = 'complete' 155 | end 156 | end 157 | util.ipyEncodeAndSend(sock, reply); 158 | end 159 | 160 | local function traceback(message) 161 | local tp = type(message) 162 | if tp ~= "string" and tp ~= "number" then return message end 163 | local debug = _G.debug 164 | if type(debug) ~= "table" then return message end 165 | local tb = debug.traceback 166 | if type(tb) ~= "function" then return message end 167 | return tb(message) 168 | end 169 | 170 | 171 | io.stdout:setvbuf('no') 172 | io.stderr:setvbuf('no') 173 | 174 | shell_router.execute_request = function (sock, msg) 175 | itorch._msg = msg 176 | iopub_router.status(iopub, msg, 'busy'); 177 | local s = session[msg.header.session] or session:create(msg.header.session) 178 | if not msg.content.silent and msg.content.store_history then 179 | s.exec_count = s.exec_count + 1; 180 | end 181 | 182 | -- send current session info to IOHandler, blocking-wait for ACK that it received it 183 | iopub:send_all({'private_msg', 'current_msg', json.encode(msg)}) 184 | assert(zassert(iopub:recv()) == 'ACK') 185 | iopub:send_all({'private_msg', 'exec_count', s.exec_count}) 186 | assert(zassert(iopub:recv()) == 'ACK') 187 | 188 | local line = msg.content.code 189 | 190 | -- help 191 | if line and line:find('^%s-?') then 192 | local pkg = line:gsub('^%s-?','') 193 | line = 'help(' .. pkg .. ')' 194 | end 195 | local cmd = line .. '\n' 196 | if cmd:sub(1,1) == "=" then cmd = "return "..cmd:sub(2) end 197 | 198 | local pok, func, perr, ok, err, output 199 | if line:find(';%s-$') or line:find('^%s-print') then 200 | func, perr = loadstring(cmd) -- syntax error (in semi-colon case) 201 | else -- syntax error in (non-semicolon, so print out the result case) 202 | func, perr = loadstring('local f = function() return '.. line ..' end; local res = {f()}; print(unpack(res))') 203 | if not func then 204 | func, perr = loadstring(cmd) 205 | end 206 | end 207 | if func then 208 | pok = true 209 | -- TODO: for lua outputs to be streamed from the executing command (for example a long for-loop), redefine 'print' to stream-out pyout messages 210 | ok,err = xpcall(func, traceback) 211 | else 212 | ok = false; 213 | err = perr; 214 | end 215 | 216 | if not msg.content.silent and msg.content.store_history then 217 | table.insert(s.history.code, msg.content.code); 218 | table.insert(s.history.output, output); 219 | end 220 | -- pyin -- iopub 221 | local o = util.msg('pyin', msg) 222 | o.content = { 223 | code = msg.content.code, 224 | execution_count = s.exec_count 225 | } 226 | util.ipyEncodeAndSend(iopub, o); 227 | 228 | if ok then 229 | -- pyout (Now handled by IOHandler.lua) 230 | 231 | -- execute_reply -- shell 232 | o = util.msg('execute_reply', msg) 233 | o.content = { 234 | status = 'ok', 235 | execution_count = s.exec_count, 236 | payload = {}, 237 | user_variables = {}, 238 | user_expressions = {} 239 | } 240 | util.ipyEncodeAndSend(sock, o); 241 | elseif pok then -- means function execution had error 242 | -- pyerr -- iopub 243 | o = util.msg('pyerr', msg) 244 | o.content = { 245 | execution_count = s.exec_count, 246 | ename = err or 'Unknown Error', 247 | evalue = '', 248 | traceback = {err} 249 | } 250 | util.ipyEncodeAndSend(iopub, o); 251 | -- execute_reply -- shell 252 | o = util.msg('execute_reply', msg) 253 | o.content = { 254 | status = 'error', 255 | execution_count = s.exec_count, 256 | ename = err or 'Unknown Error', 257 | evalue = '', 258 | traceback = {err} 259 | } 260 | util.ipyEncodeAndSend(sock, o); 261 | else -- code has syntax error 262 | -- pyerr -- iopub 263 | o = util.msg('pyerr', msg) 264 | o.content = { 265 | execution_count = s.exec_count, 266 | ename = err or 'Unknown Error', 267 | evalue = '', 268 | traceback = {perr} 269 | } 270 | util.ipyEncodeAndSend(iopub, o); 271 | -- execute_reply -- shell 272 | o = util.msg('execute_reply', msg) 273 | o.content = { 274 | status = 'error', 275 | execution_count = s.exec_count, 276 | ename = err or 'Unknown Error', 277 | evalue = '', 278 | traceback = {perr} 279 | } 280 | util.ipyEncodeAndSend(sock, o); 281 | end 282 | iopub_router.status(iopub, msg, 'idle'); 283 | end 284 | 285 | shell_router.history_request = function (sock, msg) 286 | print('WARNING: history_request not handled yet'); 287 | end 288 | 289 | shell_router.comm_open = function (sock,msg) 290 | print('WARNING: comm_open not handled yet'); 291 | end 292 | 293 | shell_router.comm_info_request = function (sock,msg) 294 | local reply = util.msg('comm_info_reply', msg) 295 | reply.content = {} 296 | reply.content.comms = {} 297 | util.ipyEncodeAndSend(sock, reply); 298 | end 299 | 300 | local word_break_characters = '[" \t\n\"\\\'><=;:%+%-%*/%%^~#{}%(%)%[%],"]' 301 | 302 | local function extract_completions(text, line, block, pos) 303 | line = line:sub(1,pos) 304 | local matches, word 305 | do -- get matches 306 | local c_word, c_line 307 | local lb = line:gsub(word_break_characters, '*') 308 | local h,p = lb:find('.*%*') 309 | if h then 310 | c_line = line:sub(p+1) 311 | else 312 | c_line = line 313 | end 314 | local h,p = c_line:find('.*%.') 315 | if h then 316 | c_word = c_line:sub(p+1) 317 | else 318 | c_word = c_line; 319 | c_line = '' 320 | end 321 | matches = completer.complete(c_word, c_line, nil, nil) 322 | word = c_word 323 | end 324 | -- now that we got correct matches, create the proper matched_text 325 | for i=1,#matches do 326 | if text ~= '' then 327 | local r,p = text:find('.*' .. word) 328 | local t2 = '' 329 | if r then 330 | t2 = text:sub(1,p-#word) 331 | end 332 | matches[i] = t2 .. matches[i]; 333 | end 334 | end 335 | return { 336 | matches = matches, 337 | matched_text = word, -- line, -- e.g. torch. should become torch.abs 338 | status = 'ok' 339 | } 340 | end 341 | 342 | shell_router.complete_request = function(sock, msg) 343 | local reply = util.msg('complete_reply', msg) 344 | reply.content = extract_completions(msg.content.text and msg.content.text or '', 345 | msg.content.line and msg.content.line or msg.content.code, 346 | msg.content.block, msg.content.cursor_pos) 347 | util.ipyEncodeAndSend(sock, reply); 348 | end 349 | 350 | shell_router.object_info_request = function(sock, msg) 351 | -- print(msg) 352 | -- TODO: I dont understand when this thing is called and when it isn't 353 | end 354 | 355 | --------------------------------------------------------------------------- 356 | local function handleShell(sock) 357 | local msg = util.ipyDecode(sock) 358 | assert(shell_router[msg.header.msg_type], 359 | 'Cannot find appropriate message handler for ' .. msg.header.msg_type) 360 | return shell_router[msg.header.msg_type](sock, msg); 361 | end 362 | 363 | local function handleControl(sock) 364 | local msg = util.ipyDecode(sock); 365 | assert(shell_router[msg.header.msg_type], 366 | 'Cannot find appropriate message handler for ' .. msg.header.msg_type) 367 | return shell_router[msg.header.msg_type](sock, msg); 368 | end 369 | 370 | local function handleStdin(sock) 371 | local buffer = zassert(sock:recv_all()) 372 | zassert(sock:send_all(buffer)) 373 | end 374 | 375 | local function handleIOPub(sock) 376 | local msg = util.ipyDecode(sock); 377 | assert(iopub_router[msg.header.msg_type], 378 | 'Cannot find appropriate message handler for ' .. msg.header.msg_type) 379 | return iopub_router[msg.header.msg_type](sock, msg); 380 | end 381 | 382 | iopub_router.status(iopub, nil, 'starting'); 383 | 384 | loop = zloop.new(1, context) 385 | loop:add_socket(shell, handleShell) 386 | loop:add_socket(control, handleControl) 387 | loop:add_socket(stdin, handleStdin) 388 | loop:add_socket(iopub, handleIOPub) 389 | loop:start() 390 | -------------------------------------------------------------------------------- /screenshots/audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/audio.png -------------------------------------------------------------------------------- /screenshots/autocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/autocomplete.png -------------------------------------------------------------------------------- /screenshots/filters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/filters.png -------------------------------------------------------------------------------- /screenshots/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/help.png -------------------------------------------------------------------------------- /screenshots/hist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/hist.png -------------------------------------------------------------------------------- /screenshots/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/html.png -------------------------------------------------------------------------------- /screenshots/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/image.png -------------------------------------------------------------------------------- /screenshots/quiver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/quiver.png -------------------------------------------------------------------------------- /screenshots/scatter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/scatter.png -------------------------------------------------------------------------------- /screenshots/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/screenshots/video.png -------------------------------------------------------------------------------- /small.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/small.mp4 -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | ]]-- 10 | local Plot = require 'itorch.Plot' 11 | 12 | -- images 13 | itorch.image({image.lena(), image.lena(), image.lena()}) 14 | 15 | require 'nn' 16 | m=nn.SpatialConvolution(3,32,25,25) 17 | itorch.image(m.weight) 18 | 19 | -- audio 20 | itorch.audio('volkswagen.mp3') 21 | 22 | -- video 23 | itorch.video('small.mp4') 24 | 25 | -- html 26 | itorch.html('

Hi there! this is arbitrary HTML

') 27 | -- window_id = itorch.html('

This text will be replaced in 2 seconds

') 28 | -- os.execute('sleep 2') 29 | -- itorch.html('

magic!

', window_id) 30 | 31 | x1 = torch.randn(40):mul(100) 32 | y1 = torch.randn(40):mul(100) 33 | x2 = torch.randn(40):mul(100) 34 | y2 = torch.randn(40):mul(100) 35 | x3 = torch.randn(40):mul(200) 36 | y3 = torch.randn(40):mul(200) 37 | 38 | -- scatter plots 39 | plot = Plot():circle(x1, y1, 'red', 'hi'):circle(x2, y2, 'blue', 'bye'):draw() 40 | plot:circle(x3,y3,'green', 'yolo'):redraw() 41 | plot:title('Scatter Plot Demo'):redraw() 42 | plot:xaxis('length'):yaxis('width'):redraw() 43 | plot:legend(true) 44 | plot:redraw() 45 | -- print(plot:toHTML()) 46 | plot:save('out.html') 47 | 48 | -- line plots 49 | plot = Plot():line(x1, y1,'red','example'):legend(true):title('Line Plot Demo'):draw() 50 | 51 | -- segment plots 52 | plot = Plot():segment(x1, y1, x1+10,y1+10, 'red','demo'):title('Segment Plot Demo'):draw() 53 | 54 | -- quiver plots 55 | xx = torch.linspace(-3,3,10) 56 | yy = torch.linspace(-3,3,10) 57 | local function meshgrid(x,y) 58 | local xx = torch.repeatTensor(x, y:size(1),1) 59 | local yy = torch.repeatTensor(y:view(-1,1), 1, x:size(1)) 60 | return xx, yy 61 | end 62 | Y, X = meshgrid(xx, yy) 63 | U = -torch.pow(X,2) + Y -1 64 | V = X - torch.pow(Y,2) +1 65 | plot = Plot():quiver(U,V,'red','',10):title('Quiver Plot Demo'):draw() 66 | 67 | -- quads/rectangles 68 | x1=torch.randn(10) 69 | y1=torch.randn(10) 70 | plot = Plot():quad(x1,y1,x1+1,y1+1,'red',''):draw() 71 | 72 | -- histogram 73 | plot = Plot():histogram(torch.randn(10000)):draw() 74 | -------------------------------------------------------------------------------- /util.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | ]]-- 10 | local zmq = require 'lzmq' 11 | local zassert = zmq.assert 12 | local json = require 'cjson' 13 | local uuid = require 'uuid' 14 | local crypto = require 'crypto' 15 | 16 | local util = {} 17 | -------------------------------------------------------------- 18 | -- Signature Key and its setter 19 | local session_key = '' 20 | local function setSessionKey(key) 21 | session_key = key 22 | end 23 | -------------------------------------------------------------- 24 | -- Common decoder function for all messages (except heartbeats which are just looped back) 25 | local function ipyDecode(sock, m) 26 | m = m or zassert(sock:recv_all()) 27 | -- print('incoming:') 28 | -- print(m) 29 | local o = {} 30 | o.uuid = {} 31 | local i = -1 32 | for k,v in ipairs(m) do 33 | if v == '' then i = k+1; break; end 34 | o.uuid[k] = v 35 | end 36 | assert(i ~= -1, 'Failed parsing till ') 37 | -- json decode 38 | for j=i+1,i+4 do if m[j] == '{}' then m[j] = nil; else m[j] = json.decode(m[j]); end; end 39 | -- populate headers 40 | o.header = m[i+1] 41 | o.parent_header = m[i+2] 42 | o.metadata = m[i+3] 43 | o.content = m[i+4] 44 | for j=i+5,#m do o.blob = (o.blob or '') .. m[j] end -- process blobs 45 | return o 46 | end 47 | -- Common encoder function for all messages (except heartbeats which are just looped back) 48 | -- See http://ipython.org/ipython-doc/stable/development/messaging.html 49 | local function ipyEncodeAndSend(sock, m) 50 | -- Message digest (for HMAC signature) 51 | local d = crypto.hmac.new('sha256', session_key) 52 | d:update(json.encode(m.header)) 53 | if m.parent_header then d:update(json.encode(m.parent_header)) else d:update('{}') end 54 | if m.metadata then d:update(json.encode(m.metadata)) else d:update('{}') end 55 | if m.content then d:update(json.encode(m.content)) else d:update('{}') end 56 | 57 | local o = {} 58 | for k,v in ipairs(m.uuid) do o[#o+1] = v end 59 | o[#o+1] = '' 60 | o[#o+1] = d:final() 61 | o[#o+1] = json.encode(m.header) 62 | if m.parent_header then o[#o+1] = json.encode(m.parent_header) else o[#o+1] = '{}' end 63 | if m.metadata then o[#o+1] = json.encode(m.metadata) else o[#o+1] = '{}' end 64 | if m.content then o[#o+1] = json.encode(m.content) else o[#o+1] = '{}' end 65 | if m.blob then o[#o+1] = m.blob end 66 | -- print('outgoing:') 67 | -- print(o) 68 | zassert(sock:send_all(o)) 69 | end 70 | 71 | local session_id = uuid.new() 72 | -- function for creating a new message object 73 | local function msg(msg_type, parent) 74 | local m = {} 75 | m.header = {} 76 | if parent then 77 | m.uuid = parent.uuid 78 | m.parent_header = parent.header 79 | else 80 | m.parent_header = {} 81 | end 82 | m.header.msg_id = uuid.new() 83 | m.header.msg_type = msg_type 84 | m.header.session = session_id 85 | m.header.date = os.date("%Y-%m-%dT%H:%M:%S") 86 | m.header.username = 'itorch' 87 | m.content = {} 88 | return m 89 | end 90 | 91 | --------------------------------------------------------------------------- 92 | 93 | util.ipyDecode = ipyDecode 94 | util.ipyEncodeAndSend = ipyEncodeAndSend 95 | util.msg = msg 96 | util.setSessionKey = setSessionKey 97 | 98 | return util 99 | -------------------------------------------------------------------------------- /volkswagen.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookarchive/iTorch/488ac0149b67035c1bf362d04929ea0c21fe61fa/volkswagen.mp3 --------------------------------------------------------------------------------