const caffe2 = {}; caffe2.ModelFactory = class { async match(context) { const identifier = context.identifier.toLowerCase(); const extension = identifier.lastIndexOf('.') > 0 ? identifier.split('.').pop().toLowerCase() : ''; switch (extension) { case 'pbtxt': case 'prototxt': { const tags = await context.tags('pbtxt'); if (tags.has('op') && !tags.has('op.attr') && !tags.has('op.graph_op_name') && !tags.has('op.endpoint')) { return context.set('caffe2.pbtxt'); } break; } case 'pb': { const tags = await context.tags('pb'); if (tags.size > 0 && Array.from(tags.keys()).every((tag) => tag <= 9) && Array.from(tags.values()).every((type) => type <= 4)) { if (tags.size === 1 && tags.get(2) === 2 && identifier.endsWith('saved_model.pb')) { return null; } const schema = [[1,2],[2,2],[3,2],[4,0],[5,2],[6,2],[7,2],[8,2],[9,2]]; if (schema.every(([key, value]) => !tags.has(key) || tags.get(key) === value)) { const stream = context.stream; if (stream.length > 3) { const buffer = stream.peek(Math.min(stream.length, 67)); const [signature, size] = buffer; switch (signature) { case 0x0A: if (size < 64 && buffer.length > 2 + size + 1 && buffer.slice(2, 2 + size).every((c) => c >= 32 && c <= 127) && buffer[2 + size] === 0x12) { return context.set('caffe2.pb'); } break; case 0x12: return context.set('caffe2.pb'); default: break; } } } } break; } default: { break; } } return null; } async open(context) { caffe2.proto = await context.require('./caffe2-proto'); caffe2.proto = caffe2.proto.caffe2; const metadata = await context.metadata('caffe2-metadata.json'); const identifier = context.identifier; const parts = identifier.split('.'); const extension = parts.pop().toLowerCase(); const base = parts.join('.'); let predict = null; let init = null; switch (context.type) { case 'caffe2.pbtxt': { if (base.toLowerCase().endsWith('init_net') || base.toLowerCase().startsWith('init_net')) { init = context; try { const name = identifier.replace('init_net', 'predict_net'); predict = await context.fetch(name); predict.set(context.type); } catch { // continue regardless of error } } else if (base.toLowerCase().endsWith('predict_net') || base.toLowerCase().startsWith('predict_net')) { predict = context; const name = identifier.replace('predict_net', 'init_net'); try { init = await context.fetch(name.replace(/\.pbtxt/, '.pb')); init.set('caffe2.pb'); } catch { try { init = await context.fetch(name); init.set('caffe2.pbtxt'); } catch { // continue regardless of error } } } else { predict = context; try { init = await context.fetch(`${base}_init.pb`); init.set('caffe2.pb'); } catch { // continue regardless of error } } break; } case 'caffe2.pb': { if (base.toLowerCase().endsWith('init_net')) { init = context; const extensions = new Set([extension, 'pb', 'pbtxt']); for (const extension of extensions) { try { const name = `${base.replace(/init_net$/, '')}predict_net.${extension}`; // eslint-disable-next-line no-await-in-loop predict = await context.fetch(name); predict.set(`caffe2.${extension}`); break; } catch { // continue regardless of error } } } else if (base.toLowerCase().endsWith('_init')) { try { const name = `${base.replace(/_init$/, '')}.${extension}`; predict = await context.fetch(name); predict.set(context.type); } catch { // continue regardless of error } } else if (base.toLowerCase().endsWith('predict_net') || base.toLowerCase().startsWith('predict_net')) { predict = context; try { const name = identifier.replace('predict_net', 'init_net'); init = await context.fetch(name); init.set(context.type); } catch { // continue regardless of error } } else { predict = context; try { const file = `${base}_init.${extension}`; init = await context.fetch(file, null); init.set(context.type); } catch { // continue regardless of error } } break; } default: { throw new caffe2.Error(`Unsupported Caffe2 format '${context.type}'.`); } } if (!predict && !init) { throw new caffe2.Error(`Caffe2 model does not contain predict or init data.`); } const open = async (context) => { if (context) { switch (context.type) { case 'caffe2.pb': try { const reader = await context.read('protobuf.binary'); return caffe2.proto.NetDef.decode(reader); } catch (error) { const message = error && error.message ? error.message : error.toString(); throw new caffe2.Error(`File format is not caffe2.NetDef (${message.replace(/\.$/, '')}).`); } case 'caffe2.pbtxt': try { const reader = await context.read('protobuf.text'); reader.field = function(tag, message) { if (message instanceof caffe2.proto.DeviceOption) { message[tag] = this.read(); return; } throw new Error(`Unknown field '${tag}' ${this.location()}`); }; return caffe2.proto.NetDef.decodeText(reader); } catch (error) { const message = error && error.message ? error.message : error.toString(); throw new caffe2.Error(`File format is not caffe2.NetDef (${message.replace(/\.$/, '')}).`); } default: throw new caffe2.Error(`Unsupported Caffe2 predict format '${context.type}'.`); } } return null; }; const predict_net = await open(predict); const init_net = await open(init); return new caffe2.Model(metadata, predict_net, init_net); } }; caffe2.Model = class { constructor(metadata, predict_net, init_net) { const net = predict_net || init_net; this.format = 'Caffe2'; this.domain = net.domain || null; this.modules = [new caffe2.Graph(metadata, predict_net, init_net)]; } }; caffe2.Graph = class { constructor(metadata, predict_net, init_net) { const net = predict_net || init_net; init_net = predict_net ? init_net : null; this.name = net.name || ''; this.nodes = []; this.description = net.type; const initializers = new Set(); const tensors = new Map(); for (const name of net.external_input) { tensors.set(name, new caffe2.Tensor(name, {})); } if (init_net) { const dataTypes = new Map([ ['GivenTensorFill', 'float32'], ['GivenTensorDoubleFill', 'float64'], ['GivenTensorBoolFill', 'boolean'], ['GivenTensorByteStringToUInt8Fill', 'uint8'], ['GivenTensorInt16Fill', 'int16'], ['GivenTensorSInt16Fill', 'int16'], ['GivenTensorIntFill', 'int32'], ['GivenTensorInt64Fill', 'int64'], ['GivenTensorStringFill', 'string'], ['Int8GivenIntTensorFill', 'int32'], ['Int8GivenTensorFill', 'int8'], ['XavierFill', null], ['ConstantFill', null] ]); for (const op of init_net.op) { if (op.output && op.output.length === 1) { const [name] = op.output; const tensor = {}; for (const arg of op.arg) { tensor[arg.name] = arg; } if (!dataTypes.has(op.type)) { throw new caffe2.Error(`Unsupported init op '${op.type}'.`); } tensor.dataType = dataTypes.get(op.type); if (tensor.values && tensor.values.floats && (tensor.values.floats.length !== 1 || tensor.values.floats[0] !== 0)) { initializers.add(name); } tensors.set(name, new caffe2.Tensor(name, tensor)); } } } const scope = {}; for (let i = 0; i < net.op.length; i++) { const op = net.op[i]; op.input = op.input.map((input) => scope[input] ? scope[input] : input); op.output = op.output.map((output) => { if (scope[output]) { const next = `${output}\n${i}`; // custom argument id scope[output] = next; return next; } scope[output] = output; return output; }); } const values = new Map(); values.map = (name, type, tensor) => { if (!values.has(name)) { values.set(name, new caffe2.Value(name, type || null, tensor || null)); } else if (type || tensor) { throw new caffe2.Value(`Duplicate value '${name}'.`); } return values.get(name); }; for (const op of net.op) { let index = 0; for (const name of op.input) { if (index > 0 && tensors.has(name)) { if (!values.has(name)) { values.set(name, new caffe2.Value(name, null, tensors.get(name))); } initializers.add(name); } index++; } } for (const op of net.op) { for (const name of op.output) { if (tensors.has(name)) { initializers.add(name); } } } let lastNode = null; let lastOutput = null; for (const op of net.op) { const node = new caffe2.Node(metadata, op, values); if (op.input.length === 1 && op.output.length >= 1 && op.input[0].split('\n').shift() === op.output[0].split('\n').shift() && lastNode && lastOutput === op.input[0].split('\n').shift()) { lastNode.chain.push(node); } else { this.nodes.push(node); lastNode = null; lastOutput = null; if (op.output.length === 1) { lastNode = node; lastOutput = op.output[0].split('\n').shift(); } } } this.inputs = []; for (const input of net.external_input) { if (net.external_input.length > 1 && initializers.has(input)) { continue; } const argument = new caffe2.Argument(input, [values.map(input)]); this.inputs.push(argument); } this.outputs = []; for (const output of net.external_output) { const argument = new caffe2.Argument(output, [values.map(output)]); this.outputs.push(argument); } } }; caffe2.Argument = class { constructor(name, value, type = null, visible = true) { this.name = name; this.value = value; this.type = type; this.visible = visible; } }; caffe2.Value = class { constructor(name, type, initializer = null) { if (typeof name !== 'string') { throw new caffe2.Error(`Invalid value identifier '${JSON.stringify(name)}'.`); } this.name = name; this.type = !type && initializer ? initializer.type : type; this.quantization = initializer && initializer.quantization ? initializer.quantization : null; this.initializer = initializer; } }; caffe2.Node = class { constructor(metadata, op, values) { this.name = op.name || ''; this.device = op.engine || ''; this.chain = []; this.type = metadata.type(op.type); this.attributes = op.arg.map((arg) => { const schema = metadata.attribute(op.type, arg.name); const name = arg.name; let value = null; let type = null; let visible = true; if (arg.floats && arg.floats.length > 0) { value = arg.floats; } else if (arg.ints && arg.ints.length > 0) { value = arg.ints; } else if (arg.nets && arg.nets.length > 0) { value = arg.nets.map((net) => new caffe2.Graph(metadata, net, null)); type = 'graph[]'; } else if (arg.n) { value = new caffe2.Graph(metadata, arg.n, null); type = 'graph'; } else { value = arg.i; } if (schema) { type = !type && schema.type ? schema.type : type; if (type === 'boolean') { value = value !== 0 && value.toString() !== '0' ? true : false; } if (schema.visible === false) { visible = false; } else if (schema.default !== undefined) { if (value === metadata.default || (value && value.toString() === schema.default.toString())) { visible = false; } } } return new caffe2.Argument(name, value, type, visible); }); const inputs = op.input; const outputs = op.output; this.inputs = []; let inputIndex = 0; if (this.type && this.type.inputs) { for (const inputDef of this.type.inputs) { if (inputIndex < inputs.length || inputDef.option !== 'optional') { const inputCount = (inputDef.option === 'variadic') ? (inputs.length - inputIndex) : 1; const inputArguments = inputs.slice(inputIndex, inputIndex + inputCount).filter((id) => id !== '' || inputDef.option !== 'optional').map((id) => values.map(id)); this.inputs.push(new caffe2.Argument(inputDef.name, inputArguments)); inputIndex += inputCount; } } } else { this.inputs.push(...inputs.slice(inputIndex).map((input, index) => { const inputName = ((inputIndex + index) === 0) ? 'input' : (inputIndex + index).toString(); return new caffe2.Argument(inputName, [values.map(input)]); })); } this.outputs = []; let outputIndex = 0; if (this.type && this.type.outputs) { for (const outputDef of this.type.outputs) { if (outputIndex < outputs.length || outputDef.option !== 'optional') { const outputCount = (outputDef.option === 'variadic') ? (outputs.length - outputIndex) : 1; const outputArguments = outputs.slice(outputIndex, outputIndex + outputCount).map((id) => values.map(id)); this.outputs.push(new caffe2.Argument(outputDef.name, outputArguments)); outputIndex += outputCount; } } } else { this.outputs.push(...outputs.slice(outputIndex).map((output, index) => { const outputName = ((outputIndex + index) === 0) ? 'output' : (outputIndex + index).toString(); return new caffe2.Argument(outputName, [values.map(output)]); })); } } }; caffe2.Tensor = class { constructor(name, tensor) { this.name = name; const shape = tensor.shape && tensor.shape.ints ? tensor.shape.ints : null; this.type = new caffe2.TensorType(tensor.dataType, new caffe2.TensorShape(shape)); this.values = null; this.category = 'Initializer'; this.encoding = '|'; if (tensor.Y_scale !== undefined || tensor.Y_zero_point !== undefined) { this.quantization = { type: 'linear', scale: [tensor.Y_scale ? tensor.Y_scale.f : 0], offset: [tensor.Y_zero_point && typeof tensor.Y_zero_point.i === 'bigint' ? tensor.Y_zero_point.i.toNumber() : 0] }; } if (tensor.values) { switch (this.type.dataType) { case 'float32': this.values = tensor.values.floats; break; case 'boolean': this.values = tensor.values.ints; break; case 'int8': this.values = new Int8Array(tensor.values.s); break; case 'int32': this.values = tensor.values.ints; break; default: break; } } } }; caffe2.TensorType = class { constructor(dataType, shape) { this.dataType = dataType || '?'; this.shape = shape; } toString() { return this.dataType + this.shape.toString(); } }; caffe2.TensorShape = class { constructor(dimensions) { this.dimensions = Array.isArray(dimensions) ? dimensions.map((dim) => typeof dim === 'bigint' ? dim.toNumber() : dim) : dimensions; } toString() { if (Array.isArray(this.dimensions) && this.dimensions.length > 0) { return `[${this.dimensions.map((dim) => dim.toString()).join(',')}]`; } return ''; } }; caffe2.Error = class extends Error { constructor(message) { super(message); this.name = 'Error loading Caffe2 model.'; } }; export const ModelFactory = caffe2.ModelFactory;