Bladeren bron

Add model metadata

Lutz Roeder 3 jaren geleden
bovenliggende
commit
3cfe4f24ff
11 gewijzigde bestanden met toevoegingen van 463 en 425 verwijderingen
  1. 18 11
      source/circle.js
  2. 7 10
      source/coreml.js
  3. 11 13
      source/dlc.js
  4. 5 0
      source/message.js
  5. 14 10
      source/mnn.js
  6. 13 12
      source/mxnet.js
  7. 32 46
      source/onnx.js
  8. 41 2
      source/onnx.py
  9. 301 292
      source/tengine.js
  10. 18 11
      source/tflite.js
  11. 3 18
      source/view-sidebar.js

+ 18 - 11
source/circle.js

@@ -77,6 +77,7 @@ circle.Model = class {
         this._format = 'Circle';
         this._format = this._format + ' v' + model.version.toString();
         this._description = model.description || '';
+        this._metadata = [];
         const builtinOperators = new Map();
         const upperCase = new Set([ '2D', 'LSH', 'SVDF', 'RNN', 'L2', 'LSTM' ]);
         for (const key of Object.keys(circle.schema.BuiltinOperator)) {
@@ -107,11 +108,21 @@ circle.Model = class {
                         const reader = flatbuffers.BinaryReader.open(data);
                         if (circle.schema.ModelMetadata.identifier(reader)) {
                             modelMetadata = circle.schema.ModelMetadata.create(reader);
-                            this._name = modelMetadata.name || '';
-                            this._version = modelMetadata.version || '';
-                            this._description = modelMetadata.description ? [ this.description, modelMetadata.description].join(' ') : this._description;
-                            this._author = modelMetadata.author || '';
-                            this._license = modelMetadata.license || '';
+                            if (modelMetadata.name) {
+                                this._name = modelMetadata.name;
+                            }
+                            if (modelMetadata.version) {
+                                this._version = modelMetadata.version;
+                            }
+                            if (modelMetadata.description) {
+                                this._description = this._description ? [ this._description, modelMetadata.description].join(' ') : modelMetadata.description;
+                            }
+                            if (modelMetadata.author) {
+                                this._metadata.push({ name: 'author', value: modelMetadata.author });
+                            }
+                            if (modelMetadata.license) {
+                                this._metadata.push({ name: 'license', value: modelMetadata.license });
+                            }
                         }
                         break;
                     }
@@ -151,12 +162,8 @@ circle.Model = class {
         return this._description;
     }
 
-    get author() {
-        return this._author;
-    }
-
-    get license() {
-        return this._license;
+    get metadata() {
+        return this._metadata;
     }
 
     get graphs() {

+ 7 - 10
source/coreml.js

@@ -184,20 +184,21 @@ coreml.Model = class {
 
     constructor(metadata, format, model, weights) {
         this._format = (format || 'Core ML') + ' v' + model.specificationVersion.toString();
+        this._metadata = [];
         this._graphs = [ new coreml.Graph(metadata, model, weights) ];
         if (model.description && model.description.metadata) {
             const properties = model.description.metadata;
             if (properties.versionString) {
                 this._version = properties.versionString;
             }
-            if (properties.author) {
-                this._author = properties.author;
-            }
             if (properties.shortDescription) {
                 this._description = properties.shortDescription;
             }
+            if (properties.author) {
+                this._metadata.push({ name: 'author', value: properties.author });
+            }
             if (properties.license) {
-                this._license = properties.license;
+                this._metadata.push({ name: 'license', value: properties.license });
             }
             if (metadata.userDefined && Object.keys(properties.userDefined).length > 0) {
                 /* empty */
@@ -217,12 +218,8 @@ coreml.Model = class {
         return this._description || null;
     }
 
-    get author() {
-        return this._author || null;
-    }
-
-    get license() {
-        return this._license || null;
+    get metadata() {
+        return this._metadata;
     }
 
     get graphs() {

+ 11 - 13
source/dlc.js

@@ -40,24 +40,22 @@ dlc.ModelFactory = class {
 dlc.Model = class {
 
     constructor(metadata, model, params, metadata_props) {
+        this._format = model ? 'DLC' : 'DLC Weights';
+        this._metadata = [];
         if (metadata_props.size > 0) {
+            const version = metadata_props.get('model-version');
+            if (version) {
+                this._version = version;
+            }
             const converter = metadata_props.get('converter-command');
             if (converter) {
                 const source = converter.split(' ').shift().trim();
                 if (source.length > 0) {
-                    this._source = source;
                     const version = metadata_props.get('converter-version');
-                    if (version) {
-                        this._source = this._source + ' v' + version;
-                    }
+                    this._metadata.push({ name: 'source', value: version ? source + ' v' + version : source });
                 }
             }
-            const version = metadata_props.get('model-version');
-            if (version) {
-                this._version = version;
-            }
         }
-        this._format = model ? 'DLC' : 'DLC Weights';
         this._graphs = [ new dlc.Graph(metadata, model, params) ];
     }
 
@@ -65,14 +63,14 @@ dlc.Model = class {
         return this._format;
     }
 
-    get source() {
-        return this._source;
-    }
-
     get version() {
         return this._version;
     }
 
+    get metadata() {
+        return this._metadata;
+    }
+
     get graphs() {
         return this._graphs;
     }

+ 5 - 0
source/message.js

@@ -28,6 +28,7 @@ message.Model = class {
         this._producer = data.producer || '';
         this._version = data.version || '';
         this._description = data.description || '';
+        this._metadata = (data.metadata || []).map((entry) => { return { name: entry.name, value: entry.value }; });
         this._graphs = (data.graphs || []).map((graph) => new message.Graph(graph));
     }
 
@@ -47,6 +48,10 @@ message.Model = class {
         return this._description;
     }
 
+    get metadata() {
+        return this._metadata;
+    }
+
     get graphs() {
         return this._graphs;
     }

+ 14 - 10
source/mnn.js

@@ -42,15 +42,19 @@ mnn.ModelFactory = class {
 mnn.Model = class {
 
     constructor(metadata, net) {
-        const NetSource = mnn.schema.NetSource;
-        switch (net.sourceType) {
-            case NetSource.CAFFE: this._source = 'Caffe'; break;
-            case NetSource.TENSORFLOW: this._source = 'TensorFlow'; break;
-            case NetSource.TFLITE: this._source = 'TensorFlow Lite'; break;
-            case NetSource.ONNX: this._source = 'ONNX'; break;
-            case NetSource.TORCH: this._source = 'Torch'; break;
-            default: throw new mnn.Error("Unsupported model source '" + net.sourceType + "'.");
+        const sources = new Map([
+            [ mnn.schema.NetSource.CAFFE, 'Caffe' ],
+            [ mnn.schema.NetSource.TENSORFLOW, 'TensorFlow' ],
+            [ mnn.schema.NetSource.TFLITE, 'TensorFlow Lite' ],
+            [ mnn.schema.NetSource.ONNX, 'ONNX' ],
+            [ mnn.schema.NetSource.TORCH, 'Torch' ]
+        ]);
+        if (!sources.has(net.sourceType)) {
+            throw new mnn.Error("Unsupported model source '" + net.sourceType + "'.");
         }
+        this._metadata = [
+            { name: 'source', value: sources.get(net.sourceType) }
+        ];
         this._graphs = [ new mnn.Graph(metadata, net) ];
     }
 
@@ -58,8 +62,8 @@ mnn.Model = class {
         return 'MNN v2';
     }
 
-    get source() {
-        return this._source || '';
+    get metadata() {
+        return this._metadata;
     }
 
     get graphs() {

+ 13 - 12
source/mxnet.js

@@ -258,8 +258,13 @@ mxnet.Model = class {
         this._version = manifest.version;
         this._description = manifest.description || '';
         this._runtime = manifest.runtime || '';
-        this._author = manifest.author || '';
-        this._license = manifest.license || '';
+        this._metadata = [];
+        if (manifest.author) {
+            this._metadata.push({ name: 'author', value: manifest.author });
+        }
+        if (manifest.license) {
+            this._metadata.push({ name: 'license', value: manifest.license });
+        }
         this._graphs = [ new mxnet.Graph(metadata, manifest, symbol, params) ];
     }
 
@@ -271,6 +276,10 @@ mxnet.Model = class {
         return this._producer;
     }
 
+    get runtime() {
+        return this._runtime;
+    }
+
     get name() {
         return this._name;
     }
@@ -283,16 +292,8 @@ mxnet.Model = class {
         return this._description;
     }
 
-    get author() {
-        return this._author;
-    }
-
-    get license() {
-        return this._license;
-    }
-
-    get runtime() {
-        return this._runtime;
+    get metadata() {
+        return this._metadata;
     }
 
     get graphs() {

+ 32 - 46
source/onnx.js

@@ -310,32 +310,44 @@ onnx.Model = class {
         }
 
         let imageFormat = '';
-        if (model.metadata_props) {
+        const metadata_props = model.metadata_props;
+        if (metadata_props) {
             const imageMetadata = {};
-            for (const metadata_prop of model.metadata_props) {
-                switch (metadata_prop.key) {
-                    case 'author':
-                        this._author = metadata_prop.value;
-                        break;
-                    case 'company':
-                        this._company = metadata_prop.value;
-                        break;
-                    case 'converted_from':
-                        this._converted_from = metadata_prop.value;
-                        break;
-                    case 'license':
-                        this._license = metadata_prop.value;
-                        break;
-                    case 'license_url':
-                        this._licenseUrl = metadata_prop.value;
-                        break;
+            const metadata = new Map(metadata_props.map((entry) => [ entry.key, entry.value ]));
+            const converted_from = metadata.get('converted_from');
+            if (converted_from) {
+                this._metadata.push({ name: 'source', value: converted_from });
+            }
+            const author = metadata.get('author');
+            if (author) {
+                this._metadata.push({ name: 'author', value: author });
+            }
+            const company = metadata.get('company');
+            if (company) {
+                this._metadata.push({ name: 'company', value: company });
+            }
+            let license = metadata.get('license');
+            const license_url = metadata.get('license_url');
+            if (license_url) {
+                license = '<a href=\'' + license_url + '\'>' + (license ? license : license_url) + '</a>';
+            }
+            if (license) {
+                this._metadata.push({ name: 'license', value: license });
+            }
+            metadata.delete('author');
+            metadata.delete('company');
+            metadata.delete('converted_from');
+            metadata.delete('license');
+            metadata.delete('license_url');
+            for (const entry of metadata) {
+                switch (entry[0]) {
                     case 'Image.BitmapPixelFormat':
                     case 'Image.ColorSpaceGamma':
                     case 'Image.NominalPixelRange':
-                        imageMetadata[metadata_prop.key] = metadata_prop.value;
+                        imageMetadata[entry[0]] = entry[1];
                         break;
                     default:
-                        this._metadata.push({ name: metadata_prop.key, value: metadata_prop.value});
+                        this._metadata.push({ name: entry[0], value: entry[1] });
                         break;
                 }
             }
@@ -390,32 +402,6 @@ onnx.Model = class {
         return this._description || null;
     }
 
-    get author() {
-        return this._author || null;
-    }
-
-    get company() {
-        return this._company || null;
-    }
-
-    get source() {
-        return this._converted_from || null;
-    }
-
-    get license() {
-        const license = [];
-        if (this._license && this._license.length > 0) {
-            license.push(this._license);
-        }
-        if (this._licenseUrl && this._licenseUrl.length > 0) {
-            license.push('<a href=\'' + this._licenseUrl + '\'>' + this._licenseUrl + '</a>');
-        }
-        if (license.length > 0) {
-            return license;
-        }
-        return null;
-    }
-
     get metadata() {
         return this._metadata;
     }

+ 41 - 2
source/onnx.py

@@ -1,8 +1,12 @@
 
+from curses import meta
+
+
 def serialize(model):
     print('Experimental')
-    import onnx.shape_inference
-    model = onnx.shape_inference.infer_shapes(model)
+    # import onnx.shape_inference
+    # model = onnx.shape_inference.infer_shapes(model)
+    import collections
     import onnx.onnx_pb
     json_model = {}
     json_model['signature'] = 'netron:onnx'
@@ -13,6 +17,37 @@ def serialize(model):
         json_model['version'] = str(model.model_version)
     if model.doc_string and len(model.doc_string):
         json_model['description'] = str(model.doc_string)
+    json_metadata = []
+    metadata = collections.OrderedDict([ [entry.key, entry.value] for entry in model.metadata_props ])
+    converted_from = metadata.get('converted_from')
+    if converted_from:
+        json_metadata.append({ 'name': 'source', 'value': converted_from })
+    author = metadata.get('author')
+    if author:
+        json_metadata.append({ 'name': 'author', 'value': author })
+    company = metadata.get('company')
+    if company:
+        json_metadata.append({ 'name': 'company', 'value': company })
+    license = metadata.get('license')
+    license_url = metadata.get('license_url')
+    if license_url:
+        license = '<a href=\'' + license_url + '\'>' + (license if license else license_url) + '</a>'
+    if license:
+        json_metadata.append({ 'name': 'license', 'value': license })
+    if 'author' in metadata:
+        metadata.pop('author')
+    if 'company' in metadata:
+        metadata.pop('company')
+    if 'converted_from' in metadata:
+        metadata.pop('converted_from')
+    if 'license' in metadata:
+        metadata.pop('license')
+    if 'license_url' in metadata:
+        metadata.pop('license_url')
+    for name, value in metadata.items():
+        json_metadata.append({ 'name': name, 'value': value })
+    if len(json_metadata) > 0:
+        json_model['metadata'] = json_metadata
     json_model['graphs'] = []
     graph = model.graph
     json_graph = {}
@@ -53,8 +88,10 @@ def serialize(model):
                 json_attribute['type'] = 'string'
                 json_attribute['value'] = attribute.s.decode('utf-8')
             elif attribute.type == onnx.onnx_pb.AttributeProto.TENSOR:
+                json_attribute['type'] = 'tensor'
                 raise Exception('Unsupported tensor attribute type')
             elif attribute.type == onnx.onnx_pb.AttributeProto.GRAPH:
+                json_attribute['graph'] = 'tensor'
                 raise Exception('Unsupported graph attribute type')
             elif attribute.type == onnx.onnx_pb.AttributeProto.FLOATS:
                 json_attribute['type'] = 'float32[]'
@@ -66,8 +103,10 @@ def serialize(model):
                 json_attribute['type'] = 'string[]'
                 json_attribute['value'] = [ item for item in attribute.strings ]
             elif attribute.type == onnx.onnx_pb.AttributeProto.TENSORS:
+                json_attribute['type'] = 'tensor[]'
                 raise Exception('Unsupported tensors attribute type')
             elif attribute.type == onnx.onnx_pb.AttributeProto.GRAPHS:
+                json_attribute['type'] = 'graph[]'
                 raise Exception('Unsupported graphs attribute type')
             else:
                 raise Exception('Unsupported attribute type')

+ 301 - 292
source/tengine.js

@@ -7,35 +7,23 @@ var base = base || require('./base');
 tengine.ModelFactory = class {
 
     match(context) {
-        const stream = context.stream;
-        if (stream.length > 4) {
-            const buffer = stream.peek(2);
-            if (buffer[0] < 4 && buffer[1] === 0) {
-                return 'tengine';
-            }
-        }
-        return undefined;
+        return tengine.Reader.open(context.stream);
     }
 
-    open(context) {
+    open(context, match) {
         return tengine.Metadata.open(context).then((metadata) => {
-            const buffer = context.stream.peek();
-            const majorVersion = buffer[0] | buffer[1] << 8;
-            const minorVersion = buffer[2] | buffer[3] << 8;
-            if (majorVersion !== 2) {
-                throw new tengine.Error("Unsupported format version 'v" + majorVersion.toString() + "." + minorVersion.toString() + "'.");
-            }
-            return new tengine.Model(metadata, buffer);
+            return new tengine.Model(metadata, match);
         });
     }
 };
 
 tengine.Model = class {
 
-    constructor(metadata, buffer) {
-        const reader = new tengine.ModelFileReader(buffer);
+    constructor(metadata, reader) {
         this._version = reader.version;
-        this._source = reader.source;
+        this._metadata = [
+            { name: 'source', value: reader.source }
+        ];
         this._graphs = reader.graphs.map((graph) => new tengine.Graph(metadata, graph));
     }
 
@@ -43,8 +31,8 @@ tengine.Model = class {
         return "Tengine v" + this._version;
     }
 
-    get source() {
-        return this._source;
+    get metadata() {
+        return this._metadata;
     }
 
     get graphs() {
@@ -494,304 +482,324 @@ tengine.Metadata = class {
     }
 };
 
-tengine.ModelFileReader = class {
+tengine.Reader = class {
 
-    constructor(buffer) {
+    static open(stream) {
+        if (stream.length > 4) {
+            const buffer = stream.peek(2);
+            if (buffer[0] < 4 && buffer[1] === 0) {
+                return new tengine.Reader(stream);
+            }
+        }
+        return null;
+    }
 
+    constructor(stream) {
+        this._stream = stream;
         // https://github.com/OAID/Tengine/blob/tengine-lite/src/serializer/tm/tm2_format.h
         // https://github.com/OAID/Tengine/wiki/The-format-of-tmfile
+    }
 
-        const types = new Map();
-        const register = (index, version, name, params) => {
-            types.set(index.toString() + ':' + version.toString(), { name: name, params: params });
-        };
-        const operator = (index, version) => {
-            let current = version;
-            while (current >= 0) {
-                if (types.has(index.toString() + ':' + current.toString())) {
-                    break;
+    _read() {
+        if (this._stream) {
+            const types = new Map();
+            const register = (index, version, name, params) => {
+                types.set(index.toString() + ':' + version.toString(), { name: name, params: params });
+            };
+            const operator = (index, version) => {
+                let current = version;
+                while (current >= 0) {
+                    if (types.has(index.toString() + ':' + current.toString())) {
+                        break;
+                    }
+                    current--;
                 }
-                current--;
-            }
-            if (current >= 0) {
-                const schema = types.get(index.toString() + ':' + current.toString());
-                if (current !== version) {
-                    types.set(index.toString() + ':' + version.toString(), schema);
+                if (current >= 0) {
+                    const schema = types.get(index.toString() + ':' + current.toString());
+                    if (current !== version) {
+                        types.set(index.toString() + ':' + version.toString(), schema);
+                    }
+                    return schema;
                 }
-                return schema;
-            }
-            return null;
-        };
-        register( 0, 0, 'Accuracy', []);
-        register( 1, 0, 'BatchNormalization', [ 'f', 'f', 'i' ]);
-        register( 2, 0, 'BilinearResize', [ 'f', 'f', 'i' ]);
-        register( 3, 0, 'Concat', [ 'i' ]);
-        register( 4, 0, 'Const', []);
-        register( 5, 0, 'Convolution', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register( 6, 0, 'Deconvolution', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register( 7, 0, 'DetectionOutput', [ 'i', 'i', 'i', 'f', 'f' ]);
-        register( 8, 0, 'DropOut', []);
-        register( 9, 0, 'Eltwise', [ 'i', 'i' ]);
-        register(10, 0, 'Flatten', [ 'i' ]);
-        register(11, 0, 'FullyConnected', [ 'i' ]);
-        register(12, 0, 'INPUT', []);
-        register(13, 0, 'LRN', [ 'i', 'f', 'f', 'i', 'f' ]);
-        register(14, 0, 'Normalize', [ 'i', 'i' ]);
-        register(15, 0, 'Permute', [ 'i', 'i', 'i', 'i', 'i' ]);
-        register(16, 0, 'Pooling', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register(17, 0, 'Prelu', []);
-        register(18, 0, 'PriorBox', [ 'f[]', 'f[]', 'f[]', 'f[]', 'i', 'i', 'i', 'i', 'i', 'f', 'f', 'f', 'i', 'i' ]);
-        register(19, 0, 'Region', [ 'i', 'i', 'i', 'i', 'f', 'f', 'f[]' ]);
-        register(20, 0, 'ReLU', [ 'f' ]);
-        register(21, 0, 'ReLU6', []);
-        register(22, 0, 'Reorg', [ 'i' ]);
-        register(23, 0, 'Reshape', [ 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        // register(23, 0, 'Reshape', [ 'i', 'i', 'i[]' ]);
-        register(24, 0, 'RoiPooling', [ 'i', 'i', 'f' ]);
-        register(25, 0, 'RPN', [ 'f[]', 'f[]', 'i', 'i', 'i', 'i', 'i', 'f', 'anchors' ]);
-        register(26, 0, 'Scale', [ 'i', 'i', 'i' ]);
-        register(27, 0, 'Slice', [ 'i', 'i[]', 'i[]', 'i[]', 'i', 'i', 'i', 'i', 'i' ]);
-        register(28, 0, 'SoftMax', [ 'i' ]);
-        register(29, 0, 'Split', [ 'i', 'i', 'boolean', 'boolean', 'i[]' ]);
-        register(30, 0, 'DetectionPostProcess', [ 'i', 'i', 'f', 'f', 'i', 'f[]' ]);
-        register(31, 0, 'Gemm', [ 'f', 'f', 'i', 'i' ]);
-        register(32, 0, 'Generic', [ 'i', 'i', 'string' ]);
-        register(33, 0, 'Logistic', []);
-        register(34, 0, 'LSTM', [ 'f', 'f', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register(35, 0, 'RNN', [ 'f', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register(36, 0, 'TanH', []);
-        register(37, 0, 'Sigmoid', []);
-        register(38, 0, 'Squeeze', [ 'i', 'i', 'i', 'i' ]);
-        register(39, 0, 'FusedbnScaleRelu', []);
-        register(40, 0, 'Pad', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'f' ]);
-        register(41, 0, 'StridedSlice', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register(42, 0, 'ArgMax', [ 'i' ]);
-        register(43, 0, 'ArgMin', [ 'i' ]);
-        register(44, 0, 'TopKV2', [ 'i', 'i' ]);
-        register(45, 0, 'Reduction', [ 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register(46, 0, 'Max', []);
-        register(47, 0, 'Min', []);
-        register(48, 0, 'GRU', [ 'f', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register(49, 0, 'Addn', 'i');
-        register(50, 0, 'SwapAxis', [ 'i', 'i' ]);
-        register(51, 0, 'Upsample', [ 'f' ]);
-        register(52, 0, 'SpaceToBatchND', [ 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register(53, 0, 'BatchToSpaceND', [ 'i', 'i', 'i', 'i', 'i', 'i' ]);
-        register(54, 0, 'Resize', [ 'f', 'f', 'i' ]);
-        register(55, 0, 'ShuffleChannel', [ 'i' ]);
-        register(56, 0, 'Crop', [ 'i', 'i', 'i', 'i', 'i', 'i', 'boolean', 'i', 'i' ]);
-        register(57, 0, 'ROIAlign', [ 'i', 'i', 'f' ]);
-        register(58, 0, 'Psroipooling', [ 'i', 'i', 'f', 'i' ]);
-        register(59, 0, 'Unary', [ 'i' ]);
-        register(60, 0, 'Expanddims', [ 'i' ]);
-        register(61, 0, 'Bias', [ 'i' ]);
-        register(62, 0, 'Noop', []);
-        register(63, 0, 'Threshold', [ 'f' ]);
-        register(64, 0, 'Hardsigmoid', [ 'f', 'f' ]);
-        register(65, 0, 'Embed', [ 'f', 'f', 'f', 'f' ]);
-        register(66, 0, 'InstanceNorm', [ 'f' ]);
-        register(67, 0, 'MVN', [ 'i', 'i', 'f' ]);
-        register(68, 0, 'Absval', []);
-        register(69, 0, 'Cast', [ 'i', 'i' ]);
-        register(70, 0, 'HardSwish', [ 'f', 'f' ]);
-        register(71, 0, 'Interp', [ 'i', 'i', 'f', 'f', 'i' ]);
-        register(72, 0, 'SELU', [ 'f', 'f' ]);
-        register(73, 0, 'ELU', [ 'f' ]);
-        register(74, 0, 'BroadMul', []);
-        register(75, 0, 'Logical', [ 'i' ]);
-        register(76, 0, 'Gather', [ 'i', 'i' ]);
-        register(77, 0, 'Transpose', [ 'i[]' ]);
-        register(78, 0, 'Comparison', [ 'i' ]);
-        register(79, 0, 'SpaceToDepth', [ 'i' ]);
-        register(80, 0, 'DepthToSpace', [ 'i' ]);
-        register(81, 0, 'Reverse', []);
-        register(82, 0, 'SparseToDense', [ 'i','i','i' ]);
-        register(83, 0, 'Ceil', []);
-        register(84, 0, 'SquaredDifference', []);
-        register(85, 0, 'Round', []);
-        register(86, 0, 'ZerosLike', []);
-        register(87, 0, 'Clip', [ 'f','f' ]);
-        register(88, 0, 'Unsqueeze', [ 'i[]' ]);
-        register(89, 0, 'ReduceL2', [ 'i','i' ]);
-        register(90, 0, 'Mean', []);
-        register(91, 0, 'MatMul', []);
-        register(92, 0, 'Expand', ['i[]']);
-        register(93, 0, 'Scatter', ['i','boolean']);
-        register(94, 0, 'Shape', []);
-        register(95, 0, 'Where', []);
-        register(96, 0, 'Tile', ['i','i']);
-        register(97, 0, 'Mish', []);
-        register(98, 0, 'L2Pool', []);
-        register(99, 0, 'LogSoftmax', []);
-        register(100, 0, 'ReLU1', []);
-        register(101, 0, 'L2Normalization', []);
-        register(102, 0, 'PackModel', ['i','i']);
-        register(103, 0, 'Num', []);
-
-        const reader = new tengine.BinaryReader(buffer);
-        this._majorVersion = reader.uint16();
-        this._minorVersion = reader.uint16();
-        this._compileVersion = reader.uint16();
-        reader.skip(2); // struct align
-        reader.seek(reader.uint32()); // root table
-        this._originalFormat = reader.int32();
-        this._subFormat = reader.int32();
-        this._graphs = [];
-        const subgraphOffsets = reader.uint32s();
-        for (const subgraphOffset of subgraphOffsets) {
-            reader.seek(subgraphOffset);
-
-            const subgraph = {};
-            subgraph.id = reader.int32();
-            subgraph.graphLayout = reader.int32();
-            /*
-            if (graphLayout == 0) {
-                return "NCHW";
-            }
-            if (graphLayout == 1) {
-                return "NHWC";
+                return null;
+            };
+            register( 0, 0, 'Accuracy', []);
+            register( 1, 0, 'BatchNormalization', [ 'f', 'f', 'i' ]);
+            register( 2, 0, 'BilinearResize', [ 'f', 'f', 'i' ]);
+            register( 3, 0, 'Concat', [ 'i' ]);
+            register( 4, 0, 'Const', []);
+            register( 5, 0, 'Convolution', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register( 6, 0, 'Deconvolution', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register( 7, 0, 'DetectionOutput', [ 'i', 'i', 'i', 'f', 'f' ]);
+            register( 8, 0, 'DropOut', []);
+            register( 9, 0, 'Eltwise', [ 'i', 'i' ]);
+            register(10, 0, 'Flatten', [ 'i' ]);
+            register(11, 0, 'FullyConnected', [ 'i' ]);
+            register(12, 0, 'INPUT', []);
+            register(13, 0, 'LRN', [ 'i', 'f', 'f', 'i', 'f' ]);
+            register(14, 0, 'Normalize', [ 'i', 'i' ]);
+            register(15, 0, 'Permute', [ 'i', 'i', 'i', 'i', 'i' ]);
+            register(16, 0, 'Pooling', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register(17, 0, 'Prelu', []);
+            register(18, 0, 'PriorBox', [ 'f[]', 'f[]', 'f[]', 'f[]', 'i', 'i', 'i', 'i', 'i', 'f', 'f', 'f', 'i', 'i' ]);
+            register(19, 0, 'Region', [ 'i', 'i', 'i', 'i', 'f', 'f', 'f[]' ]);
+            register(20, 0, 'ReLU', [ 'f' ]);
+            register(21, 0, 'ReLU6', []);
+            register(22, 0, 'Reorg', [ 'i' ]);
+            register(23, 0, 'Reshape', [ 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            // register(23, 0, 'Reshape', [ 'i', 'i', 'i[]' ]);
+            register(24, 0, 'RoiPooling', [ 'i', 'i', 'f' ]);
+            register(25, 0, 'RPN', [ 'f[]', 'f[]', 'i', 'i', 'i', 'i', 'i', 'f', 'anchors' ]);
+            register(26, 0, 'Scale', [ 'i', 'i', 'i' ]);
+            register(27, 0, 'Slice', [ 'i', 'i[]', 'i[]', 'i[]', 'i', 'i', 'i', 'i', 'i' ]);
+            register(28, 0, 'SoftMax', [ 'i' ]);
+            register(29, 0, 'Split', [ 'i', 'i', 'boolean', 'boolean', 'i[]' ]);
+            register(30, 0, 'DetectionPostProcess', [ 'i', 'i', 'f', 'f', 'i', 'f[]' ]);
+            register(31, 0, 'Gemm', [ 'f', 'f', 'i', 'i' ]);
+            register(32, 0, 'Generic', [ 'i', 'i', 'string' ]);
+            register(33, 0, 'Logistic', []);
+            register(34, 0, 'LSTM', [ 'f', 'f', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register(35, 0, 'RNN', [ 'f', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register(36, 0, 'TanH', []);
+            register(37, 0, 'Sigmoid', []);
+            register(38, 0, 'Squeeze', [ 'i', 'i', 'i', 'i' ]);
+            register(39, 0, 'FusedbnScaleRelu', []);
+            register(40, 0, 'Pad', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'f' ]);
+            register(41, 0, 'StridedSlice', [ 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register(42, 0, 'ArgMax', [ 'i' ]);
+            register(43, 0, 'ArgMin', [ 'i' ]);
+            register(44, 0, 'TopKV2', [ 'i', 'i' ]);
+            register(45, 0, 'Reduction', [ 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register(46, 0, 'Max', []);
+            register(47, 0, 'Min', []);
+            register(48, 0, 'GRU', [ 'f', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register(49, 0, 'Addn', 'i');
+            register(50, 0, 'SwapAxis', [ 'i', 'i' ]);
+            register(51, 0, 'Upsample', [ 'f' ]);
+            register(52, 0, 'SpaceToBatchND', [ 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register(53, 0, 'BatchToSpaceND', [ 'i', 'i', 'i', 'i', 'i', 'i' ]);
+            register(54, 0, 'Resize', [ 'f', 'f', 'i' ]);
+            register(55, 0, 'ShuffleChannel', [ 'i' ]);
+            register(56, 0, 'Crop', [ 'i', 'i', 'i', 'i', 'i', 'i', 'boolean', 'i', 'i' ]);
+            register(57, 0, 'ROIAlign', [ 'i', 'i', 'f' ]);
+            register(58, 0, 'Psroipooling', [ 'i', 'i', 'f', 'i' ]);
+            register(59, 0, 'Unary', [ 'i' ]);
+            register(60, 0, 'Expanddims', [ 'i' ]);
+            register(61, 0, 'Bias', [ 'i' ]);
+            register(62, 0, 'Noop', []);
+            register(63, 0, 'Threshold', [ 'f' ]);
+            register(64, 0, 'Hardsigmoid', [ 'f', 'f' ]);
+            register(65, 0, 'Embed', [ 'f', 'f', 'f', 'f' ]);
+            register(66, 0, 'InstanceNorm', [ 'f' ]);
+            register(67, 0, 'MVN', [ 'i', 'i', 'f' ]);
+            register(68, 0, 'Absval', []);
+            register(69, 0, 'Cast', [ 'i', 'i' ]);
+            register(70, 0, 'HardSwish', [ 'f', 'f' ]);
+            register(71, 0, 'Interp', [ 'i', 'i', 'f', 'f', 'i' ]);
+            register(72, 0, 'SELU', [ 'f', 'f' ]);
+            register(73, 0, 'ELU', [ 'f' ]);
+            register(74, 0, 'BroadMul', []);
+            register(75, 0, 'Logical', [ 'i' ]);
+            register(76, 0, 'Gather', [ 'i', 'i' ]);
+            register(77, 0, 'Transpose', [ 'i[]' ]);
+            register(78, 0, 'Comparison', [ 'i' ]);
+            register(79, 0, 'SpaceToDepth', [ 'i' ]);
+            register(80, 0, 'DepthToSpace', [ 'i' ]);
+            register(81, 0, 'Reverse', []);
+            register(82, 0, 'SparseToDense', [ 'i','i','i' ]);
+            register(83, 0, 'Ceil', []);
+            register(84, 0, 'SquaredDifference', []);
+            register(85, 0, 'Round', []);
+            register(86, 0, 'ZerosLike', []);
+            register(87, 0, 'Clip', [ 'f','f' ]);
+            register(88, 0, 'Unsqueeze', [ 'i[]' ]);
+            register(89, 0, 'ReduceL2', [ 'i','i' ]);
+            register(90, 0, 'Mean', []);
+            register(91, 0, 'MatMul', []);
+            register(92, 0, 'Expand', ['i[]']);
+            register(93, 0, 'Scatter', ['i','boolean']);
+            register(94, 0, 'Shape', []);
+            register(95, 0, 'Where', []);
+            register(96, 0, 'Tile', ['i','i']);
+            register(97, 0, 'Mish', []);
+            register(98, 0, 'L2Pool', []);
+            register(99, 0, 'LogSoftmax', []);
+            register(100, 0, 'ReLU1', []);
+            register(101, 0, 'L2Normalization', []);
+            register(102, 0, 'PackModel', ['i','i']);
+            register(103, 0, 'Num', []);
+
+            const buffer = this._stream.peek();
+            const reader = new tengine.BinaryReader(buffer);
+            this._majorVersion = reader.uint16();
+            this._minorVersion = reader.uint16();
+            if (this._majorVersion !== 2) {
+                throw new tengine.Error("Unsupported format version 'v" + this._majorVersion.toString() + "." + this._minorVersion.toString() + "'.");
             }
-            */
-            subgraph.originalLayout = reader.int32();
-            subgraph.inputs = reader.uint32s();
-            subgraph.outputs = reader.uint32s();
-            const nodeOffsets = reader.uint32s();
-            const tensorOffsets = reader.uint32s();
-            const bufferOffsets = reader.uint32s();
-            subgraph.name = reader.string();
-            subgraph.nodes = [];
-            subgraph.tensors = [];
-            this._graphs.push(subgraph);
-
-            // nodes
-            for (const nodeOffset of nodeOffsets) {
-                reader.seek(nodeOffset);
-                const node = {};
-                node.id = reader.int32();
-                node.inputs = reader.uint32s();
-                node.outputs = reader.uint32s();
-                const typeOffset = reader.int32();
-                node.name = reader.string();
-                const attributeOffsets = reader.uint32s();
-                node.dynamicShape = reader.boolean();
-
-                reader.seek(typeOffset);
-                node.version = reader.int32();
-                const index = reader.int32();
-                const paramsOffset = reader.uint32();
-
-                const schema = operator(index, node.version);
-                node.type = schema ? schema.name : index.toString();
-                const paramTypes = schema ? schema.params : [];
-
-                node.params = [];
-                if (paramsOffset) {
-                    reader.seek(paramsOffset);
-                    for (const paramType of paramTypes) {
-                        if (paramType !== 'boolean') {
-                            reader.align(4);
-                        }
-                        switch (paramType) {
-                            case 'i':
-                                node.params.push(reader.int32());
-                                break;
-                            case 'f':
-                                node.params.push(reader.float32());
-                                break;
-                            case 'i[]':
-                                node.params.push(reader.int32s());
-                                break;
-                            case 'f[]':
-                                node.params.push(reader.float32s());
-                                break;
-                            case 'boolean':
-                                node.params.push(reader.boolean());
-                                break;
-                            case 'string':
-                                node.params.push(reader.string());
-                                break;
-                            case 'anchors':
-                                node.params.push(reader.anchors(4));
-                                break;
-                            default:
-                                throw new tengine.Error("Unsupported param type '" + paramType + "' in '" + node.type + "'.");
+            this._compileVersion = reader.uint16();
+            reader.skip(2); // struct align
+            reader.seek(reader.uint32()); // root table
+            this._originalFormat = reader.int32();
+            this._subFormat = reader.int32();
+            this._graphs = [];
+            const subgraphOffsets = reader.uint32s();
+            for (const subgraphOffset of subgraphOffsets) {
+                reader.seek(subgraphOffset);
+
+                const subgraph = {};
+                subgraph.id = reader.int32();
+                subgraph.graphLayout = reader.int32();
+                /*
+                if (graphLayout == 0) {
+                    return "NCHW";
+                }
+                if (graphLayout == 1) {
+                    return "NHWC";
+                }
+                */
+                subgraph.originalLayout = reader.int32();
+                subgraph.inputs = reader.uint32s();
+                subgraph.outputs = reader.uint32s();
+                const nodeOffsets = reader.uint32s();
+                const tensorOffsets = reader.uint32s();
+                const bufferOffsets = reader.uint32s();
+                subgraph.name = reader.string();
+                subgraph.nodes = [];
+                subgraph.tensors = [];
+                this._graphs.push(subgraph);
+
+                // nodes
+                for (const nodeOffset of nodeOffsets) {
+                    reader.seek(nodeOffset);
+                    const node = {};
+                    node.id = reader.int32();
+                    node.inputs = reader.uint32s();
+                    node.outputs = reader.uint32s();
+                    const typeOffset = reader.int32();
+                    node.name = reader.string();
+                    const attributeOffsets = reader.uint32s();
+                    node.dynamicShape = reader.boolean();
+
+                    reader.seek(typeOffset);
+                    node.version = reader.int32();
+                    const index = reader.int32();
+                    const paramsOffset = reader.uint32();
+
+                    const schema = operator(index, node.version);
+                    node.type = schema ? schema.name : index.toString();
+                    const paramTypes = schema ? schema.params : [];
+
+                    node.params = [];
+                    if (paramsOffset) {
+                        reader.seek(paramsOffset);
+                        for (const paramType of paramTypes) {
+                            if (paramType !== 'boolean') {
+                                reader.align(4);
+                            }
+                            switch (paramType) {
+                                case 'i':
+                                    node.params.push(reader.int32());
+                                    break;
+                                case 'f':
+                                    node.params.push(reader.float32());
+                                    break;
+                                case 'i[]':
+                                    node.params.push(reader.int32s());
+                                    break;
+                                case 'f[]':
+                                    node.params.push(reader.float32s());
+                                    break;
+                                case 'boolean':
+                                    node.params.push(reader.boolean());
+                                    break;
+                                case 'string':
+                                    node.params.push(reader.string());
+                                    break;
+                                case 'anchors':
+                                    node.params.push(reader.anchors(4));
+                                    break;
+                                default:
+                                    throw new tengine.Error("Unsupported param type '" + paramType + "' in '" + node.type + "'.");
+                            }
                         }
                     }
-                }
 
-                if (node.type === 'Slice') {
-                    node.params[6] = (this._originalFormat == 5) ? node.params[6] : 0;
+                    if (node.type === 'Slice') {
+                        node.params[6] = (this._originalFormat == 5) ? node.params[6] : 0;
+                    }
+
+                    node.attributes = attributeOffsets.map((attributeOffset) => {
+                        reader.seek(attributeOffset);
+                        const name = reader.string();
+                        const value = reader.string();
+                        const type = reader.int32();
+                        return { name: name, value: value, type: type };
+                    });
+
+                    subgraph.nodes.push(node);
                 }
 
-                node.attributes = attributeOffsets.map((attributeOffset) => {
-                    reader.seek(attributeOffset);
-                    const name = reader.string();
-                    const value = reader.string();
-                    const type = reader.int32();
-                    return { name: name, value: value, type: type };
+                // buffers
+                const buffers = bufferOffsets.map((bufferOffset) => {
+                    reader.seek(bufferOffset);
+                    const size = reader.uint32();
+                    const offset = reader.int32();
+                    if (offset !== 0) {
+                        reader.seek(offset);
+                        return reader.read(size);
+                    }
+                    return null;
                 });
 
-                subgraph.nodes.push(node);
-            }
+                // tensors
+                subgraph.tensors = tensorOffsets.map((tensorOffset) => {
+                    reader.seek(tensorOffset);
+                    const tensor = {};
+                    tensor.id = reader.int32();
+                    tensor.buffer = buffers[reader.int32()];
+                    tensor.dims = reader.int32s();
+                    tensor.name = reader.string();
+                    const quantparamsOffset = reader.int32();
+                    tensor.layout = reader.int32();
+                    tensor.type = reader.int32(); // ar = 1, const = 2, input = 3, vdep, unknown
+                    tensor.dataType = reader.int32();
+                    if (quantparamsOffset) {
+                        reader.seek(quantparamsOffset);
+                        tensor.quantparams = {
+                            zeroPoint: reader.int32(),
+                            scale: reader.float32(),
+                            width: reader.int32()
+                        };
+                    }
+                    return tensor;
+                });
 
-            // buffers
-            const buffers = bufferOffsets.map((bufferOffset) => {
-                reader.seek(bufferOffset);
-                const size = reader.uint32();
-                const offset = reader.int32();
-                if (offset !== 0) {
-                    reader.seek(offset);
-                    return reader.read(size);
-                }
-                return null;
-            });
-
-            // tensors
-            subgraph.tensors = tensorOffsets.map((tensorOffset) => {
-                reader.seek(tensorOffset);
-                const tensor = {};
-                tensor.id = reader.int32();
-                tensor.buffer = buffers[reader.int32()];
-                tensor.dims = reader.int32s();
-                tensor.name = reader.string();
-                const quantparamsOffset = reader.int32();
-                tensor.layout = reader.int32();
-                tensor.type = reader.int32(); // ar = 1, const = 2, input = 3, vdep, unknown
-                tensor.dataType = reader.int32();
-                if (quantparamsOffset) {
-                    reader.seek(quantparamsOffset);
-                    tensor.quantparams = {
-                        zeroPoint: reader.int32(),
-                        scale: reader.float32(),
-                        width: reader.int32()
-                    };
-                }
-                return tensor;
-            });
-
-            for (const node of subgraph.nodes) {
-                if (node.type === 'Convolution') {
-                    switch (subgraph.graphLayout) {
-                        case 0: // NCHW
-                            node.params[6] = subgraph.tensors[node.inputs[1]].dims[1];
-                            break;
-                        case 1: // NHWC
-                            node.params[6] = subgraph.tensors[node.inputs[1]].dims[3];
-                            break;
-                        default:
-                            throw new tengine.Error("Unsupported 'Convolution' layout '" + subgraph.graphLayout + "'.");
+                for (const node of subgraph.nodes) {
+                    if (node.type === 'Convolution') {
+                        switch (subgraph.graphLayout) {
+                            case 0: // NCHW
+                                node.params[6] = subgraph.tensors[node.inputs[1]].dims[1];
+                                break;
+                            case 1: // NHWC
+                                node.params[6] = subgraph.tensors[node.inputs[1]].dims[3];
+                                break;
+                            default:
+                                throw new tengine.Error("Unsupported 'Convolution' layout '" + subgraph.graphLayout + "'.");
+                        }
                     }
                 }
             }
-
+            delete this._stream;
         }
     }
 
     get version() {
+        this._read();
         return this._majorVersion + '.' + this._minorVersion;
     }
 
     get source() {
+        this._read();
         switch (this._originalFormat) {
             case 0: return '';
             case 1: return 'Tengine';
@@ -812,6 +820,7 @@ tengine.ModelFileReader = class {
     }
 
     get graphs() {
+        this._read();
         return this._graphs;
     }
 };

+ 18 - 11
source/tflite.js

@@ -90,6 +90,7 @@ tflite.Model = class {
         this._format = 'TensorFlow Lite';
         this._format = this._format + ' v' + model.version.toString();
         this._description = model.description || '';
+        this._metadata = [];
         const builtinOperators = new Map();
         const upperCase = new Set([ '2D', 'LSH', 'SVDF', 'RNN', 'L2', 'LSTM' ]);
         for (const key of Object.keys(tflite.schema.BuiltinOperator)) {
@@ -120,11 +121,21 @@ tflite.Model = class {
                         const reader = flatbuffers.BinaryReader.open(data);
                         if (tflite.schema.ModelMetadata.identifier(reader)) {
                             modelMetadata = tflite.schema.ModelMetadata.create(reader);
-                            this._name = modelMetadata.name || '';
-                            this._version = modelMetadata.version || '';
-                            this._description = modelMetadata.description ? [ this.description, modelMetadata.description].join(' ') : this._description;
-                            this._author = modelMetadata.author || '';
-                            this._license = modelMetadata.license || '';
+                            if (modelMetadata.name) {
+                                this._name = modelMetadata.name;
+                            }
+                            if (modelMetadata.version) {
+                                this._version = modelMetadata.version;
+                            }
+                            if (modelMetadata.description) {
+                                this._description = this._description ? [ this._description, modelMetadata.description].join(' ') : modelMetadata.description;
+                            }
+                            if (modelMetadata.author) {
+                                this._metadata.push({ name: 'author', value: modelMetadata.author });
+                            }
+                            if (modelMetadata.license) {
+                                this._metadata.push({ name: 'license', value: modelMetadata.license });
+                            }
                         }
                         break;
                     }
@@ -164,12 +175,8 @@ tflite.Model = class {
         return this._description;
     }
 
-    get author() {
-        return this._author;
-    }
-
-    get license() {
-        return this._license;
+    get metadata() {
+        return this._metadata;
     }
 
     get graphs() {

+ 3 - 18
source/view-sidebar.js

@@ -737,9 +737,6 @@ sidebar.ModelSidebar = class {
         if (model.producer) {
             this._addProperty('producer', new sidebar.ValueTextView(this._host, model.producer));
         }
-        if (model.source) {
-            this._addProperty('source', new sidebar.ValueTextView(this._host, model.source));
-        }
         if (model.name) {
             this._addProperty('name', new sidebar.ValueTextView(this._host, model.name));
         }
@@ -749,15 +746,6 @@ sidebar.ModelSidebar = class {
         if (model.description) {
             this._addProperty('description', new sidebar.ValueTextView(this._host, model.description));
         }
-        if (model.author) {
-            this._addProperty('author', new sidebar.ValueTextView(this._host, model.author));
-        }
-        if (model.company) {
-            this._addProperty('company', new sidebar.ValueTextView(this._host, model.company));
-        }
-        if (model.license) {
-            this._addProperty('license', new sidebar.ValueTextView(this._host, model.license));
-        }
         if (model.domain) {
             this._addProperty('domain', new sidebar.ValueTextView(this._host, model.domain));
         }
@@ -767,14 +755,11 @@ sidebar.ModelSidebar = class {
         if (model.runtime) {
             this._addProperty('runtime', new sidebar.ValueTextView(this._host, model.runtime));
         }
-
-        const metadata = model.metadata;
-        if (metadata) {
-            for (const property of model.metadata) {
-                this._addProperty(property.name, new sidebar.ValueTextView(this._host, property.value));
+        if (model.metadata) {
+            for (const entry of model.metadata) {
+                this._addProperty(entry.name, new sidebar.ValueTextView(this._host, entry.value));
             }
         }
-
         const graphs = Array.isArray(model.graphs) ? model.graphs : [];
         if (graphs.length > 1) {
             const graphSelector = new sidebar.SelectView(this._host, model.graphs, graph);