circle.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. import * as flatbuffers from '../source/flatbuffers.js';
  2. import * as flexbuffers from '../source/flexbuffers.js';
  3. import * as zip from '../source/zip.js';
  4. const circle = {};
  5. circle.ModelFactory = class {
  6. match(context) {
  7. const reader = context.peek('flatbuffers.binary');
  8. if (reader && reader.identifier === 'CIR0') {
  9. context.type = 'circle.flatbuffers';
  10. context.target = reader;
  11. return;
  12. }
  13. const obj = context.peek('json');
  14. if (obj && obj.subgraphs && obj.operator_codes) {
  15. context.type = 'circle.flatbuffers.json';
  16. context.target = obj;
  17. return;
  18. }
  19. }
  20. async open(context) {
  21. circle.schema = await context.require('./circle-schema');
  22. circle.schema = circle.schema.circle;
  23. let model = null;
  24. const attachments = new Map();
  25. switch (context.type) {
  26. case 'circle.flatbuffers.json': {
  27. try {
  28. const reader = context.read('flatbuffers.text');
  29. model = circle.schema.Model.createText(reader);
  30. } catch (error) {
  31. const message = error && error.message ? error.message : error.toString();
  32. throw new circle.Error(`File text format is not circle.Model (${message.replace(/\.$/, '')}).`);
  33. }
  34. break;
  35. }
  36. case 'circle.flatbuffers': {
  37. try {
  38. const reader = context.target;
  39. model = circle.schema.Model.create(reader);
  40. } catch (error) {
  41. const message = error && error.message ? error.message : error.toString();
  42. throw new circle.Error(`File format is not circle.Model (${message.replace(/\.$/, '')}).`);
  43. }
  44. try {
  45. const stream = context.stream;
  46. const archive = zip.Archive.open(stream);
  47. if (archive) {
  48. for (const [name, value] of archive.entries) {
  49. attachments.set(name, value);
  50. }
  51. }
  52. } catch (error) {
  53. // continue regardless of error
  54. }
  55. break;
  56. }
  57. default: {
  58. throw new circle.Error(`Unsupported Circle format '${context.type}'.`);
  59. }
  60. }
  61. const metadata = await context.metadata('circle-metadata.json');
  62. return new circle.Model(metadata, model);
  63. }
  64. };
  65. circle.Model = class {
  66. constructor(metadata, model) {
  67. this._graphs = [];
  68. this._format = 'Circle';
  69. this._format = `${this._format} v${model.version}`;
  70. this._description = model.description || '';
  71. this._metadata = new Map();
  72. const builtinOperators = new Map();
  73. const upperCase = new Set([ '2D', 'LSH', 'SVDF', 'RNN', 'L2', 'LSTM' ]);
  74. for (const key of Object.keys(circle.schema.BuiltinOperator)) {
  75. const value = key === 'BATCH_MATMUL' ? 'BATCH_MAT_MUL' : key;
  76. const name = value.split('_').map((s) => (s.length < 1 || upperCase.has(s)) ? s : s[0] + s.substring(1).toLowerCase()).join('');
  77. const index = circle.schema.BuiltinOperator[key];
  78. builtinOperators.set(index, name);
  79. }
  80. const operators = model.operator_codes.map((operator) => {
  81. const code = operator.builtin_code || 0;
  82. const version = operator.version;
  83. const custom = code === circle.schema.BuiltinOperator.CUSTOM;
  84. const name = custom ? operator.custom_code ? operator.custom_code : 'Custom' : builtinOperators.has(code) ? builtinOperators.get(code) : code.toString();
  85. return custom ? { name: name, version: version, custom: true } : { name: name, version: version };
  86. });
  87. let modelMetadata = null;
  88. for (const metadata of model.metadata) {
  89. const buffer = model.buffers[metadata.buffer];
  90. if (buffer) {
  91. switch (metadata.name) {
  92. case 'min_runtime_version': {
  93. const data = buffer.data || new Uint8Array(0);
  94. this._runtime = new TextDecoder().decode(data);
  95. break;
  96. }
  97. case 'TFLITE_METADATA': {
  98. const data = buffer.data || new Uint8Array(0);
  99. const reader = flatbuffers.BinaryReader.open(data);
  100. if (circle.schema.ModelMetadata.identifier(reader)) {
  101. modelMetadata = circle.schema.ModelMetadata.create(reader);
  102. if (modelMetadata.name) {
  103. this._name = modelMetadata.name;
  104. }
  105. if (modelMetadata.version) {
  106. this._version = modelMetadata.version;
  107. }
  108. if (modelMetadata.description) {
  109. this._description = this._description ? [ this._description, modelMetadata.description].join(' ') : modelMetadata.description;
  110. }
  111. if (modelMetadata.author) {
  112. this._metadata.set('author', modelMetadata.author);
  113. }
  114. if (modelMetadata.license) {
  115. this._metadata.set('license', modelMetadata.license);
  116. }
  117. }
  118. break;
  119. }
  120. default: {
  121. break;
  122. }
  123. }
  124. }
  125. }
  126. const subgraphs = model.subgraphs;
  127. const subgraphsMetadata = modelMetadata ? modelMetadata.subgraph_metadata : null;
  128. for (let i = 0; i < subgraphs.length; i++) {
  129. const subgraph = subgraphs[i];
  130. const name = subgraphs.length > 1 ? i.toString() : '';
  131. const subgraphMetadata = subgraphsMetadata && i < subgraphsMetadata.length ? subgraphsMetadata[i] : null;
  132. this._graphs.push(new circle.Graph(metadata, subgraph, subgraphMetadata, name, operators, model));
  133. }
  134. }
  135. get format() {
  136. return this._format;
  137. }
  138. get runtime() {
  139. return this._runtime;
  140. }
  141. get name() {
  142. return this._name;
  143. }
  144. get version() {
  145. return this._version;
  146. }
  147. get description() {
  148. return this._description;
  149. }
  150. get metadata() {
  151. return this._metadata;
  152. }
  153. get graphs() {
  154. return this._graphs;
  155. }
  156. };
  157. circle.Graph = class {
  158. constructor(metadata, subgraph, subgraphMetadata, name, operators, model) {
  159. this._nodes = [];
  160. this._inputs = [];
  161. this._outputs = [];
  162. this._name = subgraph.name || name;
  163. const tensors = new Map();
  164. const args = (index) => {
  165. if (index === -1) {
  166. return null;
  167. }
  168. if (!tensors.has(index)) {
  169. if (index < subgraph.tensors.length) {
  170. const tensor = subgraph.tensors[index];
  171. const buffer = model.buffers[tensor.buffer];
  172. const is_variable = tensor.is_variable;
  173. const data = buffer ? buffer.data : null;
  174. const initializer = (data && data.length > 0) || is_variable ? new circle.Tensor(index, tensor, buffer, is_variable) : null;
  175. tensors.set(index, new circle.Value(index, tensor, initializer));
  176. } else {
  177. tensors.set(index, new circle.Value(index, { name: '' }, null));
  178. }
  179. }
  180. return tensors.get(index);
  181. };
  182. for (let i = 0; i < subgraph.operators.length; i++) {
  183. const node = subgraph.operators[i];
  184. const index = node.opcode_index;
  185. const operator = index < operators.length ? operators[index] : { name: `(${index})` };
  186. this._nodes.push(new circle.Node(metadata, node, operator, i.toString(), args));
  187. }
  188. const applyTensorMetadata = (argument, tensorMetadata) => {
  189. if (tensorMetadata) {
  190. const description = tensorMetadata.description;
  191. if (description) {
  192. argument.description = description;
  193. }
  194. const content = tensorMetadata.content;
  195. if (argument.type && content) {
  196. let denotation = null;
  197. const contentProperties = content.content_properties;
  198. if (contentProperties instanceof circle.schema.FeatureProperties) {
  199. denotation = 'Feature';
  200. } else if (contentProperties instanceof circle.schema.ImageProperties) {
  201. denotation = 'Image';
  202. switch (contentProperties.color_space) {
  203. case 0: denotation += '(Unknown)'; break;
  204. case 1: denotation += '(RGB)'; break;
  205. case 2: denotation += '(Grayscale)'; break;
  206. default: throw circle.Error(`Unsupported image color space '${contentProperties.color_space}'.`);
  207. }
  208. } else if (contentProperties instanceof circle.schema.BoundingBoxProperties) {
  209. denotation = 'BoundingBox';
  210. } else if (contentProperties instanceof circle.schema.AudioProperties) {
  211. denotation = `Audio(${contentProperties.sample_rate},${contentProperties.channels})`;
  212. }
  213. if (denotation) {
  214. argument.type.denotation = denotation;
  215. }
  216. }
  217. }
  218. };
  219. const inputs = subgraph.inputs;
  220. for (let i = 0; i < inputs.length; i++) {
  221. const input = inputs[i];
  222. const value = args(input);
  223. if (subgraphMetadata && i < subgraphMetadata.input_tensor_metadata.length) {
  224. applyTensorMetadata(value, subgraphMetadata.input_tensor_metadata[i]);
  225. }
  226. this._inputs.push(new circle.Argument(value ? value.name : '?', true, value ? [ value ] : []));
  227. }
  228. const outputs = subgraph.outputs;
  229. for (let i = 0; i < outputs.length; i++) {
  230. const output = outputs[i];
  231. const value = args(output);
  232. if (subgraphMetadata && i < subgraphMetadata.output_tensor_metadata.length) {
  233. applyTensorMetadata(value, subgraphMetadata.output_tensor_metadata[i]);
  234. }
  235. this._outputs.push(new circle.Argument(value ? value.name : '?', true, value ? [ value ] : []));
  236. }
  237. }
  238. get name() {
  239. return this._name;
  240. }
  241. get inputs() {
  242. return this._inputs;
  243. }
  244. get outputs() {
  245. return this._outputs;
  246. }
  247. get nodes() {
  248. return this._nodes;
  249. }
  250. };
  251. circle.Node = class {
  252. constructor(metadata, node, type, location, args) {
  253. this._location = location;
  254. this._type = type.custom ? { name: type.name, category: 'custom' } : metadata.type(type.name);
  255. this._inputs = [];
  256. this._outputs = [];
  257. this._attributes = [];
  258. if (node) {
  259. let inputs = [];
  260. let outputs = [];
  261. inputs = Array.from(node.inputs || new Int32Array(0));
  262. outputs = Array.from(node.outputs || new Int32Array(0));
  263. let inputIndex = 0;
  264. while (inputIndex < inputs.length) {
  265. let count = 1;
  266. let inputName = null;
  267. let inputVisible = true;
  268. const inputArguments = [];
  269. if (this._type && this._type.inputs && inputIndex < this._type.inputs.length) {
  270. const input = this._type.inputs[inputIndex];
  271. inputName = input.name;
  272. if (input.option == 'variadic') {
  273. count = inputs.length - inputIndex;
  274. }
  275. if (input && input.visible === false) {
  276. inputVisible = false;
  277. }
  278. }
  279. const inputArray = inputs.slice(inputIndex, inputIndex + count);
  280. for (const index of inputArray) {
  281. const value = args(index);
  282. if (value) {
  283. inputArguments.push(value);
  284. }
  285. }
  286. inputIndex += count;
  287. inputName = inputName ? inputName : inputIndex.toString();
  288. this._inputs.push(new circle.Argument(inputName, inputVisible, inputArguments));
  289. }
  290. for (let k = 0; k < outputs.length; k++) {
  291. const index = outputs[k];
  292. const outputArguments = [];
  293. const value = args(index);
  294. if (value) {
  295. outputArguments.push(value);
  296. }
  297. let outputName = k.toString();
  298. if (this._type && this._type.outputs && k < this._type.outputs.length) {
  299. const output = this._type.outputs[k];
  300. if (output && output.name) {
  301. outputName = output.name;
  302. }
  303. }
  304. this._outputs.push(new circle.Argument(outputName, true, outputArguments));
  305. }
  306. if (type.custom && node.custom_options.length > 0) {
  307. let decoded = false;
  308. if (node.custom_options_format === circle.schema.CustomOptionsFormat.FLEXBUFFERS) {
  309. try {
  310. const reader = flexbuffers.BinaryReader.open(node.custom_options);
  311. if (reader) {
  312. const custom_options = reader.read();
  313. if (Array.isArray(custom_options)) {
  314. const attribute = new circle.Attribute(null, 'custom_options', custom_options);
  315. this._attributes.push(attribute);
  316. decoded = true;
  317. } else if (custom_options) {
  318. for (const [key, value] of Object.entries(custom_options)) {
  319. const schema = metadata.attribute(type.name, key);
  320. const attribute = new circle.Attribute(schema, key, value);
  321. this._attributes.push(attribute);
  322. }
  323. decoded = true;
  324. }
  325. }
  326. } catch (err) {
  327. // continue regardless of error
  328. }
  329. }
  330. if (!decoded) {
  331. const schema = metadata.attribute(type.name, 'custom');
  332. this._attributes.push(new circle.Attribute(schema, 'custom', Array.from(node.custom_options)));
  333. }
  334. }
  335. const options = node.builtin_options;
  336. if (options) {
  337. for (const [name, value] of Object.entries(options)) {
  338. if (name === 'fused_activation_function' && value !== 0) {
  339. const activationFunctionMap = { 1: 'Relu', 2: 'ReluN1To1', 3: 'Relu6', 4: 'Tanh', 5: 'SignBit' };
  340. if (!activationFunctionMap[value]) {
  341. throw new circle.Error(`Unsupported activation funtion index '${JSON.stringify(value)}'.`);
  342. }
  343. const type = activationFunctionMap[value];
  344. this._chain = [ new circle.Node(metadata, null, { name: type }, null, []) ];
  345. }
  346. const schema = metadata.attribute(type.name, name);
  347. this._attributes.push(new circle.Attribute(schema, name, value));
  348. }
  349. }
  350. }
  351. }
  352. get type() {
  353. return this._type;
  354. }
  355. get name() {
  356. return '';
  357. }
  358. get location() {
  359. return this._location;
  360. }
  361. get inputs() {
  362. return this._inputs;
  363. }
  364. get outputs() {
  365. return this._outputs;
  366. }
  367. get chain() {
  368. return this._chain;
  369. }
  370. get attributes() {
  371. return this._attributes;
  372. }
  373. };
  374. circle.Attribute = class {
  375. constructor(metadata, name, value) {
  376. this._name = name;
  377. this._value = ArrayBuffer.isView(value) ? Array.from(value) : value;
  378. this._type = metadata && metadata.type ? metadata.type : null;
  379. if (this._name === 'fused_activation_function') {
  380. this._visible = false;
  381. }
  382. if (this._type) {
  383. this._value = circle.Utility.enum(this._type, this._value);
  384. }
  385. if (metadata) {
  386. if (metadata.visible === false) {
  387. this._visible = false;
  388. } else if (metadata.default !== undefined) {
  389. value = this._value;
  390. if (typeof value === 'function') {
  391. value = value();
  392. }
  393. if (value === metadata.default) {
  394. this._visible = false;
  395. }
  396. }
  397. }
  398. }
  399. get name() {
  400. return this._name;
  401. }
  402. get type() {
  403. return this._type;
  404. }
  405. get value() {
  406. return this._value;
  407. }
  408. get visible() {
  409. return this._visible == false ? false : true;
  410. }
  411. };
  412. circle.Argument = class {
  413. constructor(name, visible, value) {
  414. this._name = name;
  415. this._visible = visible;
  416. this._value = value;
  417. }
  418. get name() {
  419. return this._name;
  420. }
  421. get visible() {
  422. return this._visible;
  423. }
  424. get value() {
  425. return this._value;
  426. }
  427. };
  428. circle.Value = class {
  429. constructor(index, tensor, initializer) {
  430. const name = tensor.name || '';
  431. this.name = `${name}\n${index}`;
  432. this.location = index.toString();
  433. this.type = tensor.type !== undefined && tensor.shape !== undefined ? new circle.TensorType(tensor) : null;
  434. this.initializer = initializer;
  435. const quantization = tensor.quantization;
  436. if (quantization && (quantization.scale.length > 0 || quantization.zero_point.length > 0 || quantization.min.length > 0 || quantization.max.length)) {
  437. this.quantization = {
  438. type: 'linear',
  439. dimension: quantization.quantized_dimension,
  440. scale: quantization.scale,
  441. offset: quantization.zero_point.map((value) => value.toNumber()),
  442. min: quantization.min,
  443. max: quantization.max
  444. };
  445. }
  446. }
  447. };
  448. circle.Tensor = class {
  449. constructor(index, tensor, buffer, is_variable) {
  450. this._location = index.toString();
  451. this._type = new circle.TensorType(tensor);
  452. this._is_variable = is_variable;
  453. this._name = tensor.name;
  454. this._data = buffer.data.slice(0);
  455. }
  456. get category() {
  457. return this._is_variable ? 'Variable' : '';
  458. }
  459. get name() {
  460. return this._name;
  461. }
  462. get location() {
  463. return this._location;
  464. }
  465. get type() {
  466. return this._type;
  467. }
  468. get encoding() {
  469. switch (this._type.dataType) {
  470. case 'string': return '|';
  471. default: return '<';
  472. }
  473. }
  474. get values() {
  475. switch (this._type.dataType) {
  476. case 'string': {
  477. let offset = 0;
  478. const data = new DataView(this._data.buffer, this._data.byteOffset, this._data.byteLength);
  479. const count = data.getInt32(0, true);
  480. offset += 4;
  481. const offsetTable = [];
  482. for (let j = 0; j < count; j++) {
  483. offsetTable.push(data.getInt32(offset, true));
  484. offset += 4;
  485. }
  486. offsetTable.push(this._data.length);
  487. const stringTable = [];
  488. const utf8Decoder = new TextDecoder('utf-8');
  489. for (let k = 0; k < count; k++) {
  490. const textArray = this._data.subarray(offsetTable[k], offsetTable[k + 1]);
  491. stringTable.push(utf8Decoder.decode(textArray));
  492. }
  493. return stringTable;
  494. }
  495. default: return this._data;
  496. }
  497. }
  498. };
  499. circle.TensorType = class {
  500. constructor(tensor) {
  501. this._dataType = circle.Utility.dataType(tensor.type);
  502. this._shape = new circle.TensorShape(Array.from(tensor.shape || []));
  503. }
  504. get dataType() {
  505. return this._dataType;
  506. }
  507. get shape() {
  508. return this._shape;
  509. }
  510. set denotation(value) {
  511. this._denotation = value;
  512. }
  513. get denotation() {
  514. return this._denotation;
  515. }
  516. toString() {
  517. return this.dataType + this._shape.toString();
  518. }
  519. };
  520. circle.TensorShape = class {
  521. constructor(dimensions) {
  522. this._dimensions = dimensions;
  523. }
  524. get dimensions() {
  525. return this._dimensions;
  526. }
  527. toString() {
  528. if (!this._dimensions || this._dimensions.length == 0) {
  529. return '';
  530. }
  531. return `[${this._dimensions.map((dimension) => dimension.toString()).join(',')}]`;
  532. }
  533. };
  534. circle.Utility = class {
  535. static dataType(type) {
  536. if (!circle.Utility._tensorTypeMap) {
  537. circle.Utility._tensorTypeMap = new Map(Object.entries(circle.schema.TensorType).map(([key, value]) => [ value, key.toLowerCase() ]));
  538. circle.Utility._tensorTypeMap.set(6, 'boolean');
  539. }
  540. return circle.Utility._tensorTypeMap.has(type) ? circle.Utility._tensorTypeMap.get(type) : '?';
  541. }
  542. static enum(name, value) {
  543. const type = name && circle.schema ? circle.schema[name] : undefined;
  544. if (type) {
  545. circle.Utility._enums = circle.Utility._enums || new Map();
  546. if (!circle.Utility._enums.has(name)) {
  547. const entries = new Map(Object.entries(type).map(([key, value]) => [ value, key ]));
  548. circle.Utility._enums.set(name, entries);
  549. }
  550. const map = circle.Utility._enums.get(name);
  551. if (map.has(value)) {
  552. return map.get(value);
  553. }
  554. }
  555. return value;
  556. }
  557. };
  558. circle.Error = class extends Error {
  559. constructor(message) {
  560. super(message);
  561. this.name = 'Error loading Circle model.';
  562. }
  563. };
  564. export const ModelFactory = circle.ModelFactory;