├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── baboon2.png └── example.js ├── package.json ├── resample.js └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | - "0.12" 6 | before_install: 7 | - npm install -g npm@">=1.4.6" 8 | sudo: false 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Mikola Lysenko 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ndarray-resample 2 | ================ 3 | Resamples an ndarray by an arbitrary (rational) factor using a sinc kernel. 4 | 5 | [![build status](https://secure.travis-ci.org/scijs/ndarray-resample.png)](http://travis-ci.org/scijs/ndarray-resample) 6 | 7 | ## Example 8 | Here is a simple example showing how to downsample an image: 9 | 10 | ```javascript 11 | var baboon = require("luminance")(require("baboon-image")) 12 | var x = require("zeros")([256,256]) 13 | require("ndarray-resample")(x, baboon) 14 | require("save-pixels")(x, "png").pipe(process.stdout) 15 | ``` 16 | 17 | #### Output 18 | ![Output](example/baboon2.png) 19 | 20 | ##Install 21 | Install using [npm](https://www.npmjs.com/): 22 | 23 | npm install ndarray-resample 24 | 25 | ## API 26 | #### `require("ndarray-resample")(output, input[, clamp_lo, clamp_hi])` 27 | Resamples input by a factor of `output.shape/input.shape`, storing the result in output (this also means the factor can differ per dimension). 28 | 29 | * `output` gets the result of resampling 30 | * `input` is the array that gets resampled 31 | * `clamp_lo` is a threshold placed on the pixels 32 | * `clamp_hi` is an upper threhsold placed on the pixels 33 | 34 | **Note** that the pixel at the "origin" in the output corresponds to the pixel at the origin in the input. Also, the boundary conditions are periodic (for now). 35 | 36 | ## License 37 | (c) 2013-2015 Mikola Lysenko, Jasper van de Gronde. MIT License 38 | -------------------------------------------------------------------------------- /example/baboon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/ndarray-resample/73fcb86993d8cb3bad0f7fd829b95a912101c994/example/baboon2.png -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | var baboon = require("luminance")(require("baboon-image")) 2 | var x = require("zeros")([256,256]) 3 | require("../resample.js")(x, baboon) 4 | require("save-pixels")(x, "png").pipe(process.stdout) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ndarray-resample", 3 | "version": "1.0.1", 4 | "description": "Resample using sinc kernels", 5 | "main": "resample.js", 6 | "directories": { 7 | "example": "example" 8 | }, 9 | "browserify": { 10 | "transform": ["cwise"] 11 | }, 12 | "dependencies": { 13 | "ndarray-ops": "^1.2.2", 14 | "ndarray-fft": "^1.0.0", 15 | "ndarray-scratch": "^1.1.1", 16 | "cwise": "^1.0.7" 17 | }, 18 | "devDependencies": { 19 | "ndarray": "^1.0.18", 20 | "baboon-image": "^2.0.0", 21 | "luminance": "^1.0.3", 22 | "ndarray-unpack": "^1.0.0", 23 | "save-pixels": "^2.2.0", 24 | "zeros": "^1.0.0", 25 | "tape": "^4.0.0", 26 | "test-fuzzy-array": "^1.0.1" 27 | }, 28 | "scripts": { 29 | "test": "tape test/*.js" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "git://github.com/scijs/ndarray-resample.git" 34 | }, 35 | "keywords": [ 36 | "scijs", 37 | "downsample", 38 | "upsample", 39 | "resample", 40 | "ndarray", 41 | "sinc", 42 | "interpolation" 43 | ], 44 | "author": "Mikola Lysenko", 45 | "contributors": [ 46 | "Jasper van de Gronde " 47 | ], 48 | "license": "MIT", 49 | "readmeFilename": "README.md", 50 | "gitHead": "5018571a31388da17be61d3a026df4ac0b2d179d", 51 | "bugs": { 52 | "url": "https://github.com/scijs/ndarray-resample/issues" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /resample.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var fft = require("ndarray-fft") 4 | var pool = require("ndarray-scratch") 5 | var ops = require("ndarray-ops") 6 | var cwise = require("cwise") 7 | 8 | var clampScale = cwise({ 9 | args:["array", "array", "scalar", "scalar", "scalar"], 10 | body: function clampScale(out, inp, s, l, h) { 11 | var x = inp * s 12 | if(x < l) { x = l } 13 | if(x > h) { x = h } 14 | out = x 15 | } 16 | }) 17 | 18 | 19 | function resample(out, inp, clamp_lo, clamp_hi) { 20 | if(typeof clamp_lo === "undefined") { 21 | clamp_lo = -Infinity 22 | } 23 | if(typeof clamp_hi === "undefined") { 24 | clamp_hi = Infinity 25 | } 26 | 27 | var ishp = inp.shape 28 | var oshp = out.shape 29 | 30 | if (inp.shape.length !== out.shape.length) throw new Error("ndarray-resample: input and output arrays should have the same dimensions") 31 | 32 | var v, zeroInds = ishp.map(function(){return 0}) 33 | if(out.size === 1) { 34 | v = ops.sum(inp)/inp.size 35 | if(v < clamp_lo) { v = clamp_lo } 36 | if(v > clamp_hi) { v = clamp_hi } 37 | out.set.apply(out, zeroInds.concat(v)) 38 | return 39 | } else if (inp.size === 1) { 40 | v = inp.get.apply(inp, zeroInds) 41 | if(v < clamp_lo) { v = clamp_lo } 42 | if(v > clamp_hi) { v = clamp_hi } 43 | ops.assigns(out, v) 44 | return 45 | } 46 | 47 | var d = ishp.length 48 | var mshp = new Array(d), initToZero = false 49 | for(var i=0; i ishp[i]) initToZero = true // When upsampling, initialize the Fourier components of the output to zero 52 | } 53 | 54 | var x = pool.malloc(ishp) 55 | , y = pool.malloc(ishp) 56 | 57 | ops.assign(x, inp) 58 | ops.assigns(y, 0.0) 59 | 60 | fft(1, x, y) 61 | 62 | var lo = x.lo 63 | , hi = x.hi 64 | 65 | var s = pool.malloc(oshp) 66 | , t = pool.malloc(oshp) 67 | if (initToZero) { 68 | ops.assigns(s, 0.0) 69 | ops.assigns(t, 0.0) 70 | } 71 | 72 | var nr = new Array(d) 73 | , a = new Array(d) 74 | , b = new Array(d) 75 | , io = new Array(d) 76 | for(var i=0; i<1<>>1 // Take ceil(mshp[j]/2)) low frequencies (for example [0,1] for both mshp[j]==3 and mshp[j]==4) 80 | a[j] = 0 81 | b[j] = 0 82 | io[j] = 0 83 | } else { // Take the negative frequencies for this dimension 84 | nr[j] = mshp[j] - ((mshp[j]+1)>>>1) // Take the rest ([-1] for mshp[j]==3, and [-2,-1] for mshp[j]==4) 85 | if(nr[j] === 0) { 86 | continue 87 | } 88 | a[j] = oshp[j] - nr[j] 89 | b[j] = ishp[j] - nr[j] 90 | // If mshp[j] is even, set the first imaginary values (along this dimension) to zero. 91 | // For example, if mshp[j]==4, 2 and -2 correspond to the same frequency, and should be the average of the amplitudes for 2 and -2. 92 | // Since the input is real, the Fourier transform has Hermitian symmetry, and we can simply take one or the other and set the corresponding imaginary coefficient(s) to zero. 93 | // Note that when upsampling, this means that we get a asymmetric response (for example, -2, but not 2 has a non-zero weight), but this does not matter, since the weight is real anyway (again, given Hermitian symmetry). 94 | io[j] = (mshp[j]&1) ? 0 : 1 95 | } 96 | } 97 | ops.assign(hi.apply(lo.apply(s, a), nr), hi.apply(lo.apply(x, b), nr)) 98 | ops.assign(lo.apply(hi.apply(lo.apply(t, a), nr), io), lo.apply(hi.apply(lo.apply(y, b), nr), io)) 99 | ops.assigns(hi.apply(hi.apply(lo.apply(t, a), nr), io), 0.0) 100 | } 101 | 102 | fft(-1, s, t) 103 | clampScale(out, s, out.size/inp.size, clamp_lo, clamp_hi) 104 | 105 | pool.free(x) 106 | pool.free(y) 107 | pool.free(s) 108 | pool.free(t) 109 | } 110 | 111 | module.exports = resample -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var resample = require("../resample.js") 2 | var ndarray = require("ndarray") 3 | var fuzz = require("test-fuzzy-array") 4 | 5 | require("tape")("resample 1D", function(t) { 6 | var almostEqual = fuzz(t, 0.000001) 7 | var array1 = ndarray([3.5]) 8 | , array1Out = ndarray([0]) 9 | , array3 = ndarray([1,2,3]) 10 | , array6Out = ndarray([0,0,0,0,0,0]) 11 | , ref3 = [1,1,2,3,3,2] 12 | , array4 = ndarray([1,2,3,4]) 13 | , array8Out = ndarray([0,0,0,0,0,0,0,0]) 14 | , ref4 = [1.,1.0857864376269049,2.,2.5,3.,3.914213562373095,4.,2.5] 15 | , array6 = ndarray([1,2,3,4,5,6]) 16 | , array3Out = ndarray([0,0,0]) 17 | , ref6 = [2.5,2.5,5.5] 18 | , array8 = ndarray([1,2,3,4,5,6,7,8]) 19 | , array4Out = ndarray([0,0,0,0]) 20 | , ref8 = [3.,2.585786437626905,5.,7.414213562373095] 21 | , ref83 = [3.5,2.9092297248239727,7.090770275176027] 22 | 23 | resample(array3Out, array1) 24 | almostEqual(array3Out.data, [3.5,3.5,3.5], "input length: 1, output length: 6, not clamped") 25 | 26 | resample(array1Out, array6) 27 | almostEqual(array1Out.data, [3.5], "input length: 6, output length: 1, not clamped") 28 | 29 | resample(array3Out, array6) 30 | almostEqual(array3Out.data, ref6, "input length: 6, output length: 3, not clamped") 31 | 32 | resample(array6Out, array3) 33 | almostEqual(array6Out.data, ref3, "input length: 3, output length: 6, not clamped") 34 | 35 | resample(array4Out, array8) 36 | almostEqual(array4Out.data, ref8, "input length: 8, output length: 4, not clamped") 37 | 38 | resample(array8Out, array4) 39 | almostEqual(array8Out.data, ref4, "input length: 4, output length: 8, not clamped") 40 | 41 | resample(array3Out, array8) 42 | almostEqual(array3Out.data, ref83, "input length: 8, output length: 3, not clamped") 43 | 44 | t.end() 45 | }) --------------------------------------------------------------------------------