Welis Garagen-Anzeige

This commit is contained in:
2018-02-04 20:14:45 +01:00
parent 9b4b42bdc0
commit 48d7ac9661
855 changed files with 134068 additions and 5 deletions
+17
View File
@@ -0,0 +1,17 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
node_modules/*
*.DS_Store
test/*
+6
View File
@@ -0,0 +1,6 @@
language: node_js
node_js:
- "0.8"
- "0.10"
before_install:
- npm install -g npm@~1.4.6
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2013 Mikola Lysenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+253
View File
@@ -0,0 +1,253 @@
ndarray
=======
Modular multidimensional arrays for JavaScript.
[![browser support](https://ci.testling.com/mikolalysenko/ndarray.png)
](https://ci.testling.com/mikolalysenko/ndarray)
[![build status](https://secure.travis-ci.org/mikolalysenko/ndarray.png)](http://travis-ci.org/mikolalysenko/ndarray)
[![stable](https://rawgithub.com/hughsk/stability-badges/master/dist/frozen.svg)](http://github.com/hughsk/stability-badges)
#### [Big list of ndarray modules](https://github.com/mikolalysenko/ndarray/wiki/ndarray-module-list#core-module)
Introduction
============
`ndarrays` provide higher dimensional views of 1D arrays. For example, here is how you can turn a length 4 typed array into an nd-array:
```javascript
var mat = ndarray(new Float64Array([1, 0, 0, 1]), [2,2])
//Now:
//
// mat = 1 0
// 0 1
//
```
Once you have an nd-array you can access elements using `.set` and `.get`. For example, here is an implementation of [Conway's game of life](http://en.wikipedia.org/wiki/Conway's_Game_of_Life) using ndarrays:
```javascript
function stepLife(next_state, cur_state) {
//Get array shape
var nx = cur_state.shape[0],
ny = cur_state.shape[1]
//Loop over all cells
for(var i=1; i<nx-1; ++i) {
for(var j=1; j<ny-1; ++j) {
//Count neighbors
var n = 0
for(var dx=-1; dx<=1; ++dx) {
for(var dy=-1; dy<=1; ++dy) {
if(dx === 0 && dy === 0) {
continue
}
n += cur_state.get(i+dx, j+dy)
}
}
//Update state according to rule
if(n === 3 || n === 3 + cur_state.get(i,j)) {
next_state.set(i,j,1)
} else {
next_state.set(i,j,0)
}
}
}
}
```
You can also pull out views of ndarrays without copying the underlying elements. Here is an example showing how to update part of a subarray:
```javascript
var x = ndarray(new Float32Array(25), [5, 5])
var y = x.hi(4,4).lo(1,1)
for(var i=0; i<y.shape[0]; ++i) {
for(var j=0; j<y.shape[1]; ++j) {
y.set(i,j,1)
}
}
//Now:
// x = 0 0 0 0 0
// 0 1 1 1 0
// 0 1 1 1 0
// 0 1 1 1 0
// 0 0 0 0 0
```
ndarrays can be transposed, flipped, sheared and sliced in constant time per operation. They are useful for representing images, audio, volume graphics, matrices, strings and much more. They work both in node.js and with [browserify](http://browserify.org/).
Install
=======
Install the library using [npm](http://npmjs.org):
```sh
npm install ndarray
```
You can also use ndarrays in a browser with any tool that follows the CommonJS/node module conventions. The most direct way to do this is to use [browserify](https://github.com/substack/node-browserify). If you want live-reloading for faster debugging, check out [beefy](https://github.com/chrisdickinson/beefy).
API
===
Once you have ndarray installed, you can use it in your project as follows:
```javascript
var ndarray = require("ndarray")
```
## Constructor
### `ndarray(data[, shape, stride, offset])`
The default `module.exports` method is the constructor for ndarrays. It creates an n-dimensional array view wrapping an underlying storage type
* `data` is a 1D array storage. It is either an instance of `Array`, a typed array, or an object that implements `get(), set(), .length`
* `shape` is the shape of the view (Default: `data.length`)
* `stride` is the resulting stride of the new array. (Default: row major)
* `offset` is the offset to start the view (Default: `0`)
**Returns** an n-dimensional array view of the buffer
## Members
The central concept in `ndarray` is the idea of a view. The way these work is very similar to [SciPy's array slices](http://docs.scipy.org/doc/numpy/reference/arrays.indexing.html). Views are affine projections to 1D storage types. To better understand what this means, let's first look at the properties of the view object. It has exactly 4 variables:
* `array.data` - The underlying 1D storage for the multidimensional array
* `array.shape` - The shape of the typed array
* `array.stride` - The layout of the typed array in memory
* `array.offset` - The starting offset of the array in memory
Keeping a separate stride means that we can use the same data structure to support both [row major and column major storage](http://en.wikipedia.org/wiki/Row-major_order)
## Element Access
To access elements of the array, you can use the `set/get` methods:
### `array.get(i,j,...)`
Retrieves element `i,j,...` from the array. In psuedocode, this is implemented as follows:
```javascript
function get(i,j,...) {
return this.data[this.offset + this.stride[0] * i + this.stride[1] * j + ... ]
}
```
### `array.set(i,j,...,v)`
Sets element `i,j,...` to `v`. Again, in psuedocode this works like this:
```javascript
function set(i,j,...,v) {
return this.data[this.offset + this.stride[0] * i + this.stride[1] * j + ... ] = v
}
```
### `array.index(i,j, ...)`
Retrieves the index of the cell in the underlying ndarray. In JS,
```javascript
function index(i,j, ...) {
return this.offset + this.stride[0] * i + this.stride[1] * j + ...
}
```
## Properties
The following properties are created using Object.defineProperty and do not take up any physical memory. They can be useful in calculations involving ndarrays
### `array.dtype`
Returns a string representing the undelying data type of the ndarray. Excluding generic data stores these types are compatible with [`typedarray-pool`](https://github.com/mikolalysenko/typedarray-pool). This is mapped according to the following rules:
Data type | String
--------: | :-----
`Int8Array` | "int8"
`Int16Array` | "int16"
`Int32Array` | "int32"
`Uint8Array` | "uint8"
`Uint16Array` | "uint16"
`Uint32Array` | "uint32"
`Float32Array` | "float32"
`Float64Array` | "float64"
`Array` | "array"
`Uint8ArrayClamped` | "uint8_clamped"
`Buffer` | "buffer"
Other | "generic"
Generic arrays access elements of the underlying 1D store using get()/set() instead of array accessors.
### `array.size`
Returns the size of the array in logical elements.
### `array.order`
Returns the order of the stride of the array, sorted in ascending length. The first element is the first index of the shortest stride and the last is the index the longest stride.
### `array.dimension`
Returns the dimension of the array.
## Slicing
Given a view, we can change the indexing by shifting, truncating or permuting the strides. This lets us perform operations like array reversals or matrix transpose in **constant time** (well, technically `O(shape.length)`, but since shape.length is typically less than 4, it might as well be). To make life simpler, the following interfaces are exposed:
### `array.lo(i,j,k,...)`
This creates a shifted view of the array. Think of it as taking the upper left corner of the image and dragging it inward by an amount equal to `(i,j,k...)`.
### `array.hi(i,j,k,...)`
This does the dual of `array.lo()`. Instead of shifting from the top-left, it truncates from the bottom-right of the array, returning a smaller array object. Using `hi` and `lo` in combination lets you select ranges in the middle of an array.
**Note:** `hi` and `lo` do not commute. In general:
```javascript
a.hi(3,3).lo(3,3) != a.lo(3,3).hi(3,3)
```
### `array.step(i,j,k...)`
Changes the stride length by rescaling. Negative indices flip axes. For example, here is how you create a reversed view of a 1D array:
```javascript
var reversed = a.step(-1)
```
You can also change the step size to be greater than 1 if you like, letting you skip entries of a list. For example, here is how to split an array into even and odd components:
```javascript
var evens = a.step(2)
var odds = a.lo(1).step(2)
```
### `array.transpose(p0, p1, ...)`
Finally, for higher dimensional arrays you can transpose the indices in place. This has the effect of permuting the shape and stride values. For example, in a 2D array you can calculate the matrix transpose by:
```javascript
M.transpose(1, 0)
```
Or if you have a 3D volume image, you can shift the axes using more generic transformations:
```javascript
volume.transpose(2, 0, 1)
```
### `array.pick(p0, p1, ...)`
You can also pull out a subarray from an ndarray by fixing a particular axis. The way this works is you specify the direction you are picking by giving a list of values. For example, if you have an image stored as an nxmx3 array you can pull out the channel as follows:
```javascript
var red = image.pick(null, null, 0)
var green = image.pick(null, null, 1)
var blue = image.pick(null, null, 2)
```
As the above example illustrates, passing a negative or non-numeric value to a coordinate in pick skips that index.
# More information
For more discussion about ndarrays, here are some talks, tutorials and articles about them:
* [ndarray presentation](http://mikolalysenko.github.io/ndarray-presentation/)
* [Implementing multidimensional arrays in JavaScript](http://0fps.wordpress.com/2013/05/22/implementing-multidimensional-arrays-in-javascript/)
* [Cache oblivious array operations](http://0fps.wordpress.com/2013/05/28/cache-oblivious-array-operations/)
* [Some experiments](https://github.com/mikolalysenko/ndarray-experiments)
Credits
=======
(c) 2013 Mikola Lysenko. MIT License
+343
View File
@@ -0,0 +1,343 @@
var iota = require("iota-array")
var isBuffer = require("is-buffer")
var hasTypedArrays = ((typeof Float64Array) !== "undefined")
function compare1st(a, b) {
return a[0] - b[0]
}
function order() {
var stride = this.stride
var terms = new Array(stride.length)
var i
for(i=0; i<terms.length; ++i) {
terms[i] = [Math.abs(stride[i]), i]
}
terms.sort(compare1st)
var result = new Array(terms.length)
for(i=0; i<result.length; ++i) {
result[i] = terms[i][1]
}
return result
}
function compileConstructor(dtype, dimension) {
var className = ["View", dimension, "d", dtype].join("")
if(dimension < 0) {
className = "View_Nil" + dtype
}
var useGetters = (dtype === "generic")
if(dimension === -1) {
//Special case for trivial arrays
var code =
"function "+className+"(a){this.data=a;};\
var proto="+className+".prototype;\
proto.dtype='"+dtype+"';\
proto.index=function(){return -1};\
proto.size=0;\
proto.dimension=-1;\
proto.shape=proto.stride=proto.order=[];\
proto.lo=proto.hi=proto.transpose=proto.step=\
function(){return new "+className+"(this.data);};\
proto.get=proto.set=function(){};\
proto.pick=function(){return null};\
return function construct_"+className+"(a){return new "+className+"(a);}"
var procedure = new Function(code)
return procedure()
} else if(dimension === 0) {
//Special case for 0d arrays
var code =
"function "+className+"(a,d) {\
this.data = a;\
this.offset = d\
};\
var proto="+className+".prototype;\
proto.dtype='"+dtype+"';\
proto.index=function(){return this.offset};\
proto.dimension=0;\
proto.size=1;\
proto.shape=\
proto.stride=\
proto.order=[];\
proto.lo=\
proto.hi=\
proto.transpose=\
proto.step=function "+className+"_copy() {\
return new "+className+"(this.data,this.offset)\
};\
proto.pick=function "+className+"_pick(){\
return TrivialArray(this.data);\
};\
proto.valueOf=proto.get=function "+className+"_get(){\
return "+(useGetters ? "this.data.get(this.offset)" : "this.data[this.offset]")+
"};\
proto.set=function "+className+"_set(v){\
return "+(useGetters ? "this.data.set(this.offset,v)" : "this.data[this.offset]=v")+"\
};\
return function construct_"+className+"(a,b,c,d){return new "+className+"(a,d)}"
var procedure = new Function("TrivialArray", code)
return procedure(CACHED_CONSTRUCTORS[dtype][0])
}
var code = ["'use strict'"]
//Create constructor for view
var indices = iota(dimension)
var args = indices.map(function(i) { return "i"+i })
var index_str = "this.offset+" + indices.map(function(i) {
return "this.stride[" + i + "]*i" + i
}).join("+")
var shapeArg = indices.map(function(i) {
return "b"+i
}).join(",")
var strideArg = indices.map(function(i) {
return "c"+i
}).join(",")
code.push(
"function "+className+"(a," + shapeArg + "," + strideArg + ",d){this.data=a",
"this.shape=[" + shapeArg + "]",
"this.stride=[" + strideArg + "]",
"this.offset=d|0}",
"var proto="+className+".prototype",
"proto.dtype='"+dtype+"'",
"proto.dimension="+dimension)
//view.size:
code.push("Object.defineProperty(proto,'size',{get:function "+className+"_size(){\
return "+indices.map(function(i) { return "this.shape["+i+"]" }).join("*"),
"}})")
//view.order:
if(dimension === 1) {
code.push("proto.order=[0]")
} else {
code.push("Object.defineProperty(proto,'order',{get:")
if(dimension < 4) {
code.push("function "+className+"_order(){")
if(dimension === 2) {
code.push("return (Math.abs(this.stride[0])>Math.abs(this.stride[1]))?[1,0]:[0,1]}})")
} else if(dimension === 3) {
code.push(
"var s0=Math.abs(this.stride[0]),s1=Math.abs(this.stride[1]),s2=Math.abs(this.stride[2]);\
if(s0>s1){\
if(s1>s2){\
return [2,1,0];\
}else if(s0>s2){\
return [1,2,0];\
}else{\
return [1,0,2];\
}\
}else if(s0>s2){\
return [2,0,1];\
}else if(s2>s1){\
return [0,1,2];\
}else{\
return [0,2,1];\
}}})")
}
} else {
code.push("ORDER})")
}
}
//view.set(i0, ..., v):
code.push(
"proto.set=function "+className+"_set("+args.join(",")+",v){")
if(useGetters) {
code.push("return this.data.set("+index_str+",v)}")
} else {
code.push("return this.data["+index_str+"]=v}")
}
//view.get(i0, ...):
code.push("proto.get=function "+className+"_get("+args.join(",")+"){")
if(useGetters) {
code.push("return this.data.get("+index_str+")}")
} else {
code.push("return this.data["+index_str+"]}")
}
//view.index:
code.push(
"proto.index=function "+className+"_index(", args.join(), "){return "+index_str+"}")
//view.hi():
code.push("proto.hi=function "+className+"_hi("+args.join(",")+"){return new "+className+"(this.data,"+
indices.map(function(i) {
return ["(typeof i",i,"!=='number'||i",i,"<0)?this.shape[", i, "]:i", i,"|0"].join("")
}).join(",")+","+
indices.map(function(i) {
return "this.stride["+i + "]"
}).join(",")+",this.offset)}")
//view.lo():
var a_vars = indices.map(function(i) { return "a"+i+"=this.shape["+i+"]" })
var c_vars = indices.map(function(i) { return "c"+i+"=this.stride["+i+"]" })
code.push("proto.lo=function "+className+"_lo("+args.join(",")+"){var b=this.offset,d=0,"+a_vars.join(",")+","+c_vars.join(","))
for(var i=0; i<dimension; ++i) {
code.push(
"if(typeof i"+i+"==='number'&&i"+i+">=0){\
d=i"+i+"|0;\
b+=c"+i+"*d;\
a"+i+"-=d}")
}
code.push("return new "+className+"(this.data,"+
indices.map(function(i) {
return "a"+i
}).join(",")+","+
indices.map(function(i) {
return "c"+i
}).join(",")+",b)}")
//view.step():
code.push("proto.step=function "+className+"_step("+args.join(",")+"){var "+
indices.map(function(i) {
return "a"+i+"=this.shape["+i+"]"
}).join(",")+","+
indices.map(function(i) {
return "b"+i+"=this.stride["+i+"]"
}).join(",")+",c=this.offset,d=0,ceil=Math.ceil")
for(var i=0; i<dimension; ++i) {
code.push(
"if(typeof i"+i+"==='number'){\
d=i"+i+"|0;\
if(d<0){\
c+=b"+i+"*(a"+i+"-1);\
a"+i+"=ceil(-a"+i+"/d)\
}else{\
a"+i+"=ceil(a"+i+"/d)\
}\
b"+i+"*=d\
}")
}
code.push("return new "+className+"(this.data,"+
indices.map(function(i) {
return "a" + i
}).join(",")+","+
indices.map(function(i) {
return "b" + i
}).join(",")+",c)}")
//view.transpose():
var tShape = new Array(dimension)
var tStride = new Array(dimension)
for(var i=0; i<dimension; ++i) {
tShape[i] = "a[i"+i+"]"
tStride[i] = "b[i"+i+"]"
}
code.push("proto.transpose=function "+className+"_transpose("+args+"){"+
args.map(function(n,idx) { return n + "=(" + n + "===undefined?" + idx + ":" + n + "|0)"}).join(";"),
"var a=this.shape,b=this.stride;return new "+className+"(this.data,"+tShape.join(",")+","+tStride.join(",")+",this.offset)}")
//view.pick():
code.push("proto.pick=function "+className+"_pick("+args+"){var a=[],b=[],c=this.offset")
for(var i=0; i<dimension; ++i) {
code.push("if(typeof i"+i+"==='number'&&i"+i+">=0){c=(c+this.stride["+i+"]*i"+i+")|0}else{a.push(this.shape["+i+"]);b.push(this.stride["+i+"])}")
}
code.push("var ctor=CTOR_LIST[a.length+1];return ctor(this.data,a,b,c)}")
//Add return statement
code.push("return function construct_"+className+"(data,shape,stride,offset){return new "+className+"(data,"+
indices.map(function(i) {
return "shape["+i+"]"
}).join(",")+","+
indices.map(function(i) {
return "stride["+i+"]"
}).join(",")+",offset)}")
//Compile procedure
var procedure = new Function("CTOR_LIST", "ORDER", code.join("\n"))
return procedure(CACHED_CONSTRUCTORS[dtype], order)
}
function arrayDType(data) {
if(isBuffer(data)) {
return "buffer"
}
if(hasTypedArrays) {
switch(Object.prototype.toString.call(data)) {
case "[object Float64Array]":
return "float64"
case "[object Float32Array]":
return "float32"
case "[object Int8Array]":
return "int8"
case "[object Int16Array]":
return "int16"
case "[object Int32Array]":
return "int32"
case "[object Uint8Array]":
return "uint8"
case "[object Uint16Array]":
return "uint16"
case "[object Uint32Array]":
return "uint32"
case "[object Uint8ClampedArray]":
return "uint8_clamped"
}
}
if(Array.isArray(data)) {
return "array"
}
return "generic"
}
var CACHED_CONSTRUCTORS = {
"float32":[],
"float64":[],
"int8":[],
"int16":[],
"int32":[],
"uint8":[],
"uint16":[],
"uint32":[],
"array":[],
"uint8_clamped":[],
"buffer":[],
"generic":[]
}
;(function() {
for(var id in CACHED_CONSTRUCTORS) {
CACHED_CONSTRUCTORS[id].push(compileConstructor(id, -1))
}
});
function wrappedNDArrayCtor(data, shape, stride, offset) {
if(data === undefined) {
var ctor = CACHED_CONSTRUCTORS.array[0]
return ctor([])
} else if(typeof data === "number") {
data = [data]
}
if(shape === undefined) {
shape = [ data.length ]
}
var d = shape.length
if(stride === undefined) {
stride = new Array(d)
for(var i=d-1, sz=1; i>=0; --i) {
stride[i] = sz
sz *= shape[i]
}
}
if(offset === undefined) {
offset = 0
for(var i=0; i<d; ++i) {
if(stride[i] < 0) {
offset -= (shape[i]-1)*stride[i]
}
}
}
var dtype = arrayDType(data)
var ctor_list = CACHED_CONSTRUCTORS[dtype]
while(ctor_list.length <= d+1) {
ctor_list.push(compileConstructor(dtype, ctor_list.length-1))
}
var ctor = ctor_list[d+1]
return ctor(data, shape, stride, offset)
}
module.exports = wrappedNDArrayCtor
+146
View File
@@ -0,0 +1,146 @@
{
"_args": [
[
{
"raw": "ndarray@^1.0.13",
"scope": null,
"escapedName": "ndarray",
"name": "ndarray",
"rawSpec": "^1.0.13",
"spec": ">=1.0.13 <2.0.0",
"type": "range"
},
"/Users/gerrit/Documents/node.js-Projects/tinkerforge/node_modules/get-pixels"
]
],
"_from": "ndarray@>=1.0.13 <2.0.0",
"_id": "ndarray@1.0.18",
"_inCache": true,
"_location": "/ndarray",
"_nodeVersion": "0.12.2",
"_npmUser": {
"name": "mikolalysenko",
"email": "mikolalysenko@gmail.com"
},
"_npmVersion": "2.7.4",
"_phantomChildren": {},
"_requested": {
"raw": "ndarray@^1.0.13",
"scope": null,
"escapedName": "ndarray",
"name": "ndarray",
"rawSpec": "^1.0.13",
"spec": ">=1.0.13 <2.0.0",
"type": "range"
},
"_requiredBy": [
"/get-pixels",
"/ndarray-pack"
],
"_resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.18.tgz",
"_shasum": "b60d3a73224ec555d0faa79711e502448fd3f793",
"_shrinkwrap": null,
"_spec": "ndarray@^1.0.13",
"_where": "/Users/gerrit/Documents/node.js-Projects/tinkerforge/node_modules/get-pixels",
"author": {
"name": "Mikola Lysenko"
},
"bugs": {
"url": "https://github.com/mikolalysenko/ndarray/issues"
},
"dependencies": {
"iota-array": "^1.0.0",
"is-buffer": "^1.0.2"
},
"description": "Multidimensional Arrays",
"devDependencies": {
"dup": "^1.0.0",
"invert-permutation": "^1.0.0",
"permutation-rank": "^1.0.0",
"tape": "^2.12.3"
},
"directories": {
"test": "test"
},
"dist": {
"shasum": "b60d3a73224ec555d0faa79711e502448fd3f793",
"tarball": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.18.tgz"
},
"gitHead": "a85785ca7a7e12c3fc29671a4f7c214bebc4ddc7",
"homepage": "https://github.com/mikolalysenko/ndarray",
"keywords": [
"ndarray",
"array",
"multi",
"multidimensional",
"dimension",
"higher",
"image",
"volume",
"webgl",
"tensor",
"matrix",
"linear",
"algebra",
"science",
"numerical",
"computing",
"stride",
"shape"
],
"license": "MIT",
"main": "ndarray.js",
"maintainers": [
{
"name": "mikolalysenko",
"email": "mikolalysenko@gmail.com"
},
{
"name": "jaspervdg",
"email": "th.v.d.gronde@hccnet.nl"
},
{
"name": "rreusser",
"email": "rsreusser@gmail.com"
},
{
"name": "planeshifter",
"email": "pgb@andrew.cmu.edu"
},
{
"name": "hughsk",
"email": "hughskennedy@gmail.com"
},
{
"name": "substack",
"email": "substack@gmail.com"
}
],
"name": "ndarray",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git://github.com/mikolalysenko/ndarray.git"
},
"scripts": {
"test": "tape test/*.js"
},
"testling": {
"files": "test/*.js",
"browsers": [
"ie/10..latest",
"firefox/17..latest",
"firefox/nightly",
"chrome/22..latest",
"chrome/canary",
"opera/12..latest",
"opera/next",
"safari/6.0..latest",
"ipad/6.0..latest",
"iphone/6.0..latest",
"android-browser/4.2..latest"
]
},
"version": "1.0.18"
}