This commit is contained in:
Xiang Zhang 2018-09-26 11:45:56 -07:00
Родитель 43aba9d81b
Коммит 764fd2c5a6
60 изменённых файлов: 4866 добавлений и 0 удалений

35
Tools/WinMLDashboard/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,35 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
/dist
/Windows ML Dashboard*
# installer
/release
/installer
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# generated static files
public/*
!public/favicon.ico
!public/index.html
!public/manifest.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock
package-lock.json

Просмотреть файл

@ -0,0 +1,22 @@
# Contributing
## Repository layout
Files in `public/` are copied as-is to the final build output. Any files that should be packaged with the application can be put there.
Files in `src/` are processed and transpiled.
## Source code organization
Inside `src/`, there are:
* `components`: Reusable UI components
* `datastore`: Datastore for data shared among multiple components. Uses [redux](https://redux.js.org/)
* `fonts`: Autogenerated fonts; see [src/fonts/README.md](src/fonts/README.md)
* `native`: Code interfacing with native (non-web) features
* `schema`: Schemas used for input validation and autocomplete
* `view`: UI views
* `App.{css,tsx}`: Root React component
* `electronMain.js`: Electron entry point
* `index.{css,tsx}`: Index component (will be loaded with the page)
* `registerServiceWorker.js`: Service worker that caches content offline so resources can be accessed on a browser without internet connection

Просмотреть файл

@ -0,0 +1,55 @@
# Windows ML Dashboard
Developer dashboard for Windows ML and ONNX.
## Building from source
#### Prerequisites
|Requirements|Version|Download|Command to check|
|------------|-------|--------|----------------|
|Yarn |latest |[here](https://yarnpkg.com/en/docs/install)|`yarn --version`|
|Node.js |latest |[here](https://nodejs.org/en/)|`node --version`|
|Python3 |3.4+ |[here](https://www.python.org/)|`python --version`|
#### Building
1. Git clone [Microsoft/Windows-Machine-Learning](https://github.com/Microsoft/Windows-Machine-Learning) repo.
2. `cd Tools/WinMLDashboard`
3. Run `yarn` to download dependencies.
4. Then, run one of the following:
* `yarn start` to build and start the web application
* `yarn electron-prod` to build and start the desktop application
> All available commands can be seen at [package.json](package.json).
### Debugging
Chromium's dev tools pane can be used for debugging. To open it in the web view, press F12. To open it in the Electron app, run it with flag `--dev-tools`, or select *View -> Toggle Dev Tools* in the application menu, or press *Ctrl+Shift+I*.
### Distribution
To distribute the application, you can copy the whole `build` folder and the files `src/electronMain.js` and `package.json` to `node_modules/electron/dist/resources/app`, and distribute the folder `node_modules/electron/dist`. The final directory structure in `node_modules/electron/dist/resources` should be:
```
app/
├───build/
├───────...
├───src/
│ └───electronMain.js
└───package.json
```
[Electron builder](https://github.com/electron-userland/electron-builder) can be used to generate installers. [See the documentation here](https://www.electron.build/).
### Built with
* [Electron](https://electronjs.org/)
* [React](https://reactjs.org/)
* [Redux](https://redux.js.org/)
### License
This tool is under the MIT license. The license can be found at the root of this repository.

Просмотреть файл

@ -0,0 +1,411 @@
THIRD PARTY SOFTWARE NOTICES AND INFORMATION
Do Not Translate or Localize
This software incorporates material from third parties. Microsoft makes certain
open source code available at http://3rdpartysource.microsoft.com, or you may
send a check or money order for US $5.00, including the product name, the open
source component name, and version number, to:
Source Code Compliance Team
Microsoft Corporation
One Microsoft Way
Redmond, WA 98052
USA
Notwithstanding any other terms, you may reverse engineer this software to the
extent required to debug changes to any libraries licensed under the GNU Lesser
General Public License.
-------------------------START OF OPEN SOURCE LICENSES ------------------
================================================
react 16.4.1
react-dom 16.4.1
=====
MIT License
Copyright (c) Facebook, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
================================================
office-ui-fabric-react 6.39.2
=====
Office UI Fabric React
Copyright (c) Microsoft Corporation
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Note: Usage of the fonts and icons referenced in Office UI Fabric is subject to the terms listed at http://aka.ms/fabric-assets-license
================================================
================================================
ajv 6.5.2
=====
The MIT License (MIT)
Copyright (c) 2015-2017 Evgeny Poberezkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
================================================
react-markdown 3.3.4
=====
The MIT License (MIT)
Copyright (c) 2015 Espen Hovlandsdal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
================================================
react-redux 5.0.7
redux 4.0.0
=====
The MIT License (MIT)
Copyright (c) 2015-present Dan Abramov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
================================================
yauzl 2.9.2
=====
The MIT License (MIT)
Copyright (c) 2014 Josh Wolfe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
================================================
yauzl 2.9.2
=====
The MIT License (MIT)
Copyright (c) 2014 Josh Wolfe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
================================================
babel-minify 0.4.3
=====
Copyright (c) 2015-2016 Amjad Masad <amjad.masad@gmail.com>
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
================================================
electron 2.0.4
Copyright (c) 2013-2018 GitHub Inc.
electron-winstaller 2.6.4
Copyright (c) 2015 GitHub Inc.
=====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
================================================
electron-packager 12.1.2
=====
Copyright (c) 2015 Max Ogden and other contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
================================================
react-app-rewired 1.5.2
=====
MIT License
Copyright (c) 2016 Tim Arney
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
================================================
npm-font-open-sans 1.1.0
=====
Copyright (c) 2015 Enrico Hoffmann
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
================================================
Netron v2.1.1
=====
MIT License
Copyright (c) Lutz Roeder
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
================================================
typescript 2.9.2
Copyright (c) Microsoft Corporation. All rights reserved.
electron-squirrel-startup 1.0.0
Copyright (c) 2004 Lucas Hrabovsky
=====
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
================================================
================================================
@types/jest 23.3.1
@types/node 10.5.2
@types/prop-types 15.5.4
@types/react 16.4.6
@types/react-dom 16.0.6
@types/react-redux 6.0.4
@types/yauzl 2.9.0
=====
This project is licensed under the MIT license.
Copyrights are respective of each contributor listed at the beginning of each definition file.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================

Просмотреть файл

@ -0,0 +1,15 @@
var electronInstaller = require('electron-winstaller');
var path = require("path");
resultPromise = electronInstaller.createWindowsInstaller({
appDirectory: path.join('./release/WinMLDashboard-win32-x64'),
authors: 'Microsoft Corporation',
exe: 'WinMLDashboard.exe',
outputDirectory: path.join('./installer'),
setupExe: 'WinMLDashboard_setup.exe',
setupMsi: 'WinMLDashboard_setup.msi',
version: "1.0.0",
});
// tslint:disable-next-line:no-console
resultPromise.then(() => console.log("It worked!"), (e) => console.log(`No dice: ${e.message}`));

Просмотреть файл

@ -0,0 +1,18 @@
module.exports = function override(config, env) {
config.node = {
__dirname: false,
__filename: false,
Buffer: false,
child_process: false,
dgram: false,
fs: false,
global: false,
net: false,
process: false,
setImmediate: false,
tls: false,
};
config.target = 'electron-renderer';
return config;
}

@ -0,0 +1 @@
Subproject commit 950c9324c185fd4bc60be4b2434c378af4fc5f8e

Просмотреть файл

@ -0,0 +1,92 @@
#!/usr/bin/env python3
import os
from pathlib import Path
from unittest.mock import patch
import re
import shutil
import subprocess
@patch('setuptools.setup')
def get_package_data(mock_setup):
import Netron.setup as netron_setup
package_data = mock_setup.call_args[1]['package_data']['netron']
return (package_data, netron_setup.node_dependencies[0][1])
def get_netron_static_scripts(src_path):
with open(src_path / 'view-browser.html') as f:
scripts = []
regex = re.compile("<script type='text/javascript' src='(.*)'></script>")
for line in f.readlines():
match = re.match(regex, line)
if match:
scripts.append(match.group(1))
return scripts
def rebuild_needed(sources, destination):
try:
destination_mtime = os.path.getmtime(destination)
except FileNotFoundError:
return True
return any(os.path.getmtime(f) >= destination_mtime for f in sources)
def bundle_scripts(files):
bundle = []
for script in files:
with script.open('rb') as f:
bundle.append(f.read())
return b'\n'.join(bundle)
def minify(input_file, output_file):
subprocess.check_call([shutil.which('yarn'), 'minify', str(input_file), '-o', str(output_file)])
def main():
netron = Path('deps/Netron')
src = netron / 'src'
package_data, node_dependencies = get_package_data()
print('Netron package files:\n{}'.format(' '.join(package_data)))
print('Netron Node dependencies:\n{}'.format(' '.join(node_dependencies)))
static_scripts = get_netron_static_scripts(src)
print('These scripts will be bundled:\n{}'.format(' '.join(static_scripts)))
# Update script paths to point to paths before installation
for i, script in enumerate(static_scripts):
if script in package_data:
static_scripts[i] = src / script
else:
for filename in node_dependencies:
path = Path(filename)
if script == path.name:
static_scripts[i] = netron / path
package_data = set(package_data) - set(static_scripts)
public = Path('public')
ignored_extensions = ['.css', '.html', '.ico']
package_data = [src / filename for filename in package_data if not Path(filename).suffix in ignored_extensions]
for package_file in package_data:
try:
os.link(package_file, public / package_file.name)
except FileExistsError:
pass
except FileNotFoundError:
print("Warning: Got FileNotFoundError linking {} -> {}. "
"Netron's setup might be declaring files that are missing in their repository."
.format(public / package_file.name, package_file))
bundle_destination = public / 'netron_bundle.js'
if rebuild_needed(static_scripts, bundle_destination):
import tempfile
with tempfile.NamedTemporaryFile() as f:
f.write(bundle_scripts(static_scripts))
minify(f.name, bundle_destination)
else:
print('Bundle is already up to date')
if __name__ == '__main__':
main()

3
Tools/WinMLDashboard/images.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,3 @@
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'

Просмотреть файл

@ -0,0 +1,61 @@
{
"name": "WinMLDashboard",
"version": "1.0.0",
"description": "Dashboard for development in ONNX using Windows ML",
"author": "Microsoft Corporation",
"homepage": ".",
"main": "src/electronMain.js",
"private": true,
"dependencies": {
"ajv": "^6.5.2",
"electron-packager": "^12.1.2",
"electron-squirrel-startup": "^1.0.0",
"npm-font-open-sans": "^1.1.0",
"office-ui-fabric-react": "^6.40.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-markdown": "^3.4.1",
"react-redux": "^5.0.7",
"redux": "^4.0.0",
"yauzl": "^2.10.0"
},
"scripts": {
"start": "react-scripts-ts start",
"build": "react-scripts-ts build",
"test": "react-scripts-ts test --env=jsdom",
"eject": "react-scripts-ts eject",
"postinstall": "node scripts/postinstall.js && yarn copy-netron",
"copy-netron": "yarn --cwd deps/Netron && python deps/copy_netron.py",
"build-electron": "react-app-rewired build --scripts-version react-scripts-ts",
"electron-dev": "electron . http://localhost:3000",
"electron-prod": "yarn build-electron && electron .",
"packager": "yarn build-electron && electron-packager . WinMLDashboard --overwrite --win=x32 --out ./release --arch=x64 --app-version=1.0.0 --electron-version=2.0.6",
"installer": "yarn packager && node build.js"
},
"build": {
"appId": "com.microsoft.dashboard.winml",
"mac": {
"category": "public.app-category.developer-tools"
},
"win": {
"target": [
"msi"
]
}
},
"devDependencies": {
"@types/jest": "^23.3.0",
"@types/node": "^10.5.3",
"@types/prop-types": "^15.5.4",
"@types/react": "^16.4.7",
"@types/react-dom": "^16.0.6",
"@types/react-redux": "^6.0.5",
"@types/yauzl": "^2.9.0",
"babel-minify": "^0.4.3",
"electron": "^2.0.6",
"electron-winstaller": "^2.6.4",
"react-app-rewired": "^1.5.2",
"react-scripts-ts": "^2.16.0",
"typescript": "^2.9.2"
}
}

Двоичные данные
Tools/WinMLDashboard/public/favicon.ico Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.8 KiB

Просмотреть файл

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>WinML Dashboard</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Просмотреть файл

@ -0,0 +1,15 @@
{
"short_name": "WinML Dashboard",
"name": "Windows ML Dashboard",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

Просмотреть файл

@ -0,0 +1,13 @@
const fs = require('fs');
const path = require('path');
for (directory of ['node_modules/netron', 'node_modules/netron/src']) {
if (!fs.existsSync(directory))
fs.mkdirSync(directory);
}
for (source of ['view-render.css', 'view-sidebar.css', 'view.css']) {
destination = path.join('node_modules/netron/src', source);
if (!fs.existsSync(destination))
fs.linkSync(path.join('deps/Netron/src', source), path.join('node_modules/netron/src', source));
}

Просмотреть файл

@ -0,0 +1,22 @@
.App {
text-align: center;
height: 100vh;
display: flex;
flex-direction: column
}
.MainView {
flex: 1;
overflow: auto;
}
.HiddenTab {
visibility: hidden;
overflow: hidden;
height: 0px;
line-height: 0px;
}
.DisplayFlex {
display: flex;
}

Просмотреть файл

@ -0,0 +1,54 @@
import { Pivot, PivotItem } from 'office-ui-fabric-react/lib/Pivot';
import * as React from 'react';
import { initializeIcons } from './fonts';
import ConvertView from './view/convert/View';
import EditView from './view/edit/View';
import './App.css';
interface IComponentState {
tab: string,
}
const views = {
Edit: <EditView />,
// tslint:disable-next-line:object-literal-sort-keys
Convert: <ConvertView />,
};
class App extends React.Component<{}, IComponentState> {
constructor(props: {}) {
super(props);
initializeIcons();
this.state = {
tab: 'Edit',
};
}
public render() {
const pivotItems = Object.keys(views).map(x => <PivotItem headerText={x} key={x} />);
const mainView = Object.entries(views).map(([k, v]) =>
<div className={k === this.state.tab ? 'MainView' : 'HiddenTab'} key={k} >
{v}
</div>
)
return (
<div className='App'>
<Pivot onLinkClick={this.onLinkClick}>
{pivotItems}
</Pivot>
{mainView}
</div>
);
}
private onLinkClick = (item?: PivotItem, ev?: React.MouseEvent<HTMLElement>) => {
if (item) {
this.setState({ tab: item.props.headerText! });
}
}
}
export default App;

Просмотреть файл

@ -0,0 +1,27 @@
.Label {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
cursor: pointer;
text-align: left;
border: 1px lightgray;
border-style: solid none;
padding: 5px 5px 10px;
}
.Content {
padding: 5px;
transition: 200ms;
transition-timing-function: ease-out;
max-height: 2000px;
overflow: hidden;
}
pre {
display: inline;
margin: 5px;
font-size: 18px;
}

Просмотреть файл

@ -0,0 +1,43 @@
import { Label } from 'office-ui-fabric-react/lib/Label';
import * as React from 'react';
import './Collapsible.css';
interface IComponentProperties {
label: string,
}
interface IComponentState {
visible: boolean,
}
export default class Collapsible extends React.Component<IComponentProperties, IComponentState> {
constructor(props: IComponentProperties) {
super(props);
this.state = {
visible: true,
};
}
public render() {
const style: React.CSSProperties = {};
if (!this.state.visible) {
style.maxHeight = '0px';
style.visibility = 'hidden';
style.padding = '0px';
}
return (
<div>
<Label className='Label' onClick={this.toggle}><pre>{this.state.visible && '-' || '+'}</pre>{this.props.label}</Label>
<div className='Content' style={style}>
{this.props.children}
</div>
</div>
);
}
private toggle = () => {
this.setState(previousState => ({
visible: !previousState.visible,
}));
}
}

Просмотреть файл

@ -0,0 +1,20 @@
.KeyValueEditorButton {
width: 50%;
}
.KeyValueEditorButtonContainer {
width: 100%;
}
.KeyValueBox {
flex: 1;
}
.KeyValueSeparator {
padding: 8px 4px;
}
.RemoveIcon {
cursor: pointer;
padding: 10px 4px 0px 0px;
}

Просмотреть файл

@ -0,0 +1,213 @@
import * as Ajv from 'ajv';
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { ComboBox, IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import * as React from 'react';
import { connect } from 'react-redux';
import IState from '../datastore/state';
import './KeyValueEditor.css';
const ajv = new Ajv({ allErrors: true, verbose: true });
interface IComponentProperties {
actionCreator?: (keyValueObject: { [key: string]: string }) => {},
getState: (state: IState) => { [key: string]: string },
schema: any,
// Redux properties
keyValueObject?: { [key: string]: string },
updateKeyValueObject?: (keyValueObject: { [key: string]: string }) => void,
}
interface IComponentState {
caseInsensitiveSchema: any,
keyErrors: { [key: string]: string },
}
class KeyValueEditor extends React.Component<IComponentProperties, IComponentState> {
constructor(props: IComponentProperties) {
super(props);
this.state = {
caseInsensitiveSchema: this.toLowerCaseSchema(props.schema),
keyErrors: {},
};
}
public render() {
const properties = this.props.schema.properties || {};
const knownKeys = Object.keys(properties).filter(
// Don't suggesting adding keys that already exist
(x) => Object.keys(this.props.keyValueObject || {}).every((key) => !this.areSameKeys(key, x)));
const options = knownKeys.map((key: string) => ({ key, text: key }));
let rows: Array<React.ReactElement<HTMLDivElement>> = [];
if (this.props.keyValueObject) {
const [keyErrors, valueErrors] = this.validateSchema(this.state.caseInsensitiveSchema, this.props.keyValueObject);
const schemaEntries = Object.entries(properties);
rows = Object.keys(this.props.keyValueObject!).map((x: string) => {
const lowerCaseKey = x.toLowerCase();
const keyChangedCallback = (option?: IComboBoxOption, index?: number, value?: string) => {
const key = value || option!.text;
if (Object.keys(this.props.keyValueObject!).includes(key)) {
this.setState((prevState: IComponentState, props: IComponentProperties) => (
{
keyErrors: {
[lowerCaseKey]: prevState.keyErrors[lowerCaseKey] ? `${prevState.keyErrors[lowerCaseKey]} ` : 'duplicate key'
},
}
));
} else {
this.props.updateKeyValueObject!(this.copyRenameKey(this.props.keyValueObject, x, key));
}
};
const valueChangedCallback = (option?: IComboBoxOption, index?: number, value?: string) => {
const newValue = value || option!.text;
this.props.updateKeyValueObject!({ ...this.props.keyValueObject!, [x]: newValue });
};
const removeCallback = () => {
const newObject = { ...this.props.keyValueObject! };
delete newObject[x];
this.props.updateKeyValueObject!(newObject);
}
let knownValues = [];
const property = schemaEntries.find((prop) => this.areSameKeys(prop[0], x)) as [string, any];
if (property && property[1].enum) {
knownValues = property[1].enum.map((key: string) => ({ key, text: key }));
}
const keyErrorsState = this.state.keyErrors[lowerCaseKey];
return (
<div key={`${x}${keyErrorsState}`} className='DisplayFlex'>
{ this.props.actionCreator &&
<Icon className='RemoveIcon' iconName='Cancel' onClick={removeCallback} />
}
<ComboBox
className='KeyValueBox'
allowFreeform={true}
text={x}
errorMessage={keyErrorsState || keyErrors[lowerCaseKey]}
options={options}
disabled={!this.props.actionCreator}
onChanged={keyChangedCallback}
/>
<span className='KeyValueSeparator'>=</span>
<ComboBox
className='KeyValueBox'
allowFreeform={true}
text={this.props.keyValueObject![x]}
errorMessage={valueErrors[lowerCaseKey]}
options={knownValues}
disabled={!this.props.actionCreator}
onChanged={valueChangedCallback}
/>
</div>
);
});
}
const addButtonDisabled = !(this.props.keyValueObject && Object.keys(this.props.keyValueObject).every((x) => !!x));
const getSplitButtonClassNames = () => ({ splitButtonContainer: '.KeyValueEditorButtonContainer' });
return (
<div>
{rows}
{ this.props.actionCreator &&
<DefaultButton
className='KeyValueEditorButtonContainer'
iconProps={{ iconName: 'Add' }}
disabled={addButtonDisabled}
onClick={this.addProp}
split={!!knownKeys.length}
menuProps={knownKeys.length ? {
items: options,
onItemClick: this.addProp,
} : undefined}
// The Office UI includes a spam element in it when split = true, which is not of display type block
// and ignores the parent width. We do a hack to get it to 100% of the parent width.
getSplitButtonClassNames={getSplitButtonClassNames}
/>
}
</div>
);
}
private validateSchema = (schema: any, data: { [key: string]: string }) => {
const keyErrors = {};
const valueErrors = {};
if (!ajv.validate(schema, this.toLowerCaseObject(data))) {
ajv.errors!.forEach((error: Ajv.ErrorObject) => {
if (error.schemaPath.startsWith('#/properties/')) {
valueErrors[error.schemaPath.split('/', 3)[2]] = error.message!;
} else if (error.keyword === 'additionalProperties') {
const params = error.params as Ajv.AdditionalPropertiesParams;
keyErrors[params.additionalProperty] = 'unknown property';
}
});
}
return [keyErrors, valueErrors];
}
private areSameKeys = (a: string, b: string) => {
return a.toLowerCase() === b.toLowerCase();
}
private copyRenameKey = (obj: any, oldKey: string, newKey: string) => {
return Object.entries(obj).reduce((acc: any[], keyValue: [string, string]) => {
const [key, value] = keyValue;
acc[key === oldKey ? newKey : key] = value;
return acc;
}, {});
}
private renameKey = (obj: any, oldKey: string, newKey: string) => {
if (oldKey !== newKey) {
obj[newKey] = obj[oldKey];
delete obj[oldKey];
}
}
private toLowerCaseSchema = (schema: any) => {
// Hacky way of performing case insensitive validation. JSON schema has no case insensitive enum,
// so we convert the schema and object being validated to lowercase before validating.
// This code assumes the object schema has a "properties" property.
const lowerCaseSchema = JSON.parse(JSON.stringify(schema));
const properties = lowerCaseSchema.properties || {};
Object.keys(properties).forEach((key: string) => {
const propertyEnum = properties[key].enum;
if (propertyEnum) {
propertyEnum.forEach((x: string, i: number, arr: any[]) => arr[i] = x.toLowerCase());
}
this.renameKey(properties, key, key.toLowerCase());
});
return lowerCaseSchema;
}
private toLowerCaseObject = (obj: { [key: string]: string }) => {
return Object.entries(obj).reduce((acc: {}, keyValue: [string, any]) => {
const [key, value] = keyValue;
acc[key.toLowerCase()] = value.toString().toLowerCase();
return acc;
}, {});
}
private addProp = (event: any, item?: any) => {
const key = item && item.key || '';
if (this.props.keyValueObject) {
this.props.updateKeyValueObject!({ ...this.props.keyValueObject, [key]: ''});
}
};
}
const mapStateToProps = (state: IState, ownProps: IComponentProperties) => ({
keyValueObject: ownProps.getState(state),
});
const mapDispatchToProps = (dispatch: any, ownProps: IComponentProperties) => ({
updateKeyValueObject: (keyValueObject: { [key: string]: string }) => dispatch(ownProps.actionCreator!(keyValueObject)),
})
export default connect(mapStateToProps, mapDispatchToProps)(KeyValueEditor as any);

Просмотреть файл

@ -0,0 +1,21 @@
.Resizable {
position: relative;
border: 2px lightgray;
border-style: none solid;
overflow: auto;
transition: width 200ms;
width: 100%;
}
.container {
display: flex;
position: relative;
height: 100%;
width: 350px;
}
.drag {
position: relative;
width: 4px;
cursor: w-resize;
}

Просмотреть файл

@ -0,0 +1,94 @@
import * as React from 'react';
import './Resizable.css';
interface IComponentProperties {
isRightPanel?: boolean,
visible?: boolean,
}
interface IComponentStates {
isResizing: boolean,
resizeWidth: number,
};
export default class Resizable extends React.Component<IComponentProperties, IComponentStates> {
public static defaultProps: Partial<IComponentProperties> = {
isRightPanel: false,
visible: true,
};
constructor(props: any) {
super(props);
this.state = {
isResizing: false,
resizeWidth: 350,
}
}
public render() {
// TODO Have a better resizable box (that can be resized by clicking and
// draging the corners) instead of CSS' resize: horizontal property
// TODO Have a direction (horizontal/vertical) option
const style: React.CSSProperties = {};
if (!this.props.visible) {
style.width = style.minWidth = '0px';
style.pointerEvents = 'none';
}
else {
style.width = this.state.resizeWidth;
}
return (
<div className="container" style={style} >
<div className="drag"
onMouseDown= {this.whenMouseDown}
/>
<div className='Resizable' >
{this.props.children}
</div>
<div className="drag"
onMouseDown= {this.whenMouseDownRight}
/>
</div>
);
}
private whenMouseDown = () => {
this.setState({isResizing: true});
window.addEventListener('mousemove', this.whenMouseMove, false);
window.addEventListener('mouseup', this.whenMouseUp, false);
}
private whenMouseUp = () => {
this.setState({isResizing: false});
window.removeEventListener('mousemove', this.whenMouseMove, false);
window.removeEventListener('mouseup', this.whenMouseUp, false);
}
private whenMouseMove = (e: MouseEvent) => {
if(!(this.state.isResizing && this.props.isRightPanel)){
return;
}
else {
this.setState({resizeWidth: window.innerWidth - e.clientX});
}
}
private whenMouseDownRight = () => {
this.setState({isResizing: true});
window.addEventListener('mousemove', this.whenMouseMoveRight, false);
window.addEventListener('mouseup', this.whenMouseUpRight, false);
}
private whenMouseUpRight = () => {
this.setState({isResizing: false});
window.removeEventListener('mousemove', this.whenMouseMoveRight, false);
window.removeEventListener('mouseup', this.whenMouseUpRight, false);
}
private whenMouseMoveRight = (e: MouseEvent) => {
if(!(this.state.isResizing && !this.props.isRightPanel)){
return;
}
else{
this.setState({resizeWidth: e.clientX});
}
}
}

Просмотреть файл

@ -0,0 +1,52 @@
import * as actions from './actions';
import { IMetadataProps, IProperties } from './state';
export const setFile = (file?: File) => ({
file,
type: actions.SET_FILE,
})
export const setInputs = (inputs?: { [key: string]: any }) => ({
inputs,
type: actions.SET_INPUTS,
})
export const setMetadataProps = (metadataProps: IMetadataProps) => ({
metadataProps,
type: actions.SET_METADATA_PROPS,
})
export const setModelInputs = (modelInputs?: string[]) => ({
modelInputs,
type: actions.SET_MODEL_INPUTS,
})
export const setModelOutputs = (modelOutputs?: string[]) => ({
modelOutputs,
type: actions.SET_MODEL_OUTPUTS,
})
export const setNodes = (nodes?: { [key: string]: any }) => ({
nodes,
type: actions.SET_NODES,
})
export const setOutputs = (outputs?: { [key: string]: any }) => ({
outputs,
type: actions.SET_OUTPUTS,
})
export const setProperties = (properties: IProperties) => ({
properties,
type: actions.SET_PROPERTIES,
})
export const setSaveFileName = (saveFileName?: string) => ({
saveFileName,
type: actions.SET_SAVE_FILE_NAME,
})
export const setSelectedNode = (selectedNode?: string) => ({
selectedNode,
type: actions.SET_SELECTED_NODE,
})

Просмотреть файл

@ -0,0 +1,29 @@
import { IMetadataProps, IProperties } from './state';
export const SET_FILE = 'SET_FILE';
export const SET_INPUTS = 'SET_INPUTS';
export const SET_METADATA_PROPS = 'SET_METADATA_PROPS';
export const SET_MODEL_INPUTS = 'SET_MODEL_INPUTS';
export const SET_MODEL_OUTPUTS = 'SET_MODEL_OUTPUTS';
export const SET_NODES = 'SET_NODES';
export const SET_OUTPUTS = 'SET_OUTPUTS';
export const SET_PROPERTIES = 'SET_PROPERTIES';
export const SET_SAVE_FILE_NAME = 'SET_SAVE_FILE_NAME';
export const SET_SELECTED_NODE = 'SET_SELECTED_NODE';
export interface IAction {
file: File,
nodes: { [key: string]: any },
saveFileName: string,
type: string,
inputs: any[],
outputs: any[],
modelInputs: string[],
modelOutputs: string[],
metadataProps: IMetadataProps,
properties: IProperties,
selectedNode: string,
}

Просмотреть файл

@ -0,0 +1,17 @@
import { IAction, SET_INPUTS, SET_METADATA_PROPS, SET_OUTPUTS } from '../actions';
import { ModelProtoSingleton } from './modelProto';
export const protoMiddleware = (store: any) => (next: (action: IAction) => any) => (action: IAction) => {
switch (action.type) {
case SET_INPUTS:
ModelProtoSingleton.setInputs(action.inputs);
break;
case SET_METADATA_PROPS:
ModelProtoSingleton.setMetadata(action.metadataProps);
break;
case SET_OUTPUTS:
ModelProtoSingleton.setOutputs(action.outputs);
break;
}
return next(action);
}

Просмотреть файл

@ -0,0 +1,39 @@
import { IMetadataProps } from '../state';
import { Proto } from './proto';
class ModelProto extends Proto {
public setInputs(inputs: { [key: string]: any }) {
if (!Proto.getOnnx() || !this.proto) {
return;
}
this.proto.graph.input = Object.keys(inputs).map((name: string) => ({ name, ...inputs[name] }));
}
public setMetadata(metadata: IMetadataProps) {
if (!Proto.getOnnx() || !this.proto) {
return;
}
this.proto.metadataProps = Object.keys(metadata).map((x: string) => {
const entry = new Proto.types.StringStringEntryProto();
entry.key = x;
entry.value = metadata[x];
return entry;
});
}
public setOutputs(outputs: { [key: string]: any }) {
if (!Proto.getOnnx() || !this.proto) {
return;
}
this.proto.graph.output = Object.keys(outputs).map((name: string) => ({ name, ...outputs[name] }));
}
public serialize() {
Proto.getOnnx();
const clone = Proto.types.ModelProto.fromObject(this.proto);
const writer = Proto.types.ModelProto.encode(clone);
return writer.finish();
}
}
export const ModelProtoSingleton = new ModelProto();

Просмотреть файл

@ -0,0 +1,27 @@
const browserGlobal = window as any;
export class Proto {
public static types: {
ModelProto: any,
StringStringEntryProto: any,
TypeProto: any,
ValueInfoProto: any,
};
public static getOnnx() {
if (!this.onnx) {
if (!browserGlobal.protobuf) {
throw new Error("Can't find protobuf module");
}
if (!browserGlobal.protobuf.roots.onnx) {
return;
}
this.onnx = browserGlobal.protobuf.roots.onnx.onnx;
this.types = this.onnx;
}
return this.onnx;
}
private static onnx: any;
public proto: any;
}

Просмотреть файл

@ -0,0 +1,30 @@
import * as actions from './actions';
import IState from './state';
export function rootReducer(state: IState, action: actions.IAction) {
state = state || {};
switch (action.type) {
case actions.SET_FILE:
return { ...state, file: action.file };
case actions.SET_INPUTS:
return { ...state, inputs: action.inputs };
case actions.SET_OUTPUTS:
return { ...state, outputs: action.outputs };
case actions.SET_METADATA_PROPS:
return { ...state, metadataProps: action.metadataProps };
case actions.SET_MODEL_INPUTS:
return { ...state, modelInputs: action.modelInputs };
case actions.SET_MODEL_OUTPUTS:
return { ...state, modelOutputs: action.modelOutputs };
case actions.SET_NODES:
return { ...state, nodes: action.nodes };
case actions.SET_PROPERTIES:
return { ...state, properties: action.properties };
case actions.SET_SAVE_FILE_NAME:
return { ...state, saveFileName: action.saveFileName };
case actions.SET_SELECTED_NODE:
return { ...state, selectedNode: action.selectedNode };
default:
return state;
}
}

Просмотреть файл

@ -0,0 +1,21 @@
export interface IMetadataProps {
[key: string]: string
}
export interface IProperties {
[key: string]: string
}
export default interface IState {
file: File,
saveFileName: string,
inputs: { [key: string]: any },
metadataProps: IMetadataProps,
modelInputs: string[],
modelOutputs: string[],
nodes: { [key: string]: any },
outputs: { [key: string]: any },
properties: IProperties,
selectedNode: string,
}

Просмотреть файл

@ -0,0 +1,9 @@
import { applyMiddleware, createStore } from 'redux'
import { protoMiddleware } from './proto/middleware';
import { rootReducer } from './reducers';
const browserGlobal = window as any;
const store = createStore(rootReducer, browserGlobal.__REDUX_DEVTOOLS_EXTENSION__ && browserGlobal.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(protoMiddleware));
export default store;

Просмотреть файл

@ -0,0 +1,92 @@
const {app, protocol, BrowserWindow} = require('electron');
const fs = require('fs');
const path = require('path');
const url = require('url');
let mainWindow;
if(require('electron-squirrel-startup')) {
return;
}
function interceptFileProtocol() {
// Intercept the file protocol so that references to folders return its index.html file
const fileProtocol = 'file';
const cwd = process.cwd();
protocol.interceptFileProtocol(fileProtocol, (request, callback) => {
const fileUrl = new url.URL(request.url);
const hostname = decodeURI(fileUrl.hostname);
const filePath = decodeURI(fileUrl.pathname);
let resolvedPath = path.normalize(filePath);
if (resolvedPath[0] === '\\') {
// Remove URL host to pathname separator
resolvedPath = resolvedPath.substr(1);
}
if (hostname) {
resolvedPath = path.join(hostname, resolvedPath);
if (process.platform === 'win32') { // File is on a share
resolvedPath = `\\\\${resolvedPath}`;
}
}
resolvedPath = path.relative(cwd, resolvedPath);
try {
if (fs.statSync(resolvedPath).isDirectory) {
let index = path.posix.join(resolvedPath, 'index.html');
if (fs.existsSync(index)) {
resolvedPath = index;
}
}
} catch(_) {
// Use path as is if it can't be accessed
}
callback({
path: resolvedPath,
});
});
}
function createWindow() {
interceptFileProtocol();
mainWindow = new BrowserWindow({
height: 600,
width: 800,
});
global.mainWindow = mainWindow;
let pageUrl;
for (const arg of process.argv.slice(1)) {
if (arg.includes('://')) {
pageUrl = arg;
break;
}
}
if (pageUrl === undefined) {
pageUrl = url.format({
pathname: path.join(__dirname, '../build/'),
protocol: 'file',
});
}
mainWindow.loadURL(pageUrl);
if (process.argv.includes('--dev-tools')) {
mainWindow.webContents.openDevTools();
}
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});

Просмотреть файл

@ -0,0 +1,10 @@
**Files in this folder and public/fabric-icons.woff are auto generated from https://uifabricicons.azurewebsites.net/**
If you want to add icons, regenerate the font file in the above link.
The icons currently in use are:
* E70D
* E710
* E711
* EA39

Просмотреть файл

@ -0,0 +1,36 @@
// Your use of the content in the files referenced here is subject to the terms of the license at https://aka.ms/fabric-assets-license
// tslint:disable:max-line-length
import {
IIconOptions,
IIconSubset,
registerIcons
} from '@uifabric/styling';
export function initializeIcons(
baseUrl: string = '',
options?: IIconOptions
): void {
const subset: IIconSubset = {
fontFace: {
fontFamily: `"FabricMDL2Icons"`,
src: `url('${baseUrl}fabric-icons.woff') format('woff')`
},
icons: {
'Add': '\uE710',
'Cancel': '\uE711',
'ChevronDown': '\uE70D',
'ErrorBadge': '\uEA39',
},
style: {
MozOsxFontSmoothing: 'grayscale',
WebkitFontSmoothing: 'antialiased',
fontStyle: 'normal',
fontWeight: 'normal',
speak: 'none'
},
};
registerIcons(subset, options);
}

Просмотреть файл

@ -0,0 +1,9 @@
import { registerIconAlias } from '@uifabric/styling';
export const registerIconAliases = () => {
registerIconAlias('trash', 'delete');
registerIconAlias('onedrive', 'onedrivelogo');
registerIconAlias('alertsolid12', 'eventdatemissed12');
}
export default registerIconAliases;

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,18 @@
import { initializeIcons as i } from './fabric-icons';
import { IIconOptions } from '@uifabric/styling';
import { registerIconAliases } from './iconAliases';
const DEFAULT_BASE_URL = '';
export function initializeIcons(
baseUrl: string = DEFAULT_BASE_URL,
options?: IIconOptions
): void {
[i].forEach(
(initialize: (url: string, options?: IIconOptions) => void) => initialize(baseUrl, options)
);
registerIconAliases();
}
export { IconNames } from './iconNames';

Просмотреть файл

@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

Просмотреть файл

@ -0,0 +1,18 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import App from './App';
import store from './datastore/store';
import './index.css';
import { registerKeyboardShurtcuts } from './native/menu';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root') as HTMLElement
);
registerKeyboardShurtcuts();
registerServiceWorker();

Просмотреть файл

@ -0,0 +1,17 @@
# Python
Python is used to run the ONNX converters.
## How is it installed
The dashboard needs a Python distribution to install the converter and its dependencies. An isolated Python environment is used to avoid interactions with the system-wide Python distribution.
There are two possibilities to create a Python environment. On the first run of the converter, it will ask the user to choose one:
* Use an existing Python installation. The dashboard looks for executables `python3`, `py` and `python` and, for any of them with `venv` support, lists it as a candidate for installation.
* If this option is chosen, a virtual environment is created using `venv`. All packages are installed in the environment and system-wide packages are not acessible.
* Download an embedded version of Python (Windows only)
* If this option is chosen, the embeddable zip file release of Python is downloaded from the [Windows releases page](https://www.python.org/downloads/windows/). This version is a zip file with Python and its standard libraries. It's only available for Windows (due to its lack of a standardized package manager to install Python from).
* The file is unzipped. The embedded version comes without `pip`, so we install `pip` into it manually. We also change the Python library search path to include pip packages, so that packages can be imported from scripts run with this interpreter.
After either of the above choices, `pip install -r requirements.txt` is run to install the packages specified in `requirements.txt`.

Просмотреть файл

@ -0,0 +1,24 @@
import * as fs from 'fs';
import * as path from 'path';
const nodeProcess = process;
const env = nodeProcess.env;
export const appData = env.APPDATA ||
path.join(env.HOME || '/tmp', process.platform === 'darwin' ? 'Library/Preferences' : '.local/share');
export function mkdir(...directory: string[]) {
const joined = path.join(...directory);
if (fs.exists && !fs.existsSync(joined)) { // skips if running in the web
fs.mkdirSync(joined);
}
return joined;
}
export function packagedFile(...filePath: string[]) {
// Return a path to a file packaged in the application
return path.join(__filename, ...filePath);
}
// Point to the root if running in the web
export const winmlDataFolder = fs.exists ? mkdir(appData, 'WinMLDashboard') : '/';

Просмотреть файл

@ -0,0 +1,9 @@
import { fileFilterToAccept } from "./dialog";
it('converts Array<FileFilter> to HTML accept field', () => {
expect(fileFilterToAccept([
{ name: 'CoreML model', extensions: [ 'mlmodel' ] },
{ name: 'Keras model', extensions: [ 'keras', 'h5' ] },
{ name: 'ONNX model', extensions: [ 'onnx' ] },
])).toBe('.mlmodel,.keras,.h5,.onnx');
});

Просмотреть файл

@ -0,0 +1,92 @@
/**
* Provide dialogs, abstracting whether in web or Electron.
*/
import * as fs from 'fs';
import * as path from 'path';
import { getElectron } from './util';
export function fileFilterToAccept(filters: Electron.FileFilter[]) {
/**
* Convert Electron FileFilter[] to HTML accept field.
*/
return filters.map(x => x.extensions.map(extension => `.${extension}`).join(',')).join(',');
}
export function showWebOpenDialog(accept: string) {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', accept);
return new Promise<FileList>(resolve => {
input.addEventListener('change', () => resolve(input.files || undefined));
input.click();
});
}
export function showNativeOpenDialog(options: Electron.OpenDialogOptions) {
const { dialog } = getElectron().remote;
return new Promise<string[]>(resolve => dialog.showOpenDialog(options, resolve));
}
export function populateFileFields(fileLikeObject: any, filePath: string) {
fileLikeObject.lastModified = 0;
fileLikeObject.name = path.basename(filePath);
fileLikeObject.path = path.resolve(filePath);
return fileLikeObject as File;
}
export function fileFromPath(filePath: string) {
// Make the native dialogs return an object that acts like the File object returned in HTML forms.
// This way, all functions that use dialogs can have a single code path instead of one for the web
// and one for Electron.
// "new File(fs.readFileSync(x), path.basename(x))" followed by "file.path = x" doesn't work because
// the path field is read-only. Instead, we create a Blob and manually add the remaining fields (per
// https://www.w3.org/TR/FileAPI/#dfn-file and an extra "path" field, containing the real path).
const file = new Blob([fs.readFileSync(filePath)]);
return populateFileFields(file, filePath);
}
export async function showOpenDialog(filters: Electron.FileFilter[]) {
if (getElectron()) {
const paths = await showNativeOpenDialog({ filters });
return paths.map(fileFromPath);
}
return showWebOpenDialog(fileFilterToAccept(filters));
}
export function showWebSaveDialog(data: Uint8Array, filename: string) {
// This function could support more flags (e.g. multiple and directory selections):
// https://stackoverflow.com/questions/12942436/how-to-get-folder-directory-from-html-input-type-file-or-any-other-way
const blob = new Blob([data], {type: 'application/octet-stream'});
const anchor = document.createElement('a');
anchor.download = filename;
anchor.href = URL.createObjectURL(blob);
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
URL.revokeObjectURL(anchor.href);
}
export function showNativeSaveDialog(options: Electron.SaveDialogOptions) {
const { dialog } = getElectron().remote;
return new Promise<string>(resolve => dialog.showSaveDialog(options, resolve));
}
export async function save(data: Uint8Array, filename: string, filters: Electron.FileFilter[]) {
if (getElectron()) {
if (!path.isAbsolute(filename)) {
// If a suggested filename is given instead of a full path, show a save dialog
filename = await showNativeSaveDialog({ defaultPath: filename, filters });
if (!filename) {
return;
}
}
// Save contents
fs.writeFileSync(filename, new Buffer(data));
return filename;
}
showWebSaveDialog(data, filename);
return;
}

Просмотреть файл

@ -0,0 +1,130 @@
/**
* Provide native menus and shortcuts. In the web, installs shortcuts only.
*/
import { setFile, setSaveFileName } from "../datastore/actionCreators";
import { ModelProtoSingleton } from "../datastore/proto/modelProto";
import store from "../datastore/store";
import { save, showOpenDialog } from "./dialog";
import { isWeb } from "./util";
export function createMenu(electron: typeof Electron) {
const { Menu } = electron.remote;
const template = [
{
label: 'File',
submenu: [
{ label: 'Open', accelerator: 'Ctrl+O', click: onOpen },
{ label: 'Save', accelerator: 'Ctrl+S', click: onSave, enabled: false },
],
}, {
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'pasteandmatchstyle' },
{ role: 'delete' },
{ role: 'selectall '},
],
}, {
label: 'View',
submenu: [
{ role: 'toggledevtools' },
{ type: 'separator' },
{ role: 'resetzoom' },
{ role: 'zoomin' },
{ role: 'zoomout' },
{ type: 'separator' },
{ role: 'togglefullscreen '},
],
}, {
role: 'help',
submenu: [
{
label: 'Third Party Notice',
click() {
const path = require('path');
require('electron').shell.openItem(path.join('file://', process.cwd(), 'ThirdPartyNotice.txt'));
},
}
],
}
];
if (process.platform === 'darwin') {
template.unshift({
label: 'WinML Dashboard',
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services', submenu: [] },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit '},
],
} as any);
}
const menu = Menu.buildFromTemplate(template as any);
store.subscribe(() => (menu.items[0] as any).submenu.items[1].enabled = !!store.getState().nodes);
Menu.setApplicationMenu(menu);
}
export function registerKeyboardShurtcuts() {
if (isWeb()) { // Electron's accelerators are used outside web builds
document.addEventListener('keydown', (event) => {
if (event.ctrlKey) {
switch (event.code) {
case 'KeyS':
onSave();
break;
case 'KeyO':
onOpen();
break;
default:
return;
}
event.preventDefault();
}
});
}
}
async function onSave() {
if (!ModelProtoSingleton.proto) {
return;
}
const suggestedPath = store.getState().saveFileName || 'model.onnx';
const selectedPath = await save(ModelProtoSingleton.serialize(), suggestedPath,
[{ name: 'ONNX model', extensions: [ 'onnx', 'prototxt' ] }]);
if (selectedPath && selectedPath !== suggestedPath) {
store.dispatch(setSaveFileName(suggestedPath));
}
}
async function onOpen() {
const files = await showOpenDialog([
{ name: 'ONNX Model', extensions: [ 'onnx', 'pb' ] },
{ name: 'Keras Model', extensions: [ 'h5', 'json', 'keras' ] },
{ name: 'CoreML Model', extensions: [ 'mlmodel' ] },
{ name: 'Caffe Model', extensions: [ 'caffemodel' ] },
{ name: 'Caffe2 Model', extensions: [ 'pb' ] },
{ name: 'MXNet Model', extensions: [ 'model', 'json' ] },
{ name: 'TensorFlow Graph', extensions: [ 'pb', 'meta' ] },
{ name: 'TensorFlow Saved Model', extensions: [ 'pb' ] },
{ name: 'TensorFlow Lite Model', extensions: [ 'tflite' ] }
]);
if (files) {
store.dispatch(setFile(files[0]));
store.dispatch(setSaveFileName(files[0].path || files[0].name));
}
}

Просмотреть файл

@ -0,0 +1,176 @@
import { execFile, ExecFileOptions, execFileSync } from 'child_process';
import * as fs from 'fs';
import * as https from 'https';
import * as os from 'os';
import * as path from 'path';
import * as yauzl from 'yauzl';
import { mkdir, winmlDataFolder } from '../native/appData';
const localPython = mkdir(winmlDataFolder, 'python');
const embeddedPythonBinary = path.join(localPython, 'python.exe');
function filterPythonBinaries(binaries: string[]) {
return [...new Set(binaries.reduce((acc, x) => {
if (x === embeddedPythonBinary && fs.existsSync(x)) { // Install directly (without a venv) in embedded Python installations
acc.push(x);
} else {
try {
const binary = execFileSync(x, ['-c', 'import venv; import sys; print(sys.executable)'], { encoding: 'utf8' });
acc.push(binary.trim());
} catch(_) {
// Ignore binary if unavailable or no venv support
}
}
return acc;
}, [] as string[]))];
}
export function getPythonBinaries() {
const binaries = filterPythonBinaries(['python3', 'py', 'python']) as Array<string | null>;
if (process.platform === 'win32') {
binaries.push(null); // use null to represent local, embedded version
}
return binaries;
}
async function unzip(buffer: Buffer, directory: string) {
return new Promise((resolve, reject) => {
yauzl.fromBuffer(buffer, (err, zipfile) => {
if (err) {
return reject(err);
}
zipfile!.once('end', resolve);
zipfile!.once('error', reject);
zipfile!.on('entry', entry => {
const destination = path.join(directory, entry.fileName);
if (entry.fileName.endsWith('/')) {
if (fs.existsSync(destination)) {
return;
}
try {
return fs.mkdirSync(destination);
} catch (e) {
return reject(e);
}
}
zipfile!.openReadStream(entry, (error, readStream) => {
if (error) {
return reject(error);
}
readStream!.once('error', reject);
readStream!.pipe(fs.createWriteStream(destination));
});
});
});
});
}
async function downloadBinaryFile(url: string) {
return new Promise((resolve, reject) => {
const data: Buffer[] = [];
https.get(url, response => {
response
.on('data', (x: string) => data.push(Buffer.from(x, 'binary')))
.on('end', async () => {
resolve(Buffer.concat(data));
})
.on('error', reject);
});
});
}
// Change the PTH to discover modules from Lib/site-packages, so that pip modules can be found
const pythonPth = `Lib/site-packages
python36.zip
.
# Uncomment to run site.main() automatically
import site`;
export async function downloadPython() {
if (process.platform !== 'win32') {
throw Error('Unsupported platform');
}
return new Promise(async (resolve, reject) => {
try {
const data = await downloadBinaryFile('https://www.python.org/ftp/python/3.6.6/python-3.6.6-embed-amd64.zip') as Buffer;
await unzip(data, localPython);
fs.writeFileSync(path.join(localPython, 'python36._pth'), pythonPth);
} catch (err) {
reject(err);
}
resolve();
});
}
export async function downloadPip(listener?: IOutputListener) {
// Python embedded distribution for Windows doesn't have pip
return new Promise(async (resolve, reject) => {
const installer = path.join(localPython, 'get-pip.py');
try {
const data = await downloadBinaryFile('https://bootstrap.pypa.io/get-pip.py') as Buffer;
fs.writeFileSync(installer, data);
await python([installer], {}, listener);
} catch (err) {
return reject(err);
}
resolve();
});
}
export function getLocalPython() { // Get the local (embedded or venv) Python
return filterPythonBinaries([embeddedPythonBinary, path.join(localPython, 'Scripts/python'), path.join(localPython, 'bin/python')])[0];
}
interface IOutputListener {
stdout: (output: string) => void,
stderr: (output: string) => void,
}
async function execFilePromise(file: string, args: string[], options?: ExecFileOptions, listener?: IOutputListener) {
const run = async () => new Promise((resolve, reject) => {
const childProcess = execFile(file, args, {...options});
if (listener) {
childProcess.stdout.on('data', listener.stdout);
childProcess.stderr.on('data', listener.stderr);
}
childProcess.on('exit', (code, signal) => {
if (code !== 0) {
return reject(Error(`Child process ${file} ${code !== null ? `exited with code ${code}` : `killed by signal ${signal}`}`));
}
resolve();
});
});
for (let i = 0; i < 10; i++) {
try {
return await run();
} catch (err) {
if (err.code !== 'ENOENT') { // ignoring possible spurious ENOENT in child process invocation
throw err;
}
}
}
return run();
}
export async function python(command: string[], options?: ExecFileOptions, listener?: IOutputListener) {
const binary = getLocalPython();
if (!binary) {
throw Error('Failed to find local Python');
}
return execFilePromise(binary, command, options, listener);
}
export async function pip(command: string[], listener?: IOutputListener) {
let options;
if (getLocalPython() === embeddedPythonBinary) {
options = { cwd: os.tmpdir() }; // without this workaround, pip downloads packages to whatever the current working directory is
}
return python(['-m', 'pip', ...command], options, listener);
}
export async function installVenv(targetPython: string, listener?: IOutputListener) {
await execFilePromise(targetPython, ['-m', 'venv', localPython], {}, listener);
await pip(['install', '-U', 'pip'], listener);
}

Просмотреть файл

@ -0,0 +1,19 @@
import * as fs from 'fs';
import { createMenu } from './menu';
export function isWeb() {
return !fs.exists || typeof it === 'function';
}
let electron: typeof Electron;
if (!isWeb()) {
import('electron')
.then(mod => { electron = mod; createMenu(electron); })
// tslint:disable-next-line:no-console
.catch(console.error);
}
export function getElectron() {
return electron;
}

Просмотреть файл

@ -0,0 +1,122 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
// tslint:disable-next-line:no-console
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://goo.gl/SC7cgQ'
);
});
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
// tslint:disable-next-line:no-console
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
// tslint:disable-next-line:no-console
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
// tslint:disable-next-line:no-console
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
// tslint:disable-next-line:no-console
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

Просмотреть файл

@ -0,0 +1,54 @@
export default {
// https://github.com/onnx/onnx/blob/master/docs/MetadataProps.md
type: 'object',
additionalProperties: false,
properties: {
'Image.BitmapPixelFormat': {
enum: [
'Gray8',
'Rgb8',
'Bgr8',
'Rgba8',
'Bgra8',
],
},
'Image.ColorSpaceGamma': {
enum: [
'Linear',
'SRGB',
],
},
'Image.NominalPixelRange': {
enum: [
'NominalRange_0_255',
'Normalized_0_1',
'Normalized_1_1',
'NominalRange_16_235',
],
},
author: {
type: 'string',
},
company: {
type: 'string',
},
converted_from: {
type: 'string',
},
licence: {
type: 'string',
},
licence_url: {
format: 'uri',
type: 'string',
},
model_author: {
type: 'string',
},
model_license: {
type: 'string',
},
},
};

Просмотреть файл

@ -0,0 +1,32 @@
.ConvertView {
margin: 0px 10px;
text-align: initial;
height: 100%;
display: flex;
flex-direction: column;
}
.ModelConvertBrowser {
width: 100%;
}
.ms-TextField, .ConvertViewControls {
flex: 1;
}
.ConvertViewControls, .ConverterViewConsole {
overflow: auto;
}
.ConverterViewConsole {
display: block;
max-height: 400px;
}
#ConverterModelInputBrowse {
margin: 24px 5px 0px;
}
#ConvertButton {
margin: 5px 0px;
}

Просмотреть файл

@ -0,0 +1,205 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { Spinner } from 'office-ui-fabric-react/lib/Spinner';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import Collapsible from '../../components/Collapsible';
import { setFile, setSaveFileName } from '../../datastore/actionCreators';
import IState from '../../datastore/state';
import { packagedFile } from '../../native/appData';
import { fileFromPath, showNativeOpenDialog, showNativeSaveDialog } from '../../native/dialog';
import { downloadPip, downloadPython, getLocalPython, getPythonBinaries, installVenv, pip, python } from '../../native/python';
import { isWeb } from '../../native/util';
import './View.css';
enum Step {
Idle,
Downloading,
GetPip,
CreatingVenv,
InstallingRequirements,
Converting,
}
interface IComponentProperties {
// Redux properties
file: File,
setFile: typeof setFile,
setSaveFileName: typeof setSaveFileName,
}
interface IComponentState {
console: string,
currentStep: Step,
error?: Error | string,
source?: string,
}
class ConvertView extends React.Component<IComponentProperties, IComponentState> {
private localPython?: string;
constructor(props: IComponentProperties) {
super(props);
const error = isWeb() ? "The converter can't be run in the web interface" : undefined;
this.state = { console: '', error, currentStep: Step.Idle };
}
public render() {
const collabsibleRef: React.RefObject<Collapsible> = React.createRef();
return (
<div className='ConvertView'>
<div className='ConvertViewControls'>
{this.getView()}
</div>
{ this.state.console &&
<Collapsible ref={collabsibleRef} label='Console output'>
<pre className='ConverterViewConsole'>
{this.state.console}
</pre>
</Collapsible>
}
</div>
)
}
private getView() {
const { error } = this.state;
if (error) {
const message = typeof error === 'string' ? error : (`${error.stack ? `${error.stack}: ` : ''}${error.message}`);
return <MessageBar messageBarType={MessageBarType.error}>{message}</MessageBar>
}
switch (this.state.currentStep) {
case Step.Downloading:
return <Spinner label="Downloading Python..." />;
case Step.GetPip:
return <Spinner label="Getting pip in embedded Python..." />;
case Step.CreatingVenv:
return <Spinner label="Creating virtual environment..." />;
case Step.InstallingRequirements:
return <Spinner label="Downloading and installing requirements..." />;
case Step.Converting:
return <Spinner label="Converting..." />;
}
this.localPython = this.localPython || getLocalPython();
if (!this.localPython) {
return this.pythonChooser();
}
return this.converterView();
}
private printMessage = (message: string) => {
this.setState((prevState) => ({
...prevState,
console: prevState.console.concat(message),
}))
}
private printError = (error: string | Error) => {
this.setState({ currentStep: Step.Idle, error });
}
// tslint:disable-next-line:member-ordering
private outputListener = {
stderr: this.printMessage,
stdout: this.printMessage,
};
private pythonChooser = () => {
const binaries = getPythonBinaries();
const options = binaries.map((key) => key ? { key, text: key } : { key: '__download', text: 'Download a new Python binary to be used exclusively by the WinML Dashboard' });
const onChange = async (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption) => {
try {
if (option.key === '__download') {
this.setState({ currentStep: Step.Downloading });
await downloadPython();
this.setState({ currentStep: Step.GetPip });
await downloadPip(this.outputListener);
} else {
this.setState({ currentStep: Step.CreatingVenv });
await installVenv(option.key, this.outputListener);
}
this.setState({ currentStep: Step.InstallingRequirements });
await pip(['install', '-r', packagedFile('requirements.txt')], this.outputListener);
this.setState({ currentStep: Step.Idle });
} catch (error) {
this.printError(error);
}
}
// TODO Options to reinstall environment or update dependencies
return (
<ChoiceGroup
options={options}
label={binaries[0] ? 'Suitable Python versions were found in the system. Pick one to be used by the converter.' : 'No suitable Python versions were found in the system.'}
onChange={onChange}
/>
);
}
private converterView = () => {
return (
<div>
<div className='DisplayFlex ModelConvertBrowser'>
<TextField placeholder='Path' value={this.state.source || this.props.file && this.props.file.path} label='Model to convert' onChanged={this.setSource} />
<DefaultButton id='ConverterModelInputBrowse' text='Browse' onClick={this.browseSource}/>
</div>
<DefaultButton id='ConvertButton' text='Convert' disabled={!this.state.source} onClick={this.convert}/>
</div>
);
}
private setSource = (source?: string) => {
this.setState({ source })
}
private browseSource = () => {
const openDialogOptions = {
filters: [
{ name: 'CoreML model', extensions: [ 'mlmodel' ] },
{ name: 'Keras model', extensions: [ 'keras', 'h5' ] },
{ name: 'ONNX model', extensions: [ 'onnx' ] },
],
properties: Array<'openFile'>('openFile'),
};
showNativeOpenDialog(openDialogOptions)
.then((filePaths) => {
if (filePaths) {
this.setSource(filePaths[0]);
}
});
}
private convert = async () => {
const source = this.state.source!;
const destination = await showNativeSaveDialog({ filters: [{ name: 'ONNX model', extensions: ['onnx'] }, { name: 'ONNX text protobuf', extensions: ['prototxt'] }] });
if (!destination) {
return;
}
this.setState({ currentStep: Step.Converting });
try {
await python([packagedFile('convert.py'), source, destination], {}, this.outputListener);
} catch (e) {
this.printError(e);
}
this.setState({ currentStep: Step.Idle, source: undefined });
// TODO Show dialog (https://developer.microsoft.com/en-us/fabric#/components/dialog) asking whether we should open the converted model
this.props.setFile(fileFromPath(destination));
this.props.setSaveFileName(destination);
}
}
const mapStateToProps = (state: IState) => ({
file: state.file,
});
const mapDispatchToProps = {
setFile,
setSaveFileName,
}
export default connect(mapStateToProps, mapDispatchToProps)(ConvertView);

Просмотреть файл

@ -0,0 +1,241 @@
import { ComboBox, IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox';
import { ExpandingCardMode, HoverCard, IExpandingCardProps } from 'office-ui-fabric-react/lib/HoverCard';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import * as React from 'react';
import { connect } from 'react-redux';
import Collapsible from '../../components/Collapsible';
import Resizable from '../../components/Resizable';
import { setInputs, setOutputs } from '../../datastore/actionCreators';
import { Proto } from '../../datastore/proto/proto';
import IState from '../../datastore/state';
import './Panel.css';
interface IComponentProperties {
// Redux properties
inputs: { [key: string]: any },
modelInputs: string[];
modelOutputs: string[];
nodes: { [key: string]: any },
outputs: { [key: string]: any },
selectedNode: string,
setInputs: typeof setInputs,
setOutputs: typeof setOutputs,
}
const denotationOptions = ['', 'IMAGE', 'AUDIO', 'TEXT', 'TENSOR'].map((key: string) => ({ key, text: key }));
const dimensionDenotationOptions = [
'DATA_BATCH',
'DATA_CHANNEL',
'DATA_TIME',
'DATA_FEATURE',
'FILTER_IN_CHANNEL',
'FILTER_OUT_CHANNEL',
'FILTER_SPATIAL',
].map((key) => ({ key, text: key }));
const tensorProtoDataType = [
'UNDEFINED',
'float',
'uint8',
'int8',
'uint16',
'int16',
'int32',
'int64',
'string',
'bool',
'float16',
'double',
'uint32',
'uint64',
'complex64',
'complex128',
]
const getFullType = (typeProto: any): string => {
if (typeProto.tensorType) {
return `Tensor<${tensorProtoDataType[typeProto.tensorType.elemType]}>`;
} else if (typeProto.sequenceType) {
return `List<${getFullType(typeProto.sequenceType.elemType)}>`;
} else if (typeProto.mapType) {
return `Map<${tensorProtoDataType[typeProto.mapType.keyType]}, ${getFullType(typeProto.mapType.valueType)}>`;
}
return 'unknown';
}
class LeftPanel extends React.Component<IComponentProperties, {}> {
public render() {
return (
<div className="Unselectable">
<Resizable visible={this.props.selectedNode !== undefined}>
{this.props.selectedNode !== undefined && this.getContent()}
</Resizable>
</div>
);
}
private getContent() {
let name = 'Model Properties';
const modelPropertiesSelected = this.props.selectedNode === name;
let input: any[];
let output: any[];
if (modelPropertiesSelected) {
input = this.props.modelInputs;
output = this.props.modelOutputs;
} else {
const node = this.props.nodes[this.props.selectedNode];
({ input, output, name } = node);
name = `Node: ${name ? `${name} (${node.opType})` : node.opType}`;
}
const inputsForm = this.buildConnectionList(input);
const outputsForm = this.buildConnectionList(output);
return (
<div>
<Label className='PanelName'>{name}</Label>
<div className='Panel'>
<Collapsible label='Inputs'>
{inputsForm}
</Collapsible>
<Collapsible label='Outputs'>
{outputsForm}
</Collapsible>
</div>
</div>
);
}
private buildConnectionList = (connections: any[]) => {
return connections.map((x: any) => {
const valueInfoProto = this.props.inputs[x] || this.props.outputs[x];
if (!valueInfoProto) {
return (
<div key={x}>
<Label className='TensorName' disabled={true}>{`${x} (type: internal connection)`}</Label>
</div>
);
}
let tensorName = (
<div className={valueInfoProto.docString ? 'TensorDocumentationHover' : 'TensorName'}>
<b>{x}</b><span>{` (type: ${getFullType(valueInfoProto.type)})`}</span>
</div>
);
if (valueInfoProto.docString) {
const expandingCardProps: IExpandingCardProps = {
compactCardHeight: 100,
mode: ExpandingCardMode.compact,
onRenderCompactCard: (item: any): JSX.Element => (
<p className='DocString'>{valueInfoProto.docString}</p>
),
};
tensorName = (
<HoverCard expandingCardProps={expandingCardProps} instantOpenOnClick={true}>
{tensorName}
</HoverCard>
);
}
const isModelInput = this.props.modelInputs.includes(x)
const isModelOutput = this.props.modelOutputs.includes(x);
const disabled = !isModelInput && !isModelOutput;
const valueInfoProtoCopy = () => Proto.types.ValueInfoProto.fromObject(Proto.types.ValueInfoProto.toObject(valueInfoProto));
const tensorDenotationChanged = (option?: IComboBoxOption, index?: number, value?: string) => {
const nextValueInfoProto = valueInfoProtoCopy();
nextValueInfoProto.type.denotation = value || option!.text;
this.props[isModelInput ? 'setInputs' : 'setOutputs']({
...this.props[isModelInput ? 'inputs' : 'outputs'],
[x]: nextValueInfoProto,
});
};
let shapeEditor;
if (valueInfoProto.type.tensorType) {
const getValueInfoDimensions = (valueInfo: any) => valueInfo.type.tensorType.shape.dim;
shapeEditor = getValueInfoDimensions(valueInfoProto).map((dim: any, index: number) => {
const dimensionChanged = (value: string) => {
const nextValueInfoProto = valueInfoProtoCopy();
const dimension = getValueInfoDimensions(nextValueInfoProto)[index]
if (value) {
dimension.dimValue = +value;
delete dimension.dimParam;
} else {
dimension.dimParam = 'None'
delete dimension.dimValue;
}
this.props[isModelInput ? 'setInputs' : 'setOutputs']({
...this.props[isModelInput ? 'inputs' : 'outputs'],
[x]: nextValueInfoProto,
});
}
const dimensionDenotationChanged = (option?: IComboBoxOption) => {
const nextValueInfoProto = valueInfoProtoCopy();
getValueInfoDimensions(nextValueInfoProto)[index].denotation = option!.text;
this.props[isModelInput ? 'setInputs' : 'setOutputs']({
...this.props[isModelInput ? 'inputs' : 'outputs'],
[x]: nextValueInfoProto,
});
};
return (
<div key={index}>
<div className='DisplayFlex'>
<TextField
className='DenotationLabel'
label={`Dimension [${index}]`}
value={dim.dimParam === 'None' ? undefined : dim.dimValue}
inputMode='numeric'
type='number'
placeholder='None'
disabled={disabled}
onChanged={dimensionChanged} />
<ComboBox
label='Denotation'
defaultSelectedKey={dim.denotation}
className='DenotationComboBox'
options={dimensionDenotationOptions}
disabled={disabled}
onChanged={dimensionDenotationChanged} />
</div>
</div>
);
});
}
return (
<div key={x}>
{tensorName}
<div className='DenotationDiv'>
<ComboBox
className='DenotationComboBox'
label='Type denotation'
allowFreeform={true}
text={valueInfoProto.type.denotation}
options={denotationOptions}
disabled={!isModelInput && !isModelOutput}
onChanged={tensorDenotationChanged} />
</div>
{shapeEditor}
</div>
);
});
}
}
const mapStateToProps = (state: IState) => ({
inputs: state.inputs,
modelInputs: state.modelInputs,
modelOutputs: state.modelOutputs,
nodes: state.nodes,
outputs: state.outputs,
selectedNode: state.selectedNode,
})
const mapDispatchToProps = {
setInputs,
setOutputs,
};
export default connect(mapStateToProps, mapDispatchToProps)(LeftPanel);

Просмотреть файл

@ -0,0 +1,50 @@
.Unselectable {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
text-align: center;
}
.Panel {
text-align: left;
min-width: 290px;
background-color: #f8f8f8;
}
.PanelName {
text-align: center;
}
.TensorName, .TensorDocumentationHover {
display: block;
margin: 5px 5px;
}
.TensorDocumentationHover {
cursor: pointer;
}
.DenotationLabel {
margin: 2px !important;
width: 110px;
}
.DenotationComboBox {
margin: 2px;
flex: 1;
}
.DocString {
text-align: center;
vertical-align: middle;
height: 100%;
margin: 20px;
}
.FormatIsNotOnnx {
margin: 10px;
font-size: medium;
}

Просмотреть файл

@ -0,0 +1,64 @@
import { Label } from 'office-ui-fabric-react/lib/Label';
import * as React from 'react';
import { connect } from 'react-redux';
import Collapsible from '../../components/Collapsible';
import KeyValueEditor from '../../components/KeyValueEditor'
import Resizable from '../../components/Resizable';
import { setMetadataProps } from '../../datastore/actionCreators';
import IState, { IMetadataProps } from '../../datastore/state';
import MetadataSchema from '../../schema/Metadata';
import './Panel.css';
interface IComponentProperties {
// Redux properties
nodes: any,
metadataProps: IMetadataProps,
setMetadataProps: typeof setMetadataProps,
}
class RightPanel extends React.Component<IComponentProperties, {}> {
constructor(props: any) {
super(props);
}
public render() {
if (this.props.metadataProps !== undefined && !this.props.nodes) {
return (
// TODO Make it a button to navigate to the Convert tab
<Label className='FormatIsNotOnnx'>To support editing, convert the model to ONNX first.</Label>
);
}
return (
<div className="Unselectable">
<Resizable isRightPanel={true}>
<Label >Model</Label>
<div className='Panel'>
<Collapsible label='Properties'>
<KeyValueEditor getState={this.getPropertiesFromState} schema={{ type: 'object' }} />
</Collapsible>
<Collapsible label='Metadata properties'>
<KeyValueEditor actionCreator={setMetadataProps} getState={this.getMetadataPropsFromState} schema={MetadataSchema} />
</Collapsible>
</div>
</Resizable>
</div>
);
}
private getMetadataPropsFromState = (state: IState) => state.metadataProps;
private getPropertiesFromState = (state: IState) => state.properties;
}
const mapStateToProps = (state: IState) => ({
metadataProps: state.metadataProps,
nodes: state.nodes,
properties: state.properties,
});
const mapDispatchToProps = {
setMetadataProps,
}
export default connect(mapStateToProps, mapDispatchToProps)(RightPanel);

Просмотреть файл

@ -0,0 +1,4 @@
#EditView {
display: flex;
height: 100%;
}

Просмотреть файл

@ -0,0 +1,19 @@
import * as React from 'react';
import LeftPanel from './LeftPanel';
import Netron from './netron/Netron';
import RightPanel from './RightPanel';
import './View.css';
export default class EditView extends React.Component {
public render() {
return (
<div id='EditView'>
<LeftPanel />
<Netron />
<RightPanel />
</div>
);
}
}

Просмотреть файл

@ -0,0 +1,278 @@
import * as React from 'react';
import { connect } from 'react-redux';
import 'netron/src/view-render.css';
import 'netron/src/view-sidebar.css';
import 'netron/src/view.css';
import 'npm-font-open-sans/open-sans.css';
import { setInputs, setMetadataProps, setModelInputs, setModelOutputs, setNodes, setOutputs, setProperties, setSelectedNode } from '../../../datastore/actionCreators';
import { ModelProtoSingleton } from '../../../datastore/proto/modelProto';
import IState from '../../../datastore/state';
import './fixed-position-override.css';
const browserGlobal = window as any;
interface IComponentProperties {
// Redux properties
file: File,
nodes: { [key: string]: any },
setInputs: typeof setInputs,
setMetadataProps: typeof setMetadataProps,
setModelInputs: typeof setModelInputs,
setModelOutputs: typeof setModelOutputs,
setNodes: typeof setNodes,
setOutputs: typeof setOutputs,
setProperties: typeof setProperties,
setSelectedNode: typeof setSelectedNode,
}
interface IComponentState {
graph: any,
metadataProps: { [key: string]: string },
properties: { [key: string]: string },
}
class Netron extends React.Component<IComponentProperties, IComponentState> {
private proxiesRevoke: Array<() => void> = [];
constructor(props: IComponentProperties) {
super(props);
this.state = {
graph: {},
metadataProps: {},
properties: {},
}
}
public componentDidMount() {
// Netron must be run after rendering the HTML
if (!browserGlobal.host) {
const s = document.createElement('script');
s.src = "netron_bundle.js";
s.async = true;
s.onload = this.onNetronInitialized;
document.body.appendChild(s);
} else {
this.onNetronInitialized();
}
}
public componentWillUnmount() {
for (const revoke of this.proxiesRevoke) {
revoke();
}
}
public UNSAFE_componentWillReceiveProps(nextProps: IComponentProperties) {
if (!browserGlobal.host) {
return;
}
if (nextProps.file && nextProps.file !== this.props.file) {
browserGlobal.host._openFile(nextProps.file);
}
}
public shouldComponentUpdate(nextProps: IComponentProperties, nextState: IComponentState) {
return false; // Netron is a static page and all updates are handled by its JavaScript code
}
public render() {
return (
// Instead of hardcoding the page, a div with dangerouslySetInnerHTML could be used to include Netron's content
<div className='netron-root'>
<div id='welcome' className='background' style={{display: 'block'}}>
<div className='center logo'>
<img className='logo absolute' src='logo.svg' />
<img id='spinner' className='spinner logo absolute' src='spinner.svg' style={{display: 'none'}} />
</div>
<button id='open-file-button' className='center' style={{top: '200px', width: '125px', opacity: 0}}>Open Model...</button>
<input type="file" id="open-file-dialog" style={{display: 'none'}} multiple={false} accept=".onnx, .pb, .meta, .tflite, .keras, .h5, .json, .mlmodel, .caffemodel" />
<div style={{fontWeight: 'normal', color: '#e6e6e6', userSelect: 'none'}}>.</div>
<div style={{fontWeight: 600, color: '#e6e6e6', userSelect: 'none'}}>.</div>
<div style={{fontWeight: 'bold', color: '#e6e6e6', userSelect: 'none'}}>.</div>
</div>
<svg id='graph' className='graph' preserveAspectRatio='xMidYMid meet' width='100%' height='100%' />
<div id='toolbar' className='toolbar' style={{position: 'absolute', top: '10px', left: '10px', display: 'none',}}>
<button id='model-properties-button' className='xxx' title='Model Properties'>
<svg viewBox="0 0 100 100" width="24" height="24">
<rect x="12" y="12" width="76" height="76" rx="16" ry="16" strokeWidth="8" stroke="#fff" />
<line x1="30" y1="37" x2="70" y2="37" strokeWidth="8" strokeLinecap="round" stroke="#fff" />
<line x1="30" y1="50" x2="70" y2="50" strokeWidth="8" strokeLinecap="round" stroke="#fff" />
<line x1="30" y1="63" x2="70" y2="63" strokeWidth="8" strokeLinecap="round" stroke="#fff" />
<rect x="12" y="12" width="76" height="76" rx="16" ry="16" strokeWidth="4" />
<line x1="30" y1="37" x2="70" y2="37" strokeWidth="4" strokeLinecap="round" />
<line x1="30" y1="50" x2="70" y2="50" strokeWidth="4" strokeLinecap="round" />
<line x1="30" y1="63" x2="70" y2="63" strokeWidth="4" strokeLinecap="round" />
</svg>
</button>
<button id='zoom-in-button' className='icon' title='Zoom In'>
<svg viewBox="0 0 100 100" width="24" height="24">
<circle cx="50" cy="50" r="35" strokeWidth="8" stroke="#fff" />
<line x1="50" y1="38" x2="50" y2="62" strokeWidth="8" strokeLinecap="round" stroke="#fff" />
<line x1="38" y1="50" x2="62" y2="50" strokeWidth="8" strokeLinecap="round" stroke="#fff" />
<line x1="78" y1="78" x2="82" y2="82" strokeWidth="12" strokeLinecap="square" stroke="#fff" />
<circle cx="50" cy="50" r="35" strokeWidth="4" />
<line x1="50" y1="38" x2="50" y2="62" strokeWidth="4" strokeLinecap="round" />
<line x1="38" y1="50" x2="62" y2="50" strokeWidth="4" strokeLinecap="round" />
<line x1="78" y1="78" x2="82" y2="82" strokeWidth="8" strokeLinecap="square" />
</svg>
</button>
<button id='zoom-out-button' className='icon' title='Zoom Out'>
<svg viewBox="0 0 100 100" width="24" height="24">
<circle cx="50" cy="50" r="35" strokeWidth="8" stroke="#fff" />
<line x1="38" y1="50" x2="62" y2="50" strokeWidth="8" strokeLinecap="round" stroke="#fff" />
<line x1="78" y1="78" x2="82" y2="82" strokeWidth="12" strokeLinecap="square" stroke="#fff" />
<circle cx="50" cy="50" r="35" strokeWidth="4" />
<line x1="38" y1="50" x2="62" y2="50" strokeWidth="4" strokeLinecap="round" />
<line x1="78" y1="78" x2="82" y2="82" strokeWidth="8" strokeLinecap="square" />
</svg>
</button>
</div>
<div id='sidebar' className='sidebar'>
<h1 id='sidebar-title' className='sidebar-title' />
<a href='javascript:void(0)' id='sidebar-closebutton' className='sidebar-closebutton'>&times;</a>
<div id='sidebar-content' className='sidebar-content' />
</div>
</div>
);
}
private propsToObject(props: any) {
return props.reduce((acc: { [key: string]: string }, x: any) => {
// metadataProps uses key, while model.properties uses name
acc[x.key || x.name] = x.value;
return acc;
}, {});
}
private valueListToObject(values: any) {
return values.reduce((acc: { [key: string]: any }, x: any) => {
const {name, ...props} = x;
acc[name] = props;
return acc;
}, {});
}
private onNetronInitialized = () => {
// Reset document overflow property
document.documentElement.style.overflow = 'initial';
this.installProxies();
}
private installProxies = () => {
// Install proxy on browserGlobal.view.loadBuffer and update the data store
const loadBufferHandler = {
apply: (target: any, thisArg: any, args: any) => {
const [buffer, identifier, callback] = args;
// Patch the callback to update our data store first
return target.call(thisArg, buffer, identifier, (err: Error, model: any) => {
if (!err) {
this.updateDataStore(model);
}
return callback(err, model);
});
},
};
const loadBufferProxy = Proxy.revocable(browserGlobal.view.loadBuffer, loadBufferHandler);
browserGlobal.view.loadBuffer = loadBufferProxy.proxy;
this.proxiesRevoke.push(loadBufferProxy.revoke);
const panelOpenHandler = {
apply: (target: any, thisArg: any, args: any) => {
if (this.props.nodes) {
const title = args[1];
if (title === 'Model Properties') {
this.props.setSelectedNode(title);
return;
} else {
this.props.setSelectedNode(undefined);
}
}
return target.apply(thisArg, args);
},
};
const panelOpenProxy = Proxy.revocable(browserGlobal.view._sidebar.open, panelOpenHandler);
browserGlobal.view._sidebar.open = panelOpenProxy.proxy;
this.proxiesRevoke.push(panelOpenProxy.revoke);
const panelCloseHandler = {
apply: (target: any, thisArg: any, args: any) => {
this.props.setSelectedNode(undefined);
return target.apply(thisArg, args);
},
};
const panelCloseProxy = Proxy.revocable(browserGlobal.view._sidebar.open, panelCloseHandler);
browserGlobal.view._sidebar.open = panelCloseProxy.proxy;
this.proxiesRevoke.push(panelCloseProxy.revoke);
browserGlobal.view.showNodeProperties = (node: any) => this.props.setSelectedNode(node.graph.nodes.indexOf(node));
}
private getModelProperties(modelProto: any) {
const opsetImport = (modelProto.opsetImport as Array<{domain: string, version: number}>)
.reduce((acc, x) => {
const opset = `opsetImport.${x.domain || 'ai.onnx'}`;
return {
...acc,
[opset]: x.version,
};
}, {});
const properties = {
...opsetImport,
docString: modelProto.docString,
domain: modelProto.domain,
irVersion: modelProto.irVersion,
modelVersion: modelProto.modelVersion,
producerName: modelProto.producerName,
producerVersion: modelProto.producerVersion,
}
Object.entries(properties).forEach(([key, value]) => value || delete properties[key]); // filter empty properties
return properties;
}
private updateDataStore = (model: any) => {
const proto = model._model
ModelProtoSingleton.proto = null;
// FIXME What to do when model has multiple graphs?
const graph = model.graphs[0];
if (graph.constructor.name === 'OnnxGraph') {
const getNames = (list: any[]): string[] => list.map((x: any) => x.name);
const inputs = getNames(graph.inputs);
const outputs = getNames(graph.outputs);
this.props.setModelInputs(inputs);
this.props.setModelOutputs(outputs);
this.props.setInputs(this.valueListToObject(proto.graph.input));
this.props.setOutputs(this.valueListToObject(proto.graph.output));
this.props.setNodes(proto.graph.node.filter((x: any) => x.opType !== 'Constant'));
this.props.setMetadataProps(this.propsToObject(proto.metadataProps));
this.props.setProperties(this.getModelProperties(proto));
} else {
this.props.setModelInputs(undefined);
this.props.setModelOutputs(undefined);
this.props.setNodes(undefined);
this.props.setMetadataProps({});
this.props.setProperties({});
}
this.props.setSelectedNode(undefined);
ModelProtoSingleton.proto = proto;
};
}
const mapStateToProps = (state: IState) => ({
file: state.file,
nodes: state.nodes,
});
const mapDispatchToProps = {
setInputs,
setMetadataProps,
setModelInputs,
setModelOutputs,
setNodes,
setOutputs,
setProperties,
setSelectedNode,
}
export default connect(mapStateToProps, mapDispatchToProps)(Netron);

Просмотреть файл

@ -0,0 +1,20 @@
/* Overrides fixed elements in Netron */
.netron-root {
all: initial;
height: 100%;
flex-grow: 1;
position: relative;
}
.background {
position: static;
}
.center {
position: absolute;
}
.sidebar {
position: absolute;
overflow: auto;
}

Просмотреть файл

@ -0,0 +1,35 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"downlevelIteration": true,
"lib": ["es6", "es2016", "es2017.object", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true
},
"exclude": [
"acceptance-tests",
"build",
"deps",
"jest",
"node_modules",
"public",
"scripts",
"src/setupTests.ts",
"webpack",
"Windows ML Dashboard*",
"**/*.spec.ts"
]
}

Просмотреть файл

@ -0,0 +1,3 @@
{
"extends": "./tsconfig.json"
}

Просмотреть файл

@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
"linterOptions": {
"exclude": [
"config/**/*.js",
"deps/**/*.js",
"node_modules/**/*.ts"
]
}
}