Lutz Roeder 7 лет назад
Родитель
Сommit
5481040b60
12 измененных файлов с 437 добавлено и 93 удалено
  1. 1 0
      setup.py
  2. 28 5
      src/caffe-model.js
  3. 31 10
      src/coreml-model.js
  4. 24 4
      src/keras-model.js
  5. 41 21
      src/mxnet-model.js
  6. 206 0
      src/numpy.js
  7. 31 11
      src/onnx-model.js
  8. 24 10
      src/tf-model.js
  9. 39 26
      src/tflite-model.js
  10. 3 0
      src/view-electron.js
  11. 8 5
      src/view-sidebar.js
  12. 1 1
      src/view.js

+ 1 - 0
setup.py

@@ -86,6 +86,7 @@ setuptools.setup(
             'caffe-model.js', 'caffe-metadata.json', 'caffe.js',
             'caffe2-model.js', 'caffe2-metadata.json', 'caffe2.js',
             'mxnet-model.js', 'mxnet-metadata.json', 'unzip.js',
+            'numpy.js',
             'view-browser.html', 'view-browser.js',
             'view-render.css', 'view-render.js',
             'view-sidebar.css', 'view-sidebar.js',

+ 28 - 5
src/caffe-model.js

@@ -295,7 +295,7 @@ class CaffeNode {
             input.connections.forEach((connection) => {
                 if (connection.id instanceof CaffeTensor) {
                     connection.initializer = connection.id;
-                    connection.type = connection.initializer.type;
+                    connection.type = connection.initializer.type.toString();
                     connection.id = '';
                 }
             });
@@ -413,15 +413,17 @@ class CaffeTensor {
             this._shape = blob.shape.dim;
         }
 
-        this._type = '?';
+        var dataType = '?';
         if (blob.data.length > 0) {
-            this._type = 'float';
+            dataType = 'float32';
             this._data = blob.data;
         }
         else if (blob.doubleData.length > 0) {
-            this._type = 'double';
+            dataType = 'float64';
             this._data = blob.doubleData;
         }
+
+        this._type = new CaffeTensorType(dataType, this._shape);
     }
 
     get kind() {
@@ -429,7 +431,7 @@ class CaffeTensor {
     }
 
     get type() {
-        return this._type + JSON.stringify(this._shape);
+        return this._type;
     }
 
     get value() {
@@ -490,6 +492,27 @@ class CaffeTensor {
     }
 }
 
+class CaffeTensorType {
+
+    constructor(dataType, shape) {
+        this._dataType = dataType;
+        this._shape = shape;
+    }
+
+    get dataType() {
+        return this._dataType;
+    }
+
+    get shape() {
+        return this._shape;
+    }
+
+    toString() {
+        return this.dataType + (this._shape ? ('[' + this._shape.map((dimension) => dimension.toString()).join(',') + ']') : '');
+    }
+
+}
+
 class CaffeOperatorMetadata 
 {
 

+ 31 - 10
src/coreml-model.js

@@ -466,7 +466,7 @@ class CoreMLNode {
                 name: initializer.name,
                 connections: [ { 
                     id: '',
-                    type: initializer.type,
+                    type: initializer.type.toString(),
                     initializer: initializer, } ]
             };
             if (!CoreMLOperatorMetadata.operatorMetadata.getInputVisible(this._operator, initializer.name)) {
@@ -625,24 +625,27 @@ class CoreMLTensor {
     constructor(kind, name, shape, data) {
         this._kind = kind;
         this._name = name;
-        this._shape = shape;
-        this._type = null;
         this._data = null;
+        var dataType = '?';
         if (data) {
             if (data.floatValue && data.floatValue.length > 0) {
                 this._data = data.floatValue;
-                this._type = 'float';
+                dataType = 'float32';
+                this._shape = shape;
             }
             else if (data.float16Value && data.float16Value.length > 0) {
                 this._data = data.float16Value;
-                this._type = 'float16';
+                dataType = 'float16';
+                this._shape = shape;
             }
             else if (data.rawValue && data.rawValue.length > 0) {
                 this._data = null;
-                this._type = 'byte';
+                dataType = 'byte';
                 this._shape = [];
             }
         }
+
+        this._type = new CoreMLTensorType(dataType, shape);
     }
 
     get id() {
@@ -658,10 +661,7 @@ class CoreMLTensor {
     }
 
     get type() {
-        if (this._type && this._shape) {
-            return this._type + '[' + this._shape.join(',') + ']';
-        }
-        return '?';
+        return this._type;
     }
 
     get value() {
@@ -724,6 +724,27 @@ class CoreMLTensor {
     }
 }
 
+class CoreMLTensorType {
+
+    constructor(dataType, shape) {
+        this._dataType = dataType;
+        this._shape = shape;
+    }
+
+    get dataType() {
+        return this._dataType;
+    }
+
+    get shape() {
+        return this._shape;
+    }
+
+    toString() {
+        return this.dataType + (this._shape ? ('[' + this._shape.map((dimension) => dimension.toString()).join(',') + ']') : '');
+    }
+
+}
+
 class CoreMLOperatorMetadata 
 {
 

+ 24 - 4
src/keras-model.js

@@ -468,7 +468,7 @@ class KerasNode {
             input.connections.forEach((connection) => {
                 var initializer = this._initializers[connection.id];
                 if (initializer) {
-                    connection.type = initializer.type;
+                    connection.type = initializer.type.toString();
                     connection.initializer = initializer;
                 }
             });
@@ -557,8 +557,7 @@ class KerasAttribute {
 class KerasTensor {
 
     constructor(type, shape, data, reference) {
-        this._type = type;
-        this._shape = shape;
+        this._type = new KerasTensorType(type, shape);
         this._data = data;
         this._reference = reference;
     }
@@ -572,7 +571,7 @@ class KerasTensor {
     }
 
     get type() {
-        return this._type + JSON.stringify(this._shape);
+        return this._type;
     }
 
     get reference() {
@@ -682,6 +681,27 @@ class KerasTensor {
     }
 }
 
+class KerasTensorType {
+
+    constructor(dataType, shape) {
+        this._dataType = dataType;
+        this._shape = shape;
+    }
+
+    get dataType() {
+        return this._dataType;
+    }
+
+    get shape() {
+        return this._shape;
+    }
+
+    toString() {
+        return this.dataType + (this._shape ? ('[' + this._shape.map((dimension) => dimension.toString()).join(',') + ']') : '');
+    }
+
+}
+
 class KerasOperatorMetadata {
 
     static open(host, callback) {

+ 41 - 21
src/mxnet-model.js

@@ -387,11 +387,11 @@ class MXNetNode {
                     }
                     if (argument.name && argument.name.startsWith(prefix)) {
                         var dataType = '?';
-                        var shape = '';
+                        var shape = [];
                         if (argument.attrs && argument.attrs.__dtype__ && argument.attrs.__shape__) {
                             try {
                                 dataType = parseInt(argument.attrs.__dtype__);
-                                shape = argument.attrs.__shape__.replace('(', '').replace(')', '').split(' ').join('');
+                                shape = JSON.parse('[' + argument.attrs.__shape__.replace('(', '').replace(')', '').split(' ').join('').split(',').map((dimension => dimension || '\"?\"' )).join(',') + ']');
                             }
                             catch (err) {
                             }
@@ -430,7 +430,7 @@ class MXNetNode {
                 var initializer = this._initializers[connection.id];
                 if (initializer) {
                     connection.id = initializer.name || connection.id;
-                    connection.type = initializer.type;
+                    connection.type = initializer.type.toString();
                     connection.initializer = initializer;
                 }
             });
@@ -477,9 +477,8 @@ class MXNetTensor {
         this._kind = kind;
         this._name = name;
         this._dataType = dataType;
-        this._shape = shape;
         this._data = data;
-        MXNetTensor._dataTypeNameTable = MXNetTensor._dataTypeTable || [ 'float32', 'float64', 'float16', 'uint8', 'int32', 'int8', 'int64' ];
+        this._type = new MXNetTensorType(dataType, shape);
     }
 
     get kind() {
@@ -491,17 +490,7 @@ class MXNetTensor {
     }
 
     get type() {
-        var dataType = '?';
-        if (this._dataType || this._dataType === 0) {
-            if (this._dataType < MXNetTensor._dataTypeNameTable.length) {
-                dataType = MXNetTensor._dataTypeNameTable[this._dataType];
-            }
-            else {
-                dataType = this._dataType.toString();
-            }
-        }
-        var shape = Array.isArray(this._shape) ? this._shape.join(',') : this._shape.toString();
-        return dataType + '[' + shape + ']';
+        return this._type;
     }
 
     get value() {
@@ -528,15 +517,15 @@ class MXNetTensor {
             return { error: 'Tensor data is empty.' };
         }
 
-        if ((!this._dataType && this._dataType !== 0) || this._dataType == '?') {
+        if (this._type.dataType == '?') {
             return { error: 'Tensor has no data type.' };
         }
 
-        if (this._dataType >= MXNetTensor._dataTypeNameTable.length) {
+        if (this._type.dataType.length <= 1) {
             return { error: 'Tensor has unknown data type.' };
         }
 
-        if (!Array.isArray(this._shape) && this._shape.length < 1) {
+        if (this._type.shape.length < 1) {
             return { error: 'Tensor has unknown shape.' };
         }
 
@@ -544,6 +533,7 @@ class MXNetTensor {
         context.index = 0;
         context.count = 0;
         context.limit = limit;
+        context.shape = this._type.shape;
         context.data = new DataView(this._data.buffer, this._data.byteOffset, this._data.byteLength);
 
         return { value: this._decodeDimension(context, 0) };
@@ -551,8 +541,8 @@ class MXNetTensor {
 
     _decodeDimension(context, dimension) {
         var results = [];
-        var size = this._shape[dimension];
-        if (dimension == this._shape.length - 1) {
+        var size = context.shape[dimension];
+        if (dimension == context.shape.length - 1) {
             for (var i = 0; i < size; i++) {
                 if (context.count > context.limit) {
                     results.push('...');
@@ -624,6 +614,36 @@ class MXNetTensor {
     }
 }
 
+class MXNetTensorType {
+
+    constructor(dataType, shape) {
+        MXNetTensorType._dataTypeNameTable = MXNetTensor._dataTypeTable || [ 'float32', 'float64', 'float16', 'uint8', 'int32', 'int8', 'int64' ];
+        this._dataType = dataType;
+        this._shape = shape;
+    }
+
+    get dataType() {
+        var dataType = '?';
+        if (this._dataType || this._dataType === 0) {
+            if (this._dataType < MXNetTensorType._dataTypeNameTable.length) {
+                dataType = MXNetTensorType._dataTypeNameTable[this._dataType];
+            }
+            else {
+                dataType = this._dataType.toString();
+            }
+        }
+        return dataType;
+    }
+
+    get shape() {
+        return this._shape;
+    }
+
+    toString() {
+        return this.dataType + (this._shape ? ('[' + this._shape.map((dimension) => dimension.toString()).join(',') + ']') : '');
+    }
+}
+
 class MXNetOperatorMetadata {
 
     static open(host, callback) {

+ 206 - 0
src/numpy.js

@@ -0,0 +1,206 @@
+/*jshint esversion: 6 */
+
+var numpy = numpy || {};
+
+numpy.Array = class {
+
+    constructor(data, dataType, shape) {
+        this._data = data;
+        this._dataType = dataType;
+        this._shape = shape;
+    }
+
+    toBuffer() {
+
+        var writer = new numpy.Writer();
+
+        writer.write([ 0x93, 0x4E, 0x55, 0x4D, 0x50, 0x59 ]); '\\x93NUMPY'
+        writer.writeByte(1); // major
+        writer.writeByte(0); // minor
+
+        var context = {};
+        context.dataTypeSize = 1;
+        context.position = 0;
+        context.shape = this._shape;
+        context.descr = '';
+
+        switch (this._dataType) {
+            case 'float32':
+                context.dataTypeSize = 4;
+                context.descr = '<f4';
+                break;
+            case 'float64':
+                context.dataTypeSize = 8;
+                context.descr = '<f8';
+                break;
+            case 'int8':
+                context.dataTypeSize = 1;
+                context.descr = '<i1';
+                break;
+            case 'int16':
+                context.dataTypeSize = 2;
+                context.descr = '<i2';
+                break;
+            case 'int32':
+                context.dataTypeSize = 4;
+                context.descr = '<i4';
+                break;
+            case 'byte':
+            case 'uint8':
+                context.dataTypeSize = 1;
+                context.descr = '<u1';
+                break;
+            case 'uint16':
+                context.dataTypeSize = 2;
+                context.descr = '<u2';
+                break;
+            case 'int32':
+                context.dataTypeSize = 4;
+                context.descr = '<u4';
+                break;
+            default:
+                throw new numpy.Error('Unknown data type.');
+        }
+
+        var shape = '';
+        switch (this._shape.length) {
+            case 0:
+                throw new numpy.Error('Invalid shape.');
+            case 1:
+                shape = '(' + this._shape[0].toString() + ',)';
+                break;
+            default:
+                shape = '(' + this._shape.map((dimension) => dimension.toString()).join(', ') + ')';
+                break;
+        }
+
+        var properties = [];
+        properties.push("'descr': '" + context.descr + "'");
+        properties.push("'fortran_order': False");
+        properties.push("'shape': " + shape);
+        var header = '{ ' + properties.join(', ') + ' }';
+        header += ' '.repeat(16 - ((header.length + 2 + 8 + 1) & 0x0f)) + '\n';
+        writer.writeUint16(header.length); // header size
+        writer.writeString(header);
+
+        var size = context.dataTypeSize;
+        this._shape.forEach((dimension) => {
+            size *= dimension;
+        });
+
+        var array = new Uint8Array(size);
+        context.dataView = new DataView(array.buffer, array.byteOffset, size);
+        numpy.Array._encodeDimension(context, this._data, 0);
+        writer.write(array);
+
+        return writer.toBuffer();
+    }
+
+    static _encodeDimension(context, data, dimension) {
+        var size = context.shape[dimension];
+        if (dimension == context.shape.length - 1) {
+            for (var i = 0; i < size; i++) {
+                switch (context.descr)
+                {
+                    case '<f4':
+                        context.dataView.setFloat32(context.position, data[i], true);
+                        break;
+                    case '<f8':
+                        context.dataView.setFloat64(context.position, data[i], true);
+                        break;
+                    case '<i1':
+                        context.dataView.setInt8(context.position, data[i], true);
+                        break;
+                    case '<i2':
+                        context.dataView.setInt16(context.position, data[i], true);
+                        break;
+                    case '<i4':
+                        context.dataView.setInt32(context.position, data[i], true);
+                        break;
+                    case '<u1':
+                        context.dataView.setUint8(context.position, data[i], true);
+                        break;
+                    case '<u2':
+                        context.dataView.setUint16(context.position, data[i], true);
+                        break;
+                    case '<u4':
+                        context.dataView.setUint32(context.position, data[i], true);
+                        break;
+                }
+                context.position += context.dataTypeSize;
+            }
+        }
+        else {
+            for (var j = 0; j < size; j++) {
+                numpy.Array._encodeDimension(context, data[j], dimension + 1);
+            }
+        }
+    }
+};
+
+numpy.Writer = class {
+
+    constructor() {
+        this._length = 0;
+        this._head = null;
+        this._tail = null;
+    }
+
+    writeByte(value) {
+        this.writeBytes([ value ]);
+    }
+
+    writeUint16(value) {
+        var buffer = new Uint8Array(2);
+        buffer[0] = value & 0xff;
+        buffer[1] = (value >> 8) & 0xff;
+        this.write(buffer);
+    }
+
+    writeBytes(values) {
+        var array = new Uint8Array(values.length);
+        for (var i = 0; i < values.length; i++) {
+            array[i] = values[i];
+        }
+        this.write(array);
+    }
+
+    writeString(value) {
+        var array = new Uint8Array(value.length);
+        for (var i = 0; i < value.length; i++) {
+            array[i] = value.charCodeAt(i);
+        }
+        this.write(array);
+    }
+
+    write(array) {
+        var node = { buffer: array, next: null };
+        if (this._tail) {
+            this._tail.next = node;
+        }
+        else {
+            this._head = node;
+        }
+        this._tail = node;
+        this._length += node.buffer.length;
+    }
+
+    toBuffer() {
+        var array = new Uint8Array(this._length);
+        var position = 0;
+        var head = this._head;
+        while (head != null) {
+            array.set(head.buffer, position);
+            position += head.buffer.length;
+            head = head.next;
+        }
+        return array;
+    }
+};
+
+numpy.Error = class extends Error {
+    constructor(message) {
+        super(message);
+        this.name = 'NumPy Error';
+    }
+};

+ 31 - 11
src/onnx-model.js

@@ -209,7 +209,7 @@ class OnnxGraph {
                             var initializer = this._initializerMap[connection.id];
                             if (initializer) {
                                 connection.initializer = initializer;
-                                connection.type = connection.type || initializer.type;
+                                connection.type = connection.type || initializer.type.toString();
                             }
                             return connection;
                         });
@@ -471,14 +471,7 @@ class OnnxTensor {
     }
 
     get type() {
-        var result = '';
-        if (this._tensor.hasOwnProperty('dataType')) {
-            result = OnnxTensor._formatElementType(this._tensor.dataType);
-            if (this._tensor.dims) { 
-                result += '[' + this._tensor.dims.map(dimension => dimension.toString()).join(',') + ']';
-            }
-        }
-        return result;
+        return new OnnxTensorType(this._tensor);
     }
 
     get value() {
@@ -683,7 +676,7 @@ class OnnxTensor {
             var map = {};
             OnnxTensor._elementTypeMap = map;
             map[onnx.TensorProto.DataType.UNDEFINED] = 'UNDEFINED';
-            map[onnx.TensorProto.DataType.FLOAT] = 'float';
+            map[onnx.TensorProto.DataType.FLOAT] = 'float32';
             map[onnx.TensorProto.DataType.UINT8] = 'uint8';
             map[onnx.TensorProto.DataType.INT8] = 'int8';
             map[onnx.TensorProto.DataType.UINT16] = 'uint16';
@@ -693,7 +686,7 @@ class OnnxTensor {
             map[onnx.TensorProto.DataType.STRING] = 'string';
             map[onnx.TensorProto.DataType.BOOL] = 'bool';
             map[onnx.TensorProto.DataType.FLOAT16] = 'float16';
-            map[onnx.TensorProto.DataType.DOUBLE] = 'double';
+            map[onnx.TensorProto.DataType.DOUBLE] = 'float64';
             map[onnx.TensorProto.DataType.UINT32] = 'uint32';
             map[onnx.TensorProto.DataType.UINT64] = 'uint64';
             map[onnx.TensorProto.DataType.COMPLEX64] = 'complex64';
@@ -758,6 +751,33 @@ class OnnxTensor {
     }
 }
 
+class OnnxTensorType {
+
+    constructor(tensor) {
+        this._dataType = '?';
+        if (tensor.hasOwnProperty('dataType')) {
+            this._dataType = OnnxTensor._formatElementType(tensor.dataType);
+        }
+        this._shape = [];
+        if (tensor.hasOwnProperty('dims')) { 
+            this._shape = tensor.dims.map((dimension) => dimension);
+        }
+    }
+
+    get dataType() {
+        return this._dataType;
+    }
+
+    get shape() {
+        return this._shape;
+    }
+
+    toString() {
+        return this.dataType + (this._shape ? ('[' + this._shape.map((dimension) => dimension.toString()).join(',') + ']') : '');
+    }
+
+}
+
 class OnnxGraphOperatorMetadata {
 
     constructor(opsetImport) {

+ 24 - 10
src/tf-model.js

@@ -387,7 +387,7 @@ class TensorFlowNode {
                 input.connections.forEach((connection) => {
                     var initializer = this._graph._getInitializer(connection.id);
                     if (initializer) {
-                        connection.type = initializer.type;
+                        connection.type = initializer.type.toString();
                         connection.initializer = initializer;
                     }
                 });
@@ -562,14 +562,7 @@ class TensorFlowTensor {
     }
 
     get type() {
-        if (this._tensor.dtype) {
-            var type = TensorFlowTensor.formatDataType(this._tensor.dtype);
-            if (this._tensor.tensorShape) {
-                type += TensorFlowTensor.formatTensorShape(this._tensor.tensorShape);
-            }
-            return type;
-        }
-        return '?';
+        return new TensorFlowTensorType(this._tensor.dtype, this._tensor.tensorShape);
     }
 
     get kind() {
@@ -747,7 +740,7 @@ class TensorFlowTensor {
     }
 
     static formatTensorShape(shape) {
-        if (shape.dim) {
+        if (shape && shape.dim) {
             if (shape.unknownRank) {
                 return '[-]';
             }
@@ -763,6 +756,27 @@ class TensorFlowTensor {
     }
 }
 
+class TensorFlowTensorType {
+
+    constructor(dtype, shape) {
+        this._dtype = dtype;
+        this._shape = shape;
+    }
+
+    get dataType() {
+        return this._dtype ? TensorFlowTensor.formatDataType(this._dtype) : '?';
+    }
+
+    get shape() {
+        return null;
+    }
+
+    toString() {
+        return this.dataType + TensorFlowTensor.formatTensorShape(this._shape);
+    }
+    
+}
+
 class TensorFlowGraphOperatorMetadata {
 
     constructor(metaInfoDef) {

+ 39 - 26
src/tflite-model.js

@@ -133,7 +133,7 @@ class TensorFlowLiteGraph {
             results.push({ 
                 id: tensorIndex.toString(),
                 name: tensor.name(),
-                type: TensorFlowLiteTensor.formatTensorType(tensor) 
+                type: new TensorFlowLiteTensorType(tensor).toString()
             });
         }
         return results;
@@ -148,7 +148,7 @@ class TensorFlowLiteGraph {
             results.push({ 
                 id: tensorIndex.toString(),
                 name: tensor.name(),
-                type: TensorFlowLiteTensor.formatTensorType(tensor) 
+                type: new TensorFlowLiteTensorType(tensor).toString()
             });
         }
         return results;
@@ -212,7 +212,7 @@ class TensorFlowLiteNode {
             input.connections.forEach((connection) => {
                 var tensorIndex = connection.id;
                 var tensor = this._graph._graph.tensors(tensorIndex);
-                connection.type = TensorFlowLiteTensor.formatTensorType(tensor);
+                connection.type = new TensorFlowLiteTensorType(tensor).toString();
                 var initializer = this._graph.getInitializer(tensorIndex);
                 if (initializer) {
                     connection.initializer = initializer;
@@ -236,7 +236,7 @@ class TensorFlowLiteNode {
             };
             var connection = {};
             connection.id = tensorIndex.toString();
-            connection.type = TensorFlowLiteTensor.formatTensorType(tensor);
+            connection.type = new TensorFlowLiteTensorType(tensor).toString();
             var initializer = this._graph.getInitializer(tensorIndex);
             if (initializer) {
                 connection.initializer = initializer;
@@ -410,7 +410,7 @@ class TensorFlowLiteTensor {
     }
 
     get type() {
-        return TensorFlowLiteTensor.formatTensorType(this._tensor);
+        return new TensorFlowLiteTensorType(this._tensor);
     }
 
     get quantization() {
@@ -555,33 +555,46 @@ class TensorFlowLiteTensor {
         }
         return (s ? -1 : 1) * Math.pow(2, e-15) * (1 + (f / Math.pow(2, 10)));
     }
+}
 
-    static formatTensorType(tensor) {
-        if (!TensorFlowLiteTensor._tensorTypeMap)
-        {
-            TensorFlowLiteTensor._tensorTypeMap = {};
-            TensorFlowLiteTensor._tensorTypeMap[tflite.TensorType.FLOAT32] = 'float';
-            TensorFlowLiteTensor._tensorTypeMap[tflite.TensorType.FLOAT16] = 'float16';
-            TensorFlowLiteTensor._tensorTypeMap[tflite.TensorType.INT32] = 'int32';
-            TensorFlowLiteTensor._tensorTypeMap[tflite.TensorType.UINT8] = 'byte';
-            TensorFlowLiteTensor._tensorTypeMap[tflite.TensorType.INT64] = 'int64';
-            TensorFlowLiteTensor._tensorTypeMap[tflite.TensorType.STRING] = 'string';
-            TensorFlowLiteTensor._tensorTypeMap[tflite.TensorType.BOOL] = 'bool';
-        }
-        var result = TensorFlowLiteTensor._tensorTypeMap[tensor.type()]; 
-        if (!result) {
-            debugger;
-            result = '?';
-        }
+class TensorFlowLiteTensorType {
+
+    constructor(tensor) {
+        this._dataType = tensor.type();
+        this._shape = [];
         var shapeLength = tensor.shapeLength();
         if (shapeLength > 0) {
-            var dimensions = [];
             for (var i = 0; i < shapeLength; i++) {
-                dimensions.push(tensor.shape(i).toString());
+                this._shape.push(tensor.shape(i));
             }
-            result += '[' + dimensions.join(',') + ']';
         }
-        return result;
+    }
+
+    get dataType() {
+        if (!TensorFlowLiteTensorType._typeMap)
+        {
+            TensorFlowLiteTensorType._typeMap = {};
+            TensorFlowLiteTensorType._typeMap[tflite.TensorType.FLOAT32] = 'float32';
+            TensorFlowLiteTensorType._typeMap[tflite.TensorType.FLOAT16] = 'float16';
+            TensorFlowLiteTensorType._typeMap[tflite.TensorType.INT32] = 'int32';
+            TensorFlowLiteTensorType._typeMap[tflite.TensorType.UINT8] = 'byte';
+            TensorFlowLiteTensorType._typeMap[tflite.TensorType.INT64] = 'int64';
+            TensorFlowLiteTensorType._typeMap[tflite.TensorType.STRING] = 'string';
+            TensorFlowLiteTensorType._typeMap[tflite.TensorType.BOOL] = 'bool';
+        }
+        var result = TensorFlowLiteTensorType._typeMap[this._dataType]; 
+        if (result) {
+            return result;
+        }
+        return '?';
+    }
+
+    get shape() {
+        return this._shape;
+    }
+
+    toString() {
+        return this.dataType + (this._shape ? ('[' + this._shape.map((dimension) => dimension.toString()).join(',') + ']') : '');
     }
 
 }

+ 3 - 0
src/view-electron.js

@@ -143,6 +143,9 @@ class ElectronHost {
                 return;
             }    
         }
+        if (mimeType == null) {
+            encoding = 'binary';
+        }
         fs.writeFile(file, data, encoding, (err) => {
             if (err) {
                 this.error('Export write failure.', err);

+ 8 - 5
src/view-sidebar.js

@@ -73,7 +73,8 @@ class Sidebar {
 
 class NodeView {
 
-    constructor(node) {
+    constructor(node, host) {
+        this._host = host;
         this._node = node;
         this._elements = [];
         this._attributes = [];
@@ -165,7 +166,7 @@ class NodeView {
 
     addInput(name, input) {
         if (input.connections.length > 0) {
-            var item = new NameValueView(name, new NodeArgumentView(input));
+            var item = new NameValueView(name, new NodeArgumentView(input, this._host));
             this._inputs.push(item);
             this._elements.push(item.element);
         }
@@ -323,12 +324,12 @@ class NodeAttributeView {
 
 class NodeArgumentView {
 
-    constructor(list) {
+    constructor(list, host) {
         this._list = list;
         this._elements = [];
         this._items = [];
         list.connections.forEach((connection) => {
-            var item = new NodeConnectionView(connection);
+            var item = new NodeConnectionView(connection, host);
             this._items.push(item);
             this._elements.push(item.element);
         });
@@ -346,8 +347,10 @@ class NodeArgumentView {
 }
 
 class NodeConnectionView {
-    constructor(connection) {
+    constructor(connection, host) {
         this._connection = connection;
+        this._host = host;
+
         this._element = document.createElement('div');
         this._element.className = 'sidebar-view-item-value';
 

+ 1 - 1
src/view.js

@@ -780,7 +780,7 @@ class View {
     
     showNodeProperties(node, input) {
         if (node) {
-            var view = new NodeView(node);
+            var view = new NodeView(node, this._host);
             view.on('show-documentation', (sender, e) => {
                 this.showOperatorDocumentation(node);
             });