diff --git a/docs/buttons.png b/docs/buttons.png new file mode 100644 index 0000000..efca253 Binary files /dev/null and b/docs/buttons.png differ diff --git a/docs/onnx_modifier_delete.png b/docs/onnx_modifier_delete.png new file mode 100644 index 0000000..f5dc8df Binary files /dev/null and b/docs/onnx_modifier_delete.png differ diff --git a/docs/onnx_modifier_logo_1.png b/docs/onnx_modifier_logo_1.png new file mode 100644 index 0000000..dce74ba Binary files /dev/null and b/docs/onnx_modifier_logo_1.png differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..18c8b13 --- /dev/null +++ b/readme.md @@ -0,0 +1,50 @@ + + +# Introduction + +To edit an ONNX model, One common way is to visualize the model graph, and edit it using ONNX Python API. This works fine. However, we have to code to edit, then visualize to check. The two processes may iterate for many times, which is time-consuming. + +What if we have a tool, which allow us **edit and preview the editing effect in a totally visualization fashion**? Then `onnx-modifier` comes. With it, we can focus on editing the model graph in the visualization pannel. Finally, all editing information will be summarized and processed by Python ONNX automatically. Most importantly, our time will be saved! + +`onnx-modifier` is built based on the popular network viewer [netron](https://github.com/lutzroeder/netron) and the lightweight web application framework [flask](https://github.com/pallets/flask). + +Currently, the following editing operations are supported: + +- Delete a single node. +- Delete a node and all the nodes rooted with it. +- Recover/Reset a node. + +Hope it helps! + +# Get started + +Install the require Python packages by + +```bash +pip install onnx +pip install flask +``` + +Then run + +```bash +python app.py +``` + +Click the url in the output info generated by flask (`http://127.0.0.1:5000/` for example), then `onnx-modifier` will be launched in the web browser. + +Click `Open Model...` to upload the onnx model to edit. The model will be parsed and show on the page. + +# Edit + +On the left-top of the page, + + + + + + + + + +All the editing \ No newline at end of file diff --git a/static/view-grapher.js b/static/view-grapher.js index 0670271..aa64bbc 100644 --- a/static/view-grapher.js +++ b/static/view-grapher.js @@ -17,6 +17,8 @@ grapher.Graph = class { this._modelNodeName2ViewNode = new Map(); this._modelNodeName2State = new Map(); this._namedEdges = new Map(); + + this._pathArgumentNames = new Set(); // the name of arguments which occurs in both sides of an edge } get options() { @@ -82,6 +84,7 @@ grapher.Graph = class { this._namedEdges.get(from_node_name).push(to_node_name); } + setParent(node, parent) { if (!this._isCompound) { throw new Error("Cannot set parent in a non-compound graph"); diff --git a/static/view-sidebar.js b/static/view-sidebar.js index 3aeb865..5b99a19 100644 --- a/static/view-sidebar.js +++ b/static/view-sidebar.js @@ -129,16 +129,11 @@ sidebar.NodeSidebar = class { this._node = node; this._modelNodeName = modelNodeName; this._elements = []; + this._renameAuxelements = [] // auxilary elements for input/output renaming this._attributes = []; this._inputs = []; this._outputs = []; - this._addButton('Delete With Children'); - this.add_span('between-delete') - this._addButton('Delete Single Node'); - this.add_separator('line_DR') - this._addButton('Reset Node'); - if (node.type) { let showDocumentation = null; const type = node.type; @@ -188,22 +183,35 @@ sidebar.NodeSidebar = class { } const inputs = node.inputs; + // console.log(inputs) if (inputs && inputs.length > 0) { this._addHeader('Inputs'); for (const input of inputs) { - this._addInput(input.name, input); + this._addInput(input.name, input); // 这里的input.name是小白格前面的名称(不是方格内的) + this.add_rename_aux_element(input.arguments); } } + // console.log(this._renameAuxelements) const outputs = node.outputs; if (outputs && outputs.length > 0) { this._addHeader('Outputs'); for (const output of outputs) { this._addOutput(output.name, output); + this.add_rename_aux_element(output.arguments); } } this.add_separator('sidebar-view-separator') + + this._addButton('Delete With Children'); + this.add_span() + this._addButton('Delete Single Node'); + this.add_span() + this._addButton('Reset Node'); + + this.add_separator('sidebar-view-separator'); + this._addHeader('Rename helper'); } @@ -215,13 +223,31 @@ sidebar.NodeSidebar = class { add_span(className) { const span = this._host.document.createElement('span'); - span.innerHTML = " "; // (if this doesn't work, try " ") + span.innerHTML = "   "; // (if this doesn't work, try " ") span.className = className; this._elements.push(span); } + add_rename_aux_element (arguments_) { + if (arguments_.length > 0) { + for (const argument of arguments_) { + if (this._host._view._graph._pathArgumentNames.has(argument.name)) { + const buttonElement = this._host.document.createElement('button'); + buttonElement.className = 'sidebar-view-button'; + buttonElement.innerText = argument.name; + this._renameAuxelements.push(buttonElement); + } + } + } + + + } + render() { - return this._elements; + console.log(this._elements) + console.log(this._renameAuxelements) + // return this._elements; + return this._elements.concat(this._renameAuxelements); } _addHeader(title) { @@ -258,6 +284,7 @@ sidebar.NodeSidebar = class { const item = new sidebar.NameValueView(this._host, name, view); this._inputs.push(item); this._elements.push(item.render()); + } } @@ -290,7 +317,7 @@ sidebar.NodeSidebar = class { if (title === 'Reset Node') { // console.log('pressed') buttonElement.addEventListener('click', () => { - this._host._view._graph.recover_node(this._modelNodeName) + this._host._view._graph.reset_node(this._modelNodeName) }); } @@ -424,13 +451,16 @@ sidebar.NameValueView = class { const nameElement = this._host.document.createElement('div'); nameElement.className = 'sidebar-view-item-name'; - + + // ===> 这一段是input框前的名称,如attributte的pad,(不包含后面的小白块!!!)太有误导性了。。。 + // console.log(name) const nameInputElement = this._host.document.createElement('input'); nameInputElement.setAttribute('type', 'text'); nameInputElement.setAttribute('value', name); nameInputElement.setAttribute('title', name); - nameInputElement.setAttribute('readonly', 'true'); + nameInputElement.setAttribute('readonly', 'false'); nameElement.appendChild(nameInputElement); + // <=== 这一段是input框前的名称,如attributte的pad const valueElement = this._host.document.createElement('div'); valueElement.className = 'sidebar-view-item-value-list'; @@ -507,7 +537,7 @@ sidebar.ValueTextView = class { this._host = host; this._elements = []; const element = this._host.document.createElement('div'); - element.className = 'sidebar-view-item-value'; + element.className = 'sidebar-view-item-value'; // 这个渲染出后面一个长白格 this._elements.push(element); if (action) { @@ -532,6 +562,7 @@ sidebar.ValueTextView = class { } render() { + console.log(this._elements) return this._elements; } @@ -665,10 +696,15 @@ class NodeAttributeView { sidebar.ParameterView = class { constructor(host, list) { + this._host = host; this._list = list; this._elements = []; this._items = []; + + // console.log('new ParameterView') + // console.log(list.arguments) // Array(1) for (const argument of list.arguments) { + // console.log(argument) const item = new sidebar.ArgumentView(host, argument); item.on('export-tensor', (sender, tensor) => { this._raise('export-tensor', tensor); @@ -766,6 +802,10 @@ sidebar.ArgumentView = class { return this._element; } + render_rename_aux() { + return this._renameAuxelements; + } + toggle() { if (this._expander) { if (this._expander.innerText == '+') { diff --git a/static/view.js b/static/view.js index 0ec2e89..0d1fe05 100644 --- a/static/view.js +++ b/static/view.js @@ -947,17 +947,7 @@ view.Graph = class extends grapher.Graph { } for (const node of graph.nodes) { - // console.log(node) - // if (this._modelNodeName2State.get(node.name) == 'Deleted') { - // // console.log(this._modelNodeName2State.get(node.name)) - // continue; - // } const viewNode = this.createNode(node); - // My code - // if (this._modelNodeName2State.get(viewNode.modelNodeName) == 'Deleted') { - // // console.log(this._modelNodeName2State.get(node.name)) - // continue; - // } const inputs = node.inputs; for (const input of inputs) { @@ -1098,7 +1088,9 @@ view.Graph = class extends grapher.Graph { for (const argument of this._arguments.values()) { argument.build(); } - console.log(this._namedEdges) + // console.log("this._pathArgumentNames"); + // console.log(this._pathArgumentNames); + // console.log(this._namedEdges) super.build(document, origin); } }; @@ -1366,6 +1358,9 @@ view.Argument = class { this.context.setEdge(edge); this._edges.push(edge); // console.log(this.context._namedEdges); + + // this argument occurs in both sides of the edge, so it is a `path` argument + this.context._pathArgumentNames.add(this._argument.name); } } }