diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4ad284c..480073c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,51 +1,7 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/debian +// For format details, see https://aka.ms/devcontainer.json. { - "name": "Ocean Development Environment", - - // python 3.11 on debian, with latest Ocean and optional packages - // source repo: https://github.com/dwavesystems/ocean-dev-docker - "image": "docker.io/dwavesys/ocean-dev:latest", - - // install repo pip requirements (only if present) on content update - "updateContentCommand": "[ ! -r requirements.txt ] || pip install -r requirements.txt", - - // forward/expose container services (relevant only when run locally) - "forwardPorts": [ - // dwave-inspector web app - 18000, 18001, 18002, 18003, 18004, - // OAuth connect redirect URIs - 36000, 36001, 36002, 36003, 36004 - ], - - "portsAttributes": { - "18000-18004": { - "label": "D-Wave Problem Inspector", - "requireLocalPort": true - }, - "36000-36004": { - "label": "OAuth 2.0 authorization code redirect URI", - "requireLocalPort": true - } - }, - - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - // Set *default* container specific settings.json values on container create. - "settings": { - "workbench": { - "editorAssociations": { - "*.md": "vscode.markdown.preview.editor" - }, - "startupEditor": "readme" - } - }, - "extensions": [ - "ms-python.python", - "ms-toolsai.jupyter" - ] - } - } + // Debian stable with the second-latest Python, latest Ocean, and optional dev packages. + // Docker image source: https://github.com/dwavesystems/ocean-dev-docker. + // Devcontainer config: https://github.com/dwavesystems/ocean-devcontainer. + "image": "docker.io/dwavesys/ocean-dev:latest" } diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..5a19db3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,24 @@ + + +### Associated GitHub Issue + + +### Feature Implemented/Bugs Fixed + + +### Additional Information + + +### Accessibility Score + + +### AI Generation Disclosure + \ No newline at end of file diff --git a/.gitignore b/.gitignore index f0b4245..5efcced 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.DS_Store __pycache__ cache -assets/__generated_theme.css data/MNIST/ generated_json/* +static/model_diagram/* +!static/model_diagram/step_5_output_default.png diff --git a/README.md b/README.md index 0be3c7c..e993f44 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ of spin strings arising from encoding the input data into the binary latent spac generated by sampling spin strings from the trained QPU, and passing these to the decoder. -![Demo Example](static/demo.png) +![Demo Example](static/demo.png "Image of demo interface") ## Installation You can run this example without installation in cloud-based IDEs that support the -[Development Containers specification](https://containers.dev/supporting) (aka "devcontainers") +[Development Containers Specification](https://containers.dev/supporting) (aka "devcontainers") such as GitHub Codespaces. For development environments that do not support `devcontainers`, install requirements: @@ -33,9 +33,9 @@ If you are cloning the repo to your local system, working in a ## Usage Your development environment should be configured to access the -[Leap™ quantum cloud service](https://docs.ocean.dwavesys.com/en/stable/overview/sapi.html). +[Leap™ quantum cloud service](https://docs.dwavequantum.com/en/latest/ocean/sapi_access_basic.html). You can see information about supported IDEs and authorizing access to your Leap account -[here](https://docs.dwavesys.com/docs/latest/doc_leap_dev_env.html). +[here](https://docs.dwavequantum.com/en/latest/ocean/leap_authorization.html). Run the following terminal command to start the Dash application: @@ -52,7 +52,7 @@ Configuration options can be found in the [demo_configs.py](demo_configs.py) fil > [!NOTE]\ > If you plan on editing any files while the application is running, please run the application -with the `--debug` command-line argument for easier debugging: +with the `--debug` command-line argument for live reloads and easier debugging: `python app.py --debug` @@ -62,7 +62,7 @@ Variational autoencoders are machine learning models that learn latent represent through an `encoder` and then reconstruct data from these latent representations using a `decoder`. Put simply, -![eq0](static/eq0.png) +![Autoencoder reconstruction equation](static/eq0.png "Equation: x_hat equals decoder(encoder(x))") where _x_hat_ is the reconstruction of _x_. @@ -93,7 +93,7 @@ whose structure is defined in `dwave.plugins.torch.autoencoder` with a We minimize the following loss function: -![eq1](static/eq1.png) +![Total loss function](static/eq1.png "Equation: the loss L(theta) equals MSE(x, x_hat) plus MMD(z, s) plus NLL(z, theta_QPU)") whose terms correspond to a reconstruction loss (MSE), a distribution distance loss (MMD) and a likelihood loss (NLL). Note the NLL term is only optimized with respect to the QPU parameters. @@ -103,7 +103,7 @@ and a likelihood loss (NLL). Note the NLL term is only optimized with respect to The reconstruction loss considered in this problem is the mean squared error over the pixels that form the original image _x_ and the reconstructed image _x_hat_, that is: -![eq2](static/eq2.png) +![Mean squared error equation](static/eq2.png "Equation: MSE(x, y) equals the squared L2 norm of x minus y") Other losses, including perceptual losses, could be used, but from the simplicity of this dataset, we also keep a simple loss. It is worth noting that MSE could make the autoencoder produce blurry @@ -116,13 +116,13 @@ we use the maximum mean discrepancy distance, which uses samples to estimate the two distributions. To measure the (squared) maximum mean discrepancy (η\_k) between two distributions _p_ and _q_, a kernel, _k_, is used: -![eq3](static/eq3.png) +![Squared maximum mean discrepancy equation](static/eq3.png "Equation: eta_k(p, q) written as expectation values of the kernel k over samples z from p and s from q") where ϕ is the feature map associated to the kernel _k_. In this demo, a mixture of radial basis kernels are used: -![eq4](static/eq4.png) +![Radial basis kernel mixture equation](static/eq4.png "Equation: the kernel k(x, x') equals the average of d radial basis kernels with bandwidths gamma_u") where γ\_u is _2^w_ for _w ∈ \{-d/2, -d/2+1, ..., d/2\}_. Thus _MMD(z,s)_ is evaluated by sampling _z_ from the encoded data, and _s_ from the QPU to estimate the expectation values of @@ -135,11 +135,11 @@ encoded data in the latent space. For this, we train the couplings _(\{J\_ij\})_ _(\{h\_i\})_ of the QPU and train the QPU so that the encoded data in the latent space is distributed according to an approximate Boltzmann distribution defined by the QPU parameters, that is we aim for: -![eq5](static/eq5.png) +![Boltzmann distribution equation](static/eq5.png "Equation: p(z = encoder(x)) equals exp(-beta E(z)) divided by the partition function Z") where _E(z)_ is the energy of _z_ with respect to the QPU parameters, i.e., -![eq6](static/eq6.png) +![Energy equation](static/eq6.png "Equation: E(z) equals the sum of J_ij z_i z_j over GRBM edges plus the sum of h_i z_i over GRBM nodes") and _Z_ is the partition function. @@ -147,7 +147,7 @@ To ensure that the encoded data is distributed according to a Boltzmann distribu QPU parameters, we minimize the negative log-likelihood of encoded data under the QPU Boltzmann distribution: -![eq7](static/eq7.png) +![Negative log-likelihood equation](static/eq7.png "Equation: NLL(z, theta_QPU) equals negative log p(z)") ## Model Overview diff --git a/app.py b/app.py index 66e6581..9eb3fb1 100644 --- a/app.py +++ b/app.py @@ -20,9 +20,9 @@ import diskcache from dash import DiskcacheManager -from demo_configs import APP_TITLE, THEME_COLOR, THEME_COLOR_SECONDARY +from demo_configs import APP_TITLE from demo_interface import create_interface -import dash_bootstrap_components as dbc +import dash_mantine_components as dmc # Essential for initializing callbacks. Do not remove. import demo_callbacks @@ -47,12 +47,32 @@ meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}], prevent_initial_callbacks="initial_duplicate", background_callback_manager=background_callback_manager, - external_stylesheets=[dbc.themes.BOOTSTRAP] ) app.title = APP_TITLE app.config.suppress_callback_exceptions = True +app.index_string = """ + + + + {%metas%} + {%title%} + {%favicon%} + + {%css%} + + + {%app_entry%} + + + +""" + # Parse debug argument parser = argparse.ArgumentParser(description="Dash debug setting.") parser.add_argument( @@ -67,26 +87,16 @@ print(f"\nDebug has been set to: {DEBUG}") if not DEBUG: print( - "The app will not show live code updates and the Dash debug menu will be hidden.", - "If editting code while the app is running, run the app with `python app.py --debug`.\n", + "Code changes will not be reflected in the app interface and the Dash debug menu will be hidden.", + "If editing code while the app is running, run the app with `python app.py --debug`.\n", sep="\n", ) -# Generates css file and variable using THEME_COLOR and THEME_COLOR_SECONDARY settings -css = f"""/* Automatically generated theme settings css file, see app.py */ -:root {{ - --theme: {THEME_COLOR}; - --theme-secondary: {THEME_COLOR_SECONDARY}; -}} -""" -with open("assets/__generated_theme.css", "w") as f: - f.write(css) - if __name__ == "__main__": # Imports the Dash HTML code and sets it in the app. # Creates the visual layout and app (see `demo_interface.py`) - app.layout = create_interface() + app.layout = dmc.MantineProvider(create_interface()) # Run the server - app.run(debug=DEBUG, dev_tools_hot_reload=False) + app.run(debug=DEBUG) diff --git a/assets/__base.css b/assets/__base.css deleted file mode 100644 index 1eaa980..0000000 --- a/assets/__base.css +++ /dev/null @@ -1,415 +0,0 @@ -/* -This file is forked from apps/dash-clinical-analytics/assets/base.css -under the following license - -MIT License - -Copyright (c) 2019 Plotly - -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. - -Modifications are licensed under - -Apache License, Version 2.0 -(see ./LICENSE for details) - -*/ - -/* Grid -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -.container { - position: relative; - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 0 20px; - box-sizing: border-box; } - .column, - .columns { - width: 100%; - float: left; - box-sizing: border-box; } - - /* For devices larger than 400px */ - @media (min-width: 400px) { - .container { - width: 85%; - padding: 0; } - } - - /* For devices larger than 550px */ - @media (min-width: 550px) { - .container { - width: 80%; } - .column, - .columns { - margin-left: 2%; } - .column:first-child, - .columns:first-child { - margin-left: 1%; } - - .one.column, - .one.columns { width: 4.66666666667%; } - .two.columns { width: 13.3333333333%; } - .three.columns { width: 22%; } - .four.columns { width: 30.6666666667%; } - .five.columns { width: 39.3333333333%; } - .six.columns { width: 48%; } - .seven.columns { width: 56.6666666667%; } - .eight.columns { width: 65.3333333333%; } - .nine.columns { width: 74.0%; } - .ten.columns { width: 82.6666666667%; } - .eleven.columns { width: 91.3333333333%; } - .twelve.columns { width: 100%; margin-left: 0; } - - .one-third.column { width: 30.6666666667%; } - .two-thirds.column { width: 65.3333333333%; } - - .one-half.column { width: 48%; } - - /* Offsets */ - .offset-by-one.column, - .offset-by-one.columns { margin-left: 8.66666666667%; } - .offset-by-two.column, - .offset-by-two.columns { margin-left: 17.3333333333%; } - .offset-by-three.column, - .offset-by-three.columns { margin-left: 26%; } - .offset-by-four.column, - .offset-by-four.columns { margin-left: 34.6666666667%; } - .offset-by-five.column, - .offset-by-five.columns { margin-left: 43.3333333333%; } - .offset-by-six.column, - .offset-by-six.columns { margin-left: 52%; } - .offset-by-seven.column, - .offset-by-seven.columns { margin-left: 60.6666666667%; } - .offset-by-eight.column, - .offset-by-eight.columns { margin-left: 69.3333333333%; } - .offset-by-nine.column, - .offset-by-nine.columns { margin-left: 78.0%; } - .offset-by-ten.column, - .offset-by-ten.columns { margin-left: 86.6666666667%; } - .offset-by-eleven.column, - .offset-by-eleven.columns { margin-left: 95.3333333333%; } - - .offset-by-one-third.column, - .offset-by-one-third.columns { margin-left: 34.6666666667%; } - .offset-by-two-thirds.column, - .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } - - .offset-by-one-half.column, - .offset-by-one-half.columns { margin-left: 52%; } - - } - - - /* Base Styles - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - /* NOTE - html is set to 62.5% so that all the REM measurements throughout Skeleton - are based on 10px sizing. So basically 1.5rem = 15px :) */ - html { - font-size: 62.5%; } - body { - font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ - line-height: 1.6; - font-weight: 400; - font-family: "Open Sans", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; - color: rgb(50, 50, 50); } - - - /* Typography - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - h1, h2, h3, h4, h5, h6 { - margin-top: 0; - margin-bottom: 0; - font-weight: 300; } - h1 { font-size: 4.5rem; line-height: 1.2; letter-spacing: -.1rem; margin-bottom: 2rem; } - h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; margin-bottom: 1.8rem; margin-top: 1.8rem;} - h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; margin-bottom: 1.5rem; margin-top: 1.5rem;} - h4 { font-size: 2.6rem; line-height: 1.35; letter-spacing: -.08rem; margin-bottom: 1.2rem; margin-top: 1.2rem;} - h5 { font-size: 2.2rem; line-height: 1.5; letter-spacing: -.05rem; margin-bottom: 0.6rem; margin-top: 0.6rem;} - h6 { font-size: 2.0rem; line-height: 1.6; letter-spacing: 0; margin-bottom: 0.75rem; margin-top: 0.75rem;} - - p { - margin-top: 0; } - - - /* Blockquotes - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - blockquote { - border-left: 4px lightgrey solid; - padding-left: 1rem; - margin-top: 2rem; - margin-bottom: 2rem; - margin-left: 0rem; - } - - - /* Links - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - a { - color: #1EAEDB; - text-decoration: underline; - cursor: pointer;} - a:hover { - color: #0FA0CE; } - - - /* Buttons - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - .button, - button, - input[type="button"] { - display: inline-block; - height: 38px; - padding: 0 30px; - color: white; - text-align: center; - font-size: 11px; - font-weight: 600; - line-height: 38px; - letter-spacing: .1rem; - text-transform: uppercase; - text-decoration: none; - white-space: nowrap; - background-color: transparent; - border-radius: 4px; - border: 1px solid #bbb; - cursor: pointer; - box-sizing: border-box; - background-color: #2a7de1 ; - } - .button:hover, - button:hover, - - input[type="button"]:hover, - .button:focus, - button:focus, - input[type="submit"]:focus, - input[type="reset"]:focus, - input[type="button"]:focus { - color: white; - border-color: #888; - outline: 0; - background-color: #074c91; - - } - .button.button-primary, - button.button-primary, - input[type="submit"].button-primary, - input[type="reset"].button-primary, - input[type="button"].button-primary { - color: #EEEEEE; - background-color: #33C3F0; - } - .button.button-primary:hover, - button.button-primary:hover, - input[type="submit"].button-primary:hover, - input[type="reset"].button-primary:hover, - input[type="button"].button-primary:hover, - .button.button-primary:focus, - button.button-primary:focus, - input[type="submit"].button-primary:focus, - input[type="reset"].button-primary:focus, - input[type="button"].button-primary:focus { - color: #FFF; - background-color: #1EAEDB; - border-color: #1EAEDB; } - - - /* Forms - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - input[type="email"], - input[type="number"], - input[type="search"], - input[type="text"], - input[type="tel"], - input[type="url"], - input[type="password"], - textarea, - select { - height: 38px; - padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ - background-color: #fff; - border: 1px solid #D1D1D1; - border-radius: 4px; - box-shadow: none; - box-sizing: border-box; - font-family: inherit; - font-size: inherit; /*https://stackoverflow.com/questions/6080413/why-doesnt-input-inherit-the-font-from-body*/} - /* Removes awkward default styles on some inputs for iOS */ - input[type="email"], - input[type="number"], - input[type="search"], - input[type="text"], - input[type="tel"], - input[type="url"], - input[type="password"], - textarea { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; } - textarea { - min-height: 65px; - padding-top: 6px; - padding-bottom: 6px; } - input[type="email"]:focus, - input[type="number"]:focus, - input[type="search"]:focus, - input[type="text"]:focus, - input[type="tel"]:focus, - input[type="url"]:focus, - input[type="password"]:focus, - textarea:focus, - /*select:focus {*/ - /* border: 1px solid #33C3F0;*/ - /* outline: 0; }*/ - label, - legend { - display: block; - margin-bottom: 0px; } - fieldset { - padding: 0; - border-width: 0; } - input[type="checkbox"], - input[type="radio"] { - display: inline; } - label > .label-body { - display: inline-block; - margin-left: .5rem; - font-weight: normal; } - - - /* Lists - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - ul { - list-style: circle inside; } - ol { - list-style: decimal inside; } - ol, ul { - padding-left: 0; - margin-top: 0; } - ul ul, - ul ol, - ol ol, - ol ul { - margin: 1.5rem 0 1.5rem 3rem; - font-size: 90%; } - li { - margin-bottom: 1rem; } - - - /* Tables - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - table { - border-collapse: collapse; - } - th, - td { - padding: 12px 15px; - text-align: left; - border-bottom: 1px solid #E1E1E1; } - th:first-child, - td:first-child { - padding-left: 0; } - th:last-child, - td:last-child { - padding-right: 0; } - - - /* Spacing - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - button, - .button { - margin-bottom: 0rem; } - input, - textarea, - select, - fieldset { - margin-bottom: 0rem; } - pre, - dl, - figure, - table, - form { - margin-bottom: 0rem; } - p, - ul, - ol { - margin-bottom: 0.75rem; } - - /* Utilities - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - .u-full-width { - width: 100%; - box-sizing: border-box; } - .u-max-full-width { - max-width: 100%; - box-sizing: border-box; } - .u-pull-right { - float: right; } - .u-pull-left { - float: left; } - - - /* Misc - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - hr { - margin-top: 3rem; - margin-bottom: 3.5rem; - border-width: 0; - border-top: 1px solid #E1E1E1; } - - - /* Clearing - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - - /* Self Clearing Goodness */ - .container:after, - .row:after, - .u-cf { - content: ""; - display: table; - clear: both; } - - - /* Media Queries - –––––––––––––––––––––––––––––––––––––––––––––––––– */ - /* - Note: The best way to structure the use of media queries is to create the queries - near the relevant code. For example, if you wanted to change the styles for buttons - on small devices, paste the mobile query code up in the buttons section and style it - there. - */ - - - /* Larger than mobile */ - @media (min-width: 400px) {} - - /* Larger than phablet (also point when grid becomes active) */ - @media (min-width: 550px) {} - - /* Larger than tablet */ - @media (min-width: 750px) {} - - /* Larger than desktop */ - @media (min-width: 1000px) {} - - /* Larger than Desktop HD */ - @media (min-width: 1200px) {} diff --git a/assets/__demo_variables.css b/assets/__demo_variables.css index 0665f39..7b2bbcb 100644 --- a/assets/__demo_variables.css +++ b/assets/__demo_variables.css @@ -16,8 +16,6 @@ limitations under the License. /* Variable declarations for all CSS files. -Some additional variables are generated automatically in app.py -and stored in __generated_theme.css. Dash reads all css files contained in `/assets/` so no imports are necessary. */ @@ -26,22 +24,26 @@ Dash reads all css files contained in `/assets/` so no imports are necessary. --blue-bright: #03B8FF; --blue-light: #2A7DE1; --blue-dark: #074C91; - --red-light: #C70039; - --red-dark: #900C3F; + --blue-darker: #2D4376; + --blue-darkest: #202239; + --red-light: #F57677; + --red-dark: #AA3A3C; --orange: #FF7006; --teal-light: #06ECDC; - --teal-dark: #17BEBB; - --teal-darker: #008C82; - --grey-lighter: #EEEEEE; - --grey-light: #DDDDDD; + --teal: #17BEBB; + --teal-dark: #008C82; + --grey-lighter: #F5F7FB; + --grey-light: #DBDBDB; --grey: #AAAAAA; - --grey-medium: #777777; + --grey-medium: #737373; --grey-dark: #222222; - --font: "Helvetica Neue", sans-serif; - --banner-height: 5.5rem; - --left-col-width: 35rem; + --box-shadow: 0 0 1rem rgba(66, 82, 121, 0.2); + --font: "proxima-nova", "Helvetica Neue", sans-serif; + --banner-height: 6.5rem; + --left-col-width: 26.25rem; --problem-details-height: 8rem; - --latent-vector-width: 23rem; - --forward-arrow-height: 6rem; - --blue-lighter: #79adec; /* lighter varient of --blue-light */ + --title-section-height: 13.5rem; + --latent-vector-width: 14.375rem; + --forward-arrow-height: 3.75rem; + --blue-lighter: #79ADEC; /* lighter variant of --blue-light */ } diff --git a/assets/__style_guide.css b/assets/__style_guide.css index f949118..a0595cf 100644 --- a/assets/__style_guide.css +++ b/assets/__style_guide.css @@ -16,87 +16,118 @@ limitations under the License. /* A custom base of style rules defined specifically for the demos */ +*, *::before, *::after { + box-sizing: border-box; +} + body { - background-color: #f9f9f9; - color: var(--grey-dark); - font-size: 1.4rem; + color: var(--grey-darkest); + font-size: 1.125rem; + line-height: 1.5rem; margin: 0; } h1, h2, h3, h4, h5, h6, td, th, span, a, p, label, button, input { font-family: var(--font); + color: var(--blue-darkest); } h1, h2, h3, h4, h5, h6 { - color: var(--theme); - font-weight: 400; + font-weight: 600; + margin-top: 0; } h1 { - font-size: 3.2rem; + font-size: 2.5rem; + line-height: 3rem; + margin-bottom: 1rem; + font-weight: 400; } h2 { - font-size: 2.6rem; + font-size: 1.625rem; } h3 { - font-size: 2.4rem; + font-size: 1.5rem; } h4 { - font-size: 2rem; + font-size: 1.25rem; margin-top: 0; font-weight: 500; } h5 { - font-size: 1.8rem; + font-size: 1.125rem; + margin: 1rem 0; font-weight: 600; + margin-top: 1rem; + margin-bottom: 1rem; } label { - margin: 2rem 0 0.2rem; - font-weight: 500; + margin: 1rem 0 0.5rem; + font-weight: 600; + display: block; } hr { - margin: 1rem 0; + margin: 0.625rem 0; + border-width: 0; + border-top: 1px solid var(--grey-light); } table { - margin-bottom: 2rem; - font-size: 1.5rem; + margin-bottom: 1.25rem; + font-size: 1rem; + border-collapse: collapse; border: 1px solid var(--grey-light); } -th, td { - color: var(--grey-dark); - padding: 0.5rem 1rem; - border-right: 1px solid var(--grey-light); +thead { + border-bottom: 1px solid var(--grey-light); + font-weight: 600; + background-color: var(--blue-darker); +} + +tfoot { + border-top: 1px solid var(--grey-light); + font-weight: 600; +} + +th { + font-weight: 600; + color: white; + padding: 0.5rem; + text-align: left; +} + +td { + padding: 0.25rem 0.5rem; + text-align: left; } th:first-child, td:first-child { - padding-left: 1.2rem; + padding-left: 0.75rem; } th:last-child, td:last-child { - padding-right: 1.2rem; + padding-right: 0.75rem; } input[type="checkbox"], input[type="radio"] { - accent-color: var(--theme); - margin: 0 0.6rem 0 0; + accent-color: var(--blue-darker); + margin: 0 0.5rem 0 0; } .display-none { - display: none; + display: none !important; } #app-container { - min-width: 100rem; height: 100vh; display: flex; flex-direction: column; @@ -104,9 +135,9 @@ input[type="radio"] { .banner { height: var(--banner-height); - box-sizing: border-box; - background-color: var(--theme); - padding: 1.4rem 2rem; + background-color: #12131f; + padding: 0 2rem; + border-bottom: 2px solid var(--teal); display: flex; align-items: center; @@ -114,7 +145,7 @@ input[type="radio"] { } .banner img { - height: 100%; + height: 1rem; } .columns-main { @@ -124,28 +155,46 @@ input[type="radio"] { .left-column { display: flex; - height: calc(100vh - var(--banner-height)); + height: 100%; + background-color: var(--grey-lighter); + box-shadow: var(--box-shadow); + z-index: 10; } .right-column { - background-color: var(--grey-light); - padding: 2rem 0 0; width: 100%; + min-height: 40rem; +} + +.title-section { + padding: 2rem 1rem 1.5rem; + background-color: var(--blue-darkest); + background-image: url("background.svg"); + background-position: 30% 0; + height: var(--title-section-height); +} + +.title-section h1 { + color: white; +} + +.title-section p { + margin-bottom: 0; + color: white; } .settings { - margin: 2rem 0; + margin: 1rem 0 2rem; } -.radio label, -.checklist label { - margin-top: 0.2rem; - font-weight: 400; +.form-section { + display: flex; + flex: 1; } -.radio--inline label:first-child, -.checklist--inline label:first-child { - margin-right: 1.4rem; +.settings-and-buttons-wrapper { + padding: 0 1rem 2rem; + flex: 1; } /* The following rules are overwriting Dash styling */ @@ -154,7 +203,7 @@ input[type="radio"] { } .is-focused:not(.is-open) > .Select-control { - border-color: var(--theme-secondary); + border-color: var(--teal); box-shadow: none; } @@ -163,6 +212,37 @@ input[type="radio"] { } div.dash-sk-circle { - height: 6rem; - width: 6rem; + height: 4rem; + width: 4rem; +} + +/* Hide the skip link off-screen by default */ +.skip-link { + position: absolute; + left: -1.5rem; + width: 1px; + height: 1px; + overflow: hidden; +} + +/* Make the skip link visible when it receives keyboard focus */ +.skip-link:focus { + color: var(--teal); + position: static; + width: auto; + height: auto; + overflow: visible; + display: inline-block; + padding: 1rem; + z-index: 1000; +} + +@media screen and (max-width: 1000px) { + .columns-main { + flex-direction: column; + } + + .form-section { + flex-direction: column; + } } diff --git a/assets/_buttons.css b/assets/_buttons.css index 2694961..b1ab3a5 100644 --- a/assets/_buttons.css +++ b/assets/_buttons.css @@ -17,31 +17,48 @@ limitations under the License. /* Style rules for buttons */ button { - font-size: 1.4rem; - line-height: 1.4rem; - padding: 1.8rem 3.6rem; - height: auto; - color: white; - transition: all 0.2s ease-in-out; border: none; - background-color: var(--theme); + font-size: 0.875rem; + font-weight: 600; + line-height: 1.75; + border-radius: 4px; + padding: 0.75rem 1.5rem; + cursor: pointer; + transition: all 0.2s ease-in-out; } -button:hover:not(:disabled) { - filter: brightness(80%); +.button { + font-size: 0.875rem; + font-weight: 600; + line-height: 1.75; + border-radius: 4px; + padding: 0.75rem 1.5rem; color: white; + text-transform: uppercase; + background: radial-gradient(61.22% 95.86% at 26.96% 100%, #4c71c6 0%, var(--blue-darker) 100%); + transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1), + border-color 250ms cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: rgba(29, 30, 36, 0.19) 0px 5px 14px 0px; + border: 1px solid rgba(76, 113, 198, 0.4); +} + +.button:hover { + background: radial-gradient(61.22% 95.86% at 26.96% 100%, var(--blue-darker) 0%, var(--blue-darker) 100%); } -.button-group button { +#run-button, +#cancel-button { width: 100%; } -.button-group button:last-child { - background-color: var(--red-light); +#cancel-button { + background: radial-gradient(61.22% 95.86% at 26.96% 100%, var(--red-light) 0%, var(--red-dark) 100%); + border: 1px solid rgb(245, 118, 119, 0.4); } -.button-group button:last-child:hover { - background-color: var(--red-dark); +#cancel-button:hover { + background: radial-gradient(61.22% 95.86% at 26.96% 100%, var(--red-dark) 0%, var(--red-dark) 100%); } button:disabled { @@ -49,3 +66,11 @@ button:disabled { filter: brightness(70%); opacity: 0.7; } + +button:focus-visible { + box-shadow: 0 0 0 4px var(--teal); +} + +.button-small { + padding: 0.35rem 1rem; +} diff --git a/assets/_collapse.css b/assets/_collapse.css index f6549dc..faa8ae1 100644 --- a/assets/_collapse.css +++ b/assets/_collapse.css @@ -16,69 +16,102 @@ limitations under the License. /* Style rules for collapsible dropdowns like left-column-collapse and problem-details-collapse */ +.collapse-arrow { + height: 1.5rem; + position: relative; + width: 1.4rem; + transition: border-color 0.25s ease-in-out; +} + +.collapse-arrow:before, +.collapse-arrow:after { + content: ""; + display: block; + position: absolute; + height: 0.25rem; + width: 1.5rem; + background: var(--blue-darker); + border-radius: 0.125rem; +} + +.collapse-arrow:before { + rotate: -70deg; + top: 0; +} + +.collapse-arrow:after { + rotate: 70deg; + bottom: 0; +} + +.settings-and-buttons-wrapper { + height: calc(100vh - var(--title-section-height)); + overflow-y: auto; + direction: rtl; +} + +.settings-and-buttons, +.progress-bars { + direction: ltr; +} + .left-column .left-column-layer-1 { width: var(--left-col-width); - transition: width 0.6s ease-in-out; - overflow-x: hidden; - overflow-y: auto; + transition: width 0.5s ease-in-out; direction: rtl; } .details-to-collapse { height: var(--problem-details-height); - transition: height 0.6s ease-in-out; + transition: height 0.5s ease-in-out; overflow: hidden; } .left-column .left-column-layer-2 { - padding: 3rem 2rem 4rem; width: var(--left-col-width); box-sizing: border-box; direction: ltr; + display: flex; + flex-direction: column; + height: 100%; } .details-collapse-wrapper { - margin-bottom: 2rem; + margin-bottom: 1.25rem; overflow: hidden; } -.left-column-collapse, -.left-column-collapse:hover, -.left-column-collapse:focus { +.left-column-collapse { background: white; - border-right: 1px solid var(--grey-lighter); + border: none; height: 100%; border-radius: 0; - padding: 0 0 0 0.5rem; - filter: none; + padding: 0; display: block; } +.left-column-collapse:hover, +.left-column-collapse:focus { + background: var(--grey-lighter); +} + .details-collapse, .details-collapse:hover, .details-collapse:focus { background: none; display: flex; - padding: 0 2rem 0 0; + align-items: center; + padding: 0 1.25rem 0 0; text-transform: none; -} - -.collapse-arrow { - border-right: 4px solid var(--grey-light); - border-bottom: 4px solid var(--grey-light); - transform: rotate(135deg) skew(165deg, 165deg); - height: 2rem; - width: 2rem; - margin-right: -0.3rem; - transition: border-color 0.25s ease-in-out; + color: var(--blue-dark); } .details-collapse .collapse-arrow { - transform: rotate(225deg) skew(165deg, 165deg); - margin: 1.5rem 0 0 1.5rem; - border-color: var(--theme); - height: 1.2rem; - width: 1.2rem; + transform: rotate(90deg); + width: 3rem; + margin-top: 1.5rem; + margin-left: 0.5rem; + color: var(--blue-darkest); } .left-column-collapse:hover .collapse-arrow { @@ -86,18 +119,28 @@ limitations under the License. } .collapsed .left-column-collapse { - padding: 0 0.5rem 0 0; + padding: 0 0.25rem 0 0; } .collapsed .left-column-collapse .collapse-arrow { - margin-left: -0.3rem; - margin-right: 0; - transform: rotate(315deg) skew(165deg, 165deg); + margin-right: -4px; + transform: rotate(180deg); +} + +.title-section { + transition: all 0.5s ease-in-out; +} + +.collapsed .title-section { + height: 0; + padding: 0; + overflow: hidden; } .collapsed .details-collapse .collapse-arrow { - margin-top: 1rem; - transform: rotate(45deg) skew(165deg, 165deg); + margin-bottom: 1.5rem; + margin-top: 0; + transform: rotate(-90deg); } .collapsed .details-to-collapse { @@ -105,5 +148,40 @@ limitations under the License. } .collapsed .left-column-layer-1 { - width: 0; + width: 1.5rem; +} + +@media screen and (max-width: 1000px) { + .left-column { + height: auto; + width: 100%; + } + + .left-column .left-column-layer-1, + .left-column .left-column-layer-2, + .left-column .left-column-collapse { + width: 100%; + } + + .left-column-collapse .collapse-arrow { + transform: rotate(90deg); + margin: auto; + } + + .collapsed .title-section { + height: 0; + overflow-y: hidden; + } + + .collapsed .settings-and-buttons-wrapper { + height: 0; + flex: unset; + padding: 0; + overflow-y: hidden; + } + + .collapsed .left-column-collapse .collapse-arrow { + margin-right: auto; + transform: rotate(-90deg); + } } diff --git a/assets/_inputs.css b/assets/_inputs.css new file mode 100644 index 0000000..3170f4a --- /dev/null +++ b/assets/_inputs.css @@ -0,0 +1,117 @@ +/* +Copyright 2024 D-Wave + +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. +*/ + +input::placeholder { + color: #656565 !important; +} + +/* Style rules for the dmc.Slider element */ +.slider { + margin-bottom: 2rem; +} + +.mantine-Slider-label, +.mantine-Slider-markLabel, +.mantine-RangeSlider-label, +.mantine-RangeSlider-markLabel { + font-family: var(--font); +} + +.mantine-Slider-markLabel, +.mantine-RangeSlider-markLabel { + color: #666666; +} + +.mantine-Slider-label, +.mantine-RangeSlider-label { + background-color: white; + color: var(--grey-dark); + border: 1px solid var(--grey); + border-radius: 2px; + top: auto; + bottom: calc(-2.25rem * var(--mantine-scale)); + width: 1.5rem; + text-align: center; + padding: 0.15rem 0; +} + +.mantine-Slider-label:before, +.mantine-RangeSlider-label:before, +.mantine-Slider-label:after, +.mantine-RangeSlider-label:after { + content: ''; + display: block; + position: absolute; + width: 0; + height: 0; +} + +.mantine-Slider-label:before, +.mantine-RangeSlider-label:before { + left: 6px; + top: -13px; + border: 6px solid transparent; + border-bottom-color: var(--grey); +} + +.mantine-Slider-label:after, +.mantine-RangeSlider-label:after { + left: 7px; + top: -10px; + border: 5px solid transparent; + border-bottom-color: white; +} + +/* Style rules for the dmc.NumberInput and dmc.TextInput elements */ +.mantine-NumberInput-input:focus-visible, +.mantine-TextInput-input:focus-visible { + box-shadow: 0 0 0 2px var(--teal); +} + +/* Style rules for the dmc.Select element */ +.mantine-Select-input:focus-visible { + box-shadow: 0 0 0 2px var(--teal); +} + +.mantine-InputWrapper-label.mantine-RadioGroup-label, +.mantine-InputWrapper-label.mantine-CheckboxGroup-label { + font-family: var(--font); + color: var(--blue-darkest); + font-size: 1.125rem; + line-height: 1.5rem; + margin: 1rem 0 0.5rem; + font-weight: 600; + display: block; +} + +.radio label, +.checklist label, +label.mantine-Checkbox-label { + margin-top: 0.125rem; + font-weight: 400; + font-size: 1rem; + line-height: 1rem; +} + +.radio--inline label:first-child, +.checklist--inline label:first-child { + margin-right: 0.75rem; +} + +.radio:not(.radio--inline) .mantine-Group-root, +.checklist:not(.checklist--inline) .mantine-Group-root { + display: block; +} diff --git a/assets/_slider.css b/assets/_slider.css deleted file mode 100644 index 9b04378..0000000 --- a/assets/_slider.css +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2024 D-Wave - -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. -*/ - -/* Style rules for the dcc.Slider element */ - -.rc-slider-tooltip-inner { - font-family: var(--font); -} - -.slider { - padding-left: 6px !important; - padding-right: 8px !important; -} - -.rc-slider-handle, -.rc-slider-dot-active { - border-color: var(--theme-secondary); - transition: filter 0.1s ease-in-out; -} - -.rc-slider-handle:active { - box-shadow: 0 0 5px var(--theme-secondary); -} - -.rc-slider-handle:active, -.rc-slider-handle:hover, -.rc-slider-handle-click-focused, -.rc-slider-handle-click-focused:focus { - border-color: var(--theme-secondary); - filter: brightness(80%) -} - -.rc-slider-track { - background-color: var(--theme-secondary); -} - -.rc-slider-tooltip-inner { - background-color: white; - color: var(--grey-dark); - border: 1px solid var(--grey); - border-radius: 2px; -} - -.rc-slider-tooltip { - pointer-events: none; -} - -.rc-slider-tooltip .rc-slider-tooltip-arrow { - display: none; -} - -.rc-slider-tooltip-inner:before, -.rc-slider-tooltip-inner:after { - content: ''; - display: block; - position: absolute; - width: 0; - height: 0; -} - -.rc-slider-tooltip-inner:before { - left: 6px; - top: -11px; - border: 6px solid transparent; - border-bottom-color: var(--grey); -} - -.rc-slider-tooltip-inner:after { - left: 7px; - top: -9px; - border: 5px solid transparent; - border-bottom-color: white; -} - -.rc-slider-tooltip-placement-top .rc-slider-tooltip-inner:before { - bottom: -3px; - top: auto; - transform: rotate(180deg); -} - -.rc-slider-tooltip-placement-top .rc-slider-tooltip-inner:after { - bottom: -1px; - top: auto; - transform: rotate(180deg); -} diff --git a/assets/_tabs.css b/assets/_tabs.css index 2cfac0d..e9d6f23 100644 --- a/assets/_tabs.css +++ b/assets/_tabs.css @@ -16,46 +16,63 @@ limitations under the License. /* Style rules for tabs like the input and results tabs */ -.tab-container { - border-bottom: 3px solid var(--theme-secondary); - flex-direction: row; +nav { + height: 100%; } -.tab { - margin: 0 2rem; - border-top-left-radius: 0.6rem; - border-top-right-radius: 0.6rem; +.mantine-Tabs-root { + height: 100%; } -div.tab.tab--selected { - border: 3px solid var(--theme-secondary) !important; - border-bottom: none !important; - cursor: default; - box-shadow: 0 6px 0 -3px white; +.mantine-Tabs-panel { + height: calc(100% - var(--banner-height)); } -div.tab.tab--disabled { - cursor: not-allowed !important; +.mantine-Tabs-list { + --tabs-list-gap: 1.5rem; + height: 100%; } -.tab:first-child { - margin-right: 1rem; +.mantine-Tabs-list::before { + border: none; } -.tab:last-child { - margin-left: 1rem; +.mantine-Tabs-tab { + font-size: 1.5rem; + font-weight: 400; + border-radius: 0; + border-bottom-width: 0; + border-top-width: 5px; + height: 100%; } -.tab-parent { - display: flex; - flex-direction: column; +button.mantine-Tabs-tab:disabled { + filter: unset; + opacity: 1; + color: #7E7F86; + border-color: transparent; } -.tab-content, -.tab-parent { - height: 100%; +.mantine-Tabs-tab span { + color: #A0A1A5; + opacity: 1; + transition: color 0.2s ease-in-out; +} + +.mantine-Tabs-tab:where([data-active]) span { + opacity: 1; + color: white; +} + +.mantine-Tabs-tab:hover { + background-color: unset; +} + +.mantine-Tabs-tab:hover:where(:not([data-active])) { + border-color: unset; } -.tab-content { - background-color: white; +.mantine-Tabs-tab:hover:where(:not(:disabled)) span { + opacity: 1; + color: white; } diff --git a/assets/background.svg b/assets/background.svg new file mode 100644 index 0000000..d8ad4ad --- /dev/null +++ b/assets/background.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/background2.svg b/assets/background2.svg new file mode 100644 index 0000000..f29fa10 --- /dev/null +++ b/assets/background2.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/demo.css b/assets/demo.css index 8361386..219877d 100644 --- a/assets/demo.css +++ b/assets/demo.css @@ -24,8 +24,13 @@ body { overflow: hidden; } -.tab-content-results { - padding: 3rem 3rem 0; +.tab-content-wrapper { + background-color: black; +} + +#tabs-panel-results-tab .tab-content-wrapper, +#tabs-panel-loss-tab .tab-content-wrapper { + padding: 1.875rem 1.875rem 0; display: flex; flex-direction: column; justify-content: space-between; @@ -33,17 +38,14 @@ body { box-sizing: border-box; } -.input, .results { - display: flex; - justify-content: center; - align-items: center; - height: 100%; +.tab-content-wrapper h3 { + color: var(--grey-lighter); } #input-file { padding: 1rem; border: 1px dashed var(--grey-dark); - border-radius: 0.5rem; + border-radius: 0.25rem; text-align: center; } @@ -55,11 +57,11 @@ div.left-column .left-column-layer-2 { } .header-wrapper { - padding: 3rem 2rem 2rem; + padding: 1.875rem 1.25rem 1.25rem; } div.left-column .tab-content { - padding: 2rem 2rem 4rem; + padding: 1.25rem 1.25rem 2.5rem; display: flex; flex-direction: column; justify-content: space-between; @@ -67,7 +69,7 @@ div.left-column .tab-content { } div.left-column .tab { - padding: 0.6rem; + padding: 0.375rem; border: 1px solid #d6d6d6; } @@ -83,13 +85,36 @@ div.left-column .tab { margin-top: 0; } -#setting-tabs-parent .tab-content { - padding-top: 0; - padding-bottom: 2rem; +#setting-tabs-panel-generate-tab .settings { + margin-bottom: 3rem; } -.button-group { - margin-bottom: 2rem; +#setting-tabs .banner { + background: none; + height: 4rem; + margin-bottom: 1.5rem; + padding: 0; +} + +#setting-tabs .banner .mantine-Tabs-list { + --tabs-list-gap: 0; + width: 100%; +} + +#setting-tabs .banner .mantine-Tabs-tab { + font-size: 1.25rem; + flex-basis: 50%; +} + +.progress-bars { + margin: 1.25rem auto 0; + max-width: 22rem; +} + +.progress-bars p { + margin: 0; + font-size: 1rem; + color: var(--grey-lighter); } .graph-wrapper-flex { @@ -104,12 +129,12 @@ div.left-column .tab { } progress { - height: 2rem; + height: 1.25rem; width: 100%; } progress::-webkit-progress-bar { - background-color: var(--theme); + background-color: var(--blue-light); } .display-flex { @@ -134,10 +159,10 @@ progress::-webkit-progress-bar { #popup > div { position: absolute; background-color: white; - left: calc(50% - 20rem); + left: calc(50% - 12.5rem); top: 40%; z-index: 10; - padding: 3rem; + padding: 1.875rem; width: 40rem; border-radius: 1rem; border: 4px solid var(--red-light); @@ -146,8 +171,8 @@ progress::-webkit-progress-bar { #popup-toggle { position: absolute; right: 0; - top: -1.5rem; - font-size: 3rem; + top: -1rem; + font-size: 1.875rem; color: var(--red-light); cursor: pointer; padding: 1rem; @@ -160,10 +185,14 @@ progress::-webkit-progress-bar { .model-details { margin-top: 1rem; + margin-bottom: 2rem; } .model-details p { + margin-top: 0.25rem; margin-bottom: 0; + font-size: 1rem; + color: var(--grey-lighter); } .checklist label { @@ -171,7 +200,7 @@ progress::-webkit-progress-bar { } .problem-details-table { - margin-bottom: 4rem; + margin-bottom: 2.5rem; } #file-name-help-text { @@ -191,17 +220,16 @@ progress::-webkit-progress-bar { display: flex; justify-content: space-between; align-items: center; - background-color: black; - height: calc(100vh - var(--banner-height) - 8.8rem); + height: calc(100vh - var(--banner-height)); overflow: hidden; - padding: 0 2rem; + padding: 0 1.25rem; } -.graph-model-wrapper .graph-model-itermediate-step { +.graph-model-wrapper .graph-model-intermediate-step { width: 12%; } -.graph-model-wrapper .graph-model-itermediate-step img { +.graph-model-wrapper .graph-model-intermediate-step img { width: 100%; height: auto; image-rendering: pixelated; @@ -225,12 +253,12 @@ progress::-webkit-progress-bar { #latent-space-vector { margin: auto; - font-size: 2rem; - border-left: 3px solid var(--theme-secondary); - border-right: 3px solid var(--theme-secondary); + font-size: 1.25rem; + border-left: 3px solid var(--blue-light); + border-right: 3px solid var(--blue-light); font-weight: 800; position: relative; - padding: 0 0.5rem; + padding: 0 0.25rem; text-align: center; display: flex; width: var(--latent-vector-width); @@ -256,7 +284,7 @@ progress::-webkit-progress-bar { left: 0; height: 3px; width: 100%; - background: linear-gradient(to right, var(--theme-secondary) 3%, transparent 3%, transparent 97%, var(--theme-secondary) 97%); + background: linear-gradient(to right, var(--blue-light) 3%, transparent 3%, transparent 97%, var(--blue-light) 97%); } #latent-space-vector:after { @@ -267,58 +295,57 @@ progress::-webkit-progress-bar { position: relative; display: flex; justify-content: center; - margin-top: 1.5rem; + margin-top: 1rem; } .curly-brace > div { - border-top: 3px solid var(--theme-secondary); - width: calc(var(--latent-vector-width)/2 - 2rem); - height: 2rem; + border-top: 3px solid var(--blue-light); + width: calc(var(--latent-vector-width)/2 - 1.25rem); + height: 1.25rem; position: relative; } .curly-brace > div:first-child { - border-top-right-radius: 2rem; + border-top-right-radius: 1.25rem; } .curly-brace > div:last-child { - border-top-left-radius: 2rem; + border-top-left-radius: 1.25rem; } .curly-brace > div:last-child:after, .curly-brace > div:first-child:before { content: ""; - width: 2rem; - height: 2rem; + width: 1.25rem; + height: 1.25rem; position: absolute; display: block; } .curly-brace > div:first-child:before { - border-bottom: 3px solid var(--theme-secondary); - border-bottom-left-radius: 2rem; - left: -2rem; - top: -2rem; + border-bottom: 3px solid var(--blue-light); + border-bottom-left-radius: 1.25rem; + left: -1.25rem; + top: -1.25rem; } .curly-brace > div:last-child:after { - border-bottom: 3px solid var(--theme-secondary); - border-bottom-right-radius: 2rem; - top: -2rem; - right: -2rem; + border-bottom: 3px solid var(--blue-light); + border-bottom-right-radius: 1.25rem; + top: -1.25rem; + right: -1.25rem; } .latent-vector-diagram { text-align: center; - color: var(--theme-secondary); + color: var(--blue-light); height: 100%; } #latent-diagram-size { color: var(--blue-lighter); font-weight: 800; - font-size: 2rem; - margin-top: -1rem; + font-size: 1.25rem; } .latent-loss-arrows { @@ -326,7 +353,7 @@ progress::-webkit-progress-bar { display: flex; justify-content: space-between; width: 102%; - top: calc(30% - 100px); + top: calc(30% - 60px); left: 0; pointer-events: none; } @@ -335,82 +362,82 @@ progress::-webkit-progress-bar { #arrow-right-pointer-events { pointer-events: auto; height: 100%; - width: 6rem; + width: 3.75rem; position: absolute; top: 0; } #arrow-left-pointer-events { - left: -2rem; + left: -1.25rem; } #arrow-right-pointer-events { - right: -2rem; + right: -1.25rem; } .arrow-left, .arrow-right { border: 4px solid; - border-color: transparent transparent transparent var(--theme-secondary); + border-color: transparent transparent transparent var(--blue-light); border-radius: 50%; - height: 18rem; - width: 10rem; + height: 10.25rem; + width: 6.25rem; } .arrow-left:after, .arrow-right:before { content: ""; position: absolute; - height: 2rem; - width: 2rem; + height: 1.25rem; + width: 1.25rem; transform: rotate(10deg); } .arrow-right { - border-color: transparent var(--theme-secondary) transparent transparent; + border-color: transparent var(--blue-light) transparent transparent; right: 0; } .arrow-left:after { - border-right: 4px solid var(--theme-secondary); - border-bottom: 4px solid var(--theme-secondary); - bottom: 2rem; - left: 0.4rem; + border-right: 4px solid var(--blue-light); + border-bottom: 4px solid var(--blue-light); + bottom: 1.25rem; + left: 0.25rem; } .arrow-right:before { content: ""; position: absolute; - border-left: 4px solid var(--theme-secondary); - border-top: 4px solid var(--theme-secondary); - height: 2rem; - width: 2rem; - top: 2rem; - right: 0.4rem; + border-left: 4px solid var(--blue-light); + border-top: 4px solid var(--blue-light); + height: 1.25rem; + width: 1.25rem; + top: 1.25rem; + right: 0.25rem; } .arrow-left:before, .arrow-right:after { content: "NLL"; - font-weight: 600; - font-size: 2rem; + font-weight: 700; + font-size: 1.25rem; color: var(--blue-lighter); position: absolute; background-color: black; } .arrow-left:before { - left: -1rem; - top: 2rem; + left: -0.5rem; + top: 1rem; } .arrow-right:after { content: "MMD"; - right: -1rem; - bottom: 2rem; + right: -0.5rem; + bottom: 1rem; } -.graph-model-itermediate-step { +.graph-model-intermediate-step { position: relative; } @@ -419,7 +446,7 @@ progress::-webkit-progress-bar { width: 180%; left: -60%; top: calc(50% - var(--forward-arrow-height)/2); - background: linear-gradient(to right, transparent 0%, var(--theme-secondary) 56%); + background: linear-gradient(to right, transparent 0%, var(--blue-light) 56%); position: absolute; pointer-events: none; } @@ -432,7 +459,7 @@ progress::-webkit-progress-bar { content: ""; position: absolute; border: calc(var(--forward-arrow-height)/2) solid transparent; - border-left-color: var(--theme-secondary); + border-left-color: var(--blue-light); top: 0; right: calc(-1*var(--forward-arrow-height)); } @@ -441,7 +468,7 @@ progress::-webkit-progress-bar { content: "ENCODING"; color: black; opacity: 0.45; - font-size: 1.8rem; + font-size: 1.125rem; font-weight: bolder; letter-spacing: 2px; font-style: italic; @@ -450,7 +477,7 @@ progress::-webkit-progress-bar { line-height: var(--forward-arrow-height); } -.graph-model-itermediate-step:last-of-type .forward-arrow:before { +.graph-model-intermediate-step:last-of-type .forward-arrow:before { content: "DECODING"; } @@ -460,14 +487,95 @@ progress::-webkit-progress-bar { transition: filter 0.1s linear, opacity 0.1s linear; } -/* Overwriting bootstrap tooltip styling */ -.tooltip { - --bs-tooltip-arrow-height: 1.5rem; - --bs-tooltip-arrow-width: 1.5rem; - --bs-tooltip-bg: white; - --bs-tooltip-color: var(--grey-dark); - --bs-tooltip-font-size: 1.4rem; - --bs-tooltip-padding-x: 1rem; - --bs-tooltip-padding-y: 1rem; - --bs-tooltip-max-width: 30rem; +.button { + width: 100%; +} + +#cancel-generation-button, +#cancel-training-button { + background: radial-gradient(61.22% 95.86% at 26.96% 100%, var(--red-light) 0%, var(--red-dark) 100%); + border: 1px solid rgb(245, 118, 119, 0.4); +} + +#cancel-generation-button:hover, +#cancel-training-button:hover { + background: radial-gradient(61.22% 95.86% at 26.96% 100%, var(--red-dark) 0%, var(--red-dark) 100%); +} + +.mantine-Tooltip-tooltip { + padding: 1rem; +} + +.mantine-Tooltip-tooltip h5 { + margin-top: 0; +} + +.mantine-Tooltip-tooltip p { + margin-bottom: 0; +} + + +/* Overrides left column styling */ +.settings-and-buttons { + color: #f5f7fb; + background-color: rgba(32, 34, 57, 0.5); + transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1); + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 1.25rem; + overflow: hidden; +} + +.settings-and-buttons .mantine-Tabs-panel { + padding: 0 1.625rem 1.625rem; +} + +.settings-and-buttons .mantine-Tabs-tab { + border-top-width: 0; +} + +.settings-and-buttons .mantine-Tabs-tab:where([data-active]) { + background: rgba(18, 19, 31, 0.55); +} + +.settings-and-buttons .mantine-Tabs-tab:where(:not([data-active])) span { + color: #CECFD0; +} + +.title-section { + background: none; +} + +.left-column { + background-image: url("/assets/background2.svg"); + background-size: cover; +} + +label, +.mantine-InputWrapper-label.mantine-CheckboxGroup-label { + color: var(--grey-lighter); +} + +.mantine-Slider-markLabel { + color: var(--grey-lighter); +} + +.slider.mantine-Slider-root { + --slider-color: var(--teal-dark) !important; +} + +.mantine-Checkbox-root .mantine-Checkbox-input { + --checkbox-color: var(--teal-dark) !important; +} + +.left-column-collapse { + background: none; +} + +.left-column-collapse:hover, +.left-column-collapse:focus { + background: rgba(255, 255, 255, 0.1); +} + +.collapse-arrow:before, .collapse-arrow:after { + background: var(--grey-lighter); } diff --git a/assets/model_diagram/latent_encoded.json b/assets/model_diagram/latent_encoded.json deleted file mode 100644 index a553353..0000000 --- a/assets/model_diagram/latent_encoded.json +++ /dev/null @@ -1 +0,0 @@ -[-1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0] \ No newline at end of file diff --git a/assets/model_diagram/latent_qpu.json b/assets/model_diagram/latent_qpu.json deleted file mode 100644 index b943142..0000000 --- a/assets/model_diagram/latent_qpu.json +++ /dev/null @@ -1 +0,0 @@ -[-1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0] \ No newline at end of file diff --git a/assets/model_diagram/step_1_input.png b/assets/model_diagram/step_1_input.png deleted file mode 100644 index ae08973..0000000 Binary files a/assets/model_diagram/step_1_input.png and /dev/null differ diff --git a/assets/model_diagram/step_2_encode.png b/assets/model_diagram/step_2_encode.png deleted file mode 100644 index 2e440ab..0000000 Binary files a/assets/model_diagram/step_2_encode.png and /dev/null differ diff --git a/assets/model_diagram/step_4_decode.png b/assets/model_diagram/step_4_decode.png deleted file mode 100644 index ff98ee9..0000000 Binary files a/assets/model_diagram/step_4_decode.png and /dev/null differ diff --git a/assets/model_diagram/step_5_output.png b/assets/model_diagram/step_5_output.png deleted file mode 100644 index 9946a3e..0000000 Binary files a/assets/model_diagram/step_5_output.png and /dev/null differ diff --git a/demo_callbacks.py b/demo_callbacks.py index 146f328..94685ef 100644 --- a/demo_callbacks.py +++ b/demo_callbacks.py @@ -21,11 +21,11 @@ import re from pathlib import Path from typing import NamedTuple -import torch import dash import plotly.io as pio -from dash import ALL, ctx, MATCH +import torch +from dash import ALL, MATCH, ctx from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate from plotly import graph_objects as go @@ -36,7 +36,7 @@ generate_latent_vector, generate_model_data, generate_options, - generate_problem_details_table + generate_problem_details_table, ) from src.model_wrapper import ModelWrapper from src.utils.callback_helpers import ( @@ -60,13 +60,14 @@ @dash.callback( Output({"type": "to-collapse-class", "index": MATCH}, "className"), + Output({"type": "collapse-trigger", "index": MATCH}, "aria-expanded"), inputs=[ Input({"type": "collapse-trigger", "index": MATCH}, "n_clicks"), State({"type": "to-collapse-class", "index": MATCH}, "className"), ], prevent_initial_call=True, ) -def toggle_left_column(collapse_trigger: int, to_collapse_class: str) -> str: +def toggle_left_column(collapse_trigger: int, to_collapse_class: str) -> tuple[str, str]: """Toggles a 'collapsed' class that hides and shows some aspect of the UI. Args: @@ -76,13 +77,14 @@ def toggle_left_column(collapse_trigger: int, to_collapse_class: str) -> str: Returns: str: The new class name of the thing to collapse. + str: The aria-expanded value. """ classes = to_collapse_class.split(" ") if to_collapse_class else [] if "collapsed" in classes: classes.remove("collapsed") - return " ".join(classes) - return to_collapse_class + " collapsed" if to_collapse_class else "collapsed" + return " ".join(classes), "true" + return to_collapse_class + " collapsed" if to_collapse_class else "collapsed", "false" @dash.callback( @@ -146,11 +148,16 @@ def update_model_diagram_imgs( fig_qpu = go.Figure(fig_qpu) fig_encoded = go.Figure(fig_encoded) - with open(LATENT_QPU_FILE, "r") as f: - latent_qpu = json.load(f) - - with open(LATENT_ENCODED_FILE, "r") as f: - latent_encoded = json.load(f) + try: + with open(LATENT_QPU_FILE, "r") as f: + latent_qpu = json.load(f) + + with open(LATENT_ENCODED_FILE, "r") as f: + latent_encoded = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + # The latent files are written during the run (latent_qpu.json only at the end of each + # epoch), so they may be missing or half-written on early progress ticks. + raise PreventUpdate color_mapping_qpu = [GRAPH_COLORS[int(latent_qpu[i] > 0)] for i in latent_mapping] color_mapping_encoded = [GRAPH_COLORS[int(latent_encoded[i] > 0)] for i in latent_mapping] @@ -183,6 +190,7 @@ class CheckQpuAndUpdateModelReturn(NamedTuple): step_5_img: str = dash.no_update has_loaded_diagram: bool = True + @dash.callback( Output("popup", "className"), Output("generate-button", "disabled"), @@ -202,7 +210,7 @@ class CheckQpuAndUpdateModelReturn(NamedTuple): Input("setting-tabs", "value"), State("example-image", "data"), State("has-loaded-diagram", "data"), - ] + ], ) def check_qpu_and_update_model( model_file_name: str, @@ -237,7 +245,9 @@ def check_qpu_and_update_model( step_5_img: The file path for the output image. has_loaded_diagram: Keeps track of whether this is a page load. """ - switched_to_generate_tab = ctx.triggered_id == "setting-tabs" and setting_tabs_value == "generate-tab" + switched_to_generate_tab = ( + ctx.triggered_id == "setting-tabs" and setting_tabs_value == "generate-tab" + ) # If first load, or a new model is chosen, or the settings tab is changed to "generate" if not ctx.triggered_id or ctx.triggered_id == "model-file-name" or switched_to_generate_tab: @@ -279,7 +289,11 @@ def check_qpu_and_update_model( latent_mapping=latent_mapping, step_2_img=f"{STEP_2_FILE}?force_refresh={force_refresh}", step_4_img=f"{STEP_4_FILE}?force_refresh={force_refresh}", - step_5_img=f"{STEP_5_FILE}?force_refresh={force_refresh}" if has_loaded_diagram else dash.no_update, + step_5_img=( + f"{STEP_5_FILE}?force_refresh={force_refresh}" + if has_loaded_diagram + else dash.no_update + ), has_loaded_diagram=False if not ctx.triggered_id else True, ) @@ -296,22 +310,22 @@ def check_qpu_and_update_model( @dash.callback( Output("tune-parameter-settings", "className"), - Input("tune-params", "value"), + Input("tune-params", "checked"), ) -def toggle_tuning_params(tune_params: list[int]) -> str: +def toggle_tuning_params(tune_params: bool) -> str: """Show/hide tune parameter settings when Tune Parameters box is toggled. Args: - tune_params: The value of the Tune Parameters checkbox as a list. + tune_params: The value of the Tune Parameters checkbox. Returns: tune-parameter-settings-classname: The class name to show/hide the tune parameter settings. """ - return "" if len(tune_params) else "display-none" + return "" if tune_params else "display-none" @dash.callback( - Output("model-file-name", "options"), + Output("model-file-name", "data"), Output("model-file-name", "value"), Input("last-trained-model", "data"), ) @@ -319,10 +333,10 @@ def initialize_training_model(last_trained_model: str) -> tuple[list[str], str]: """Initializes the Trained Models dropdown options based on model files available. Args: - last_trained_model: The most recently trained model directiory name. + last_trained_model: The most recently trained model directory name. Returns: - model-file-name-options: The options for the Trained Model dropdown selection. + model-file-name-data: The options for the Trained Model dropdown selection. model-file-name-value: The value of the dropdown. """ models = [] @@ -336,10 +350,10 @@ def initialize_training_model(last_trained_model: str) -> tuple[list[str], str]: models.append(directory) if not len(models): - models = generate_options(["No Models Found (please train and save a model)"]) + models = ["No Models Found (please train and save a model)"] return ( - models, + generate_options(models), last_trained_model if last_trained_model else models[0], ) @@ -406,7 +420,7 @@ def cancel_progress(cancel_train: int, cancel_generate: int) -> tuple[str, str]: progress-wrapper-className: The classname of the second progress wrapper. """ - return "visibility-hidden", "visibility-hidden" + return "display-none", "display-none" @dash.callback( @@ -482,8 +496,8 @@ class UpdateEachEpochReturn(NamedTuple): Output("results-tab", "disabled"), Output("loss-tab", "disabled"), Output("tabs", "value"), - Output("results-tab", "label"), - Output("loss-tab", "label"), + Output("results-tab", "children"), + Output("loss-tab", "children"), Output("problem-details", "children"), inputs=[ Input("epoch-checker", "n_intervals"), @@ -583,11 +597,11 @@ def update_each_epoch(epoch_checker: int, last_saved_id: int) -> UpdateEachEpoch State("example-image", "data"), ], running=[ - (Output("cancel-training-button", "className"), "", "display-none"), - (Output("train-button", "className"), "display-none", ""), + (Output("cancel-training-button", "style"), {}, {"display": "none"}), + (Output("train-button", "style"), {"display": "none"}, {}), (Output("generate-tab", "disabled"), True, False), # Disables generate tab while running. - (Output("results-tab", "label"), "Training...", "Generated Images"), - (Output("loss-tab", "label"), "Training...", "Loss Graphs"), + (Output("results-tab", "children"), "Training...", "Generated Images"), + (Output("loss-tab", "children"), "Training...", "Loss Graphs"), (Output("epoch-checker", "disabled"), False, True), ], cancel=[Input("cancel-training-button", "n_clicks")], @@ -659,7 +673,7 @@ def train( fig_mse_loss, fig_dvae_loss, file_name, - "visibility-hidden", + "display-none", ) @@ -671,7 +685,7 @@ class GenerateReturn(NamedTuple): fig_mse_loss: go.Figure = dash.no_update fig_total_loss: go.Figure = dash.no_update popup_classname: str = "display-none" - progress_wrapper_classname: str = "visibility-hidden" + progress_wrapper_classname: str = "display-none" results_tab_disabled: bool = dash.no_update loss_tab_disabled: bool = dash.no_update problem_details_table: list = dash.no_update @@ -691,16 +705,16 @@ class GenerateReturn(NamedTuple): inputs=[ Input("generate-button", "n_clicks"), State("model-file-name", "value"), - State("tune-params", "value"), + State("tune-params", "checked"), State({"type": "n-epochs", "index": 1}, "value"), State("example-image", "data"), ], running=[ - (Output("cancel-generation-button", "className"), "", "display-none"), - (Output("generate-button", "className"), "display-none", ""), + (Output("cancel-generation-button", "style"), {}, {"display": "none"}), + (Output("generate-button", "style"), {"display": "none"}, {}), (Output("train-tab", "disabled"), True, False), # Disables train tab while running. - (Output("results-tab", "label"), "Generating...", "Generated Images"), - (Output("loss-tab", "label"), "Generating...", "Loss Graphs"), + (Output("results-tab", "children"), "Generating...", "Generated Images"), + (Output("loss-tab", "children"), "Generating...", "Loss Graphs"), (Output("epoch-checker", "disabled"), False, True), ], progress=[ @@ -714,7 +728,7 @@ def generate( set_progress, generate_click: int, model_file_name: str, - tune_parameters: list, + tune_parameters: bool, n_epochs: int, example_image: list, ) -> GenerateReturn: @@ -763,7 +777,13 @@ def generate( model.train_init(n_epochs) fig_output, fig_reconstructed, fig_mse_loss, fig_dvae_loss = execute_training( - set_progress, model, n_epochs, model_data["qpu"], model_data["n_latents"], loss_data, example_image=example_image + set_progress, + model, + n_epochs, + model_data["qpu"], + model_data["n_latents"], + loss_data, + example_image=example_image, ) model_file_name += f"_tuned_{n_epochs}_epochs" @@ -779,7 +799,7 @@ def generate( else: fig_output = model.generate_output(latent_qpu_file=LATENT_QPU_FILE, sharpen=SHARPEN_OUTPUT) - fig_reconstructed = model.generate_reconstucted_samples(sharpen=SHARPEN_OUTPUT) + fig_reconstructed = model.generate_reconstructed_samples(sharpen=SHARPEN_OUTPUT) model.losses = loss_data fig_mse_loss, fig_dvae_loss = model.generate_loss_plot() diff --git a/demo_configs.py b/demo_configs.py index 02d1c3f..29db9ba 100644 --- a/demo_configs.py +++ b/demo_configs.py @@ -14,12 +14,6 @@ """This file stores input parameters for the app.""" -# THEME_COLOR is used for the button, text, and banner and should be dark -# and pass accessibility checks with white: https://webaim.org/resources/contrastchecker/ -# THEME_COLOR_SECONDARY can be light or dark and is used for sliders, loading icon, and tabs -THEME_COLOR = "#074C91" # D-Wave dark blue default #074C91 -THEME_COLOR_SECONDARY = "#2A7DE1" # D-Wave blue default #2A7DE1 - THUMBNAIL = "static/dwave_logo.svg" APP_TITLE = "ML Image Generation" diff --git a/demo_interface.py b/demo_interface.py index bea9fed..b831794 100644 --- a/demo_interface.py +++ b/demo_interface.py @@ -13,15 +13,16 @@ # limitations under the License. """This file stores the Dash HTML layout for the app.""" + from __future__ import annotations import json -from typing import Any, Optional +from enum import EnumMeta +import dash_mantine_components as dmc from dash import dcc, html from dwave.cloud import Client from plotly import graph_objects as go -import dash_bootstrap_components as dbc from demo_configs import ( DEFAULT_QPU, @@ -30,16 +31,15 @@ MAIN_HEADER, SLIDER_EPOCHS, SLIDER_LATENTS, - THEME_COLOR_SECONDARY, THUMBNAIL, ) from src.utils.callback_helpers import ( - get_example_image, LATENT_ENCODED_FILE, STEP_1_FILE, STEP_2_FILE, STEP_4_FILE, - STEP_5_FILE_DEFAULT + STEP_5_FILE_DEFAULT, + get_example_image, ) # Initialize available QPUs @@ -66,61 +66,60 @@ LATENT_DIAGRAM_END = 1 # An empty black fig to show when loading -DEFAULT_FIG = go.Figure( - layout=go.Layout(paper_bgcolor="black", plot_bgcolor="black") -) +DEFAULT_FIG = go.Figure(layout=go.Layout(paper_bgcolor="black", plot_bgcolor="black")) DEFAULT_FIG.update_xaxes(showgrid=False, zeroline=False) DEFAULT_FIG.update_yaxes(showgrid=False, zeroline=False) -def slider(label: str, id: str, config: dict) -> html.Div: +THEME_COLOR = "#2d4376" + + +def slider(label: str, id: str | dict, config: dict) -> html.Div: """Slider element for value selection. Args: label: The title that goes above the slider. id: A unique selector for this element. - config: A dictionary of slider configerations, see dcc.Slider Dash docs. + config: A dictionary of slider configurations, see dmc.Slider Dash Mantine docs. """ return html.Div( className="slider-wrapper", children=[ - html.Label(label), - dcc.Slider( + html.Label(label, htmlFor=str(id)), + dmc.Slider( id=id, className="slider", **config, - marks={ - config["min"]: str(config["min"]), - config["max"]: str(config["max"]), - }, - tooltip={ - "placement": "bottom", - "always_visible": True, - }, + marks=[ + {"value": config["min"], "label": f'{config["min"]}'}, + {"value": config["max"], "label": f'{config["max"]}'}, + ], + labelAlwaysOn=True, + thumbLabel=f"{label} slider", + color=THEME_COLOR, ), ], ) -def dropdown(label: str, id: str, options: list, value: Optional[Any] = None) -> html.Div: +def dropdown(label: str, id: str, options: list, value: str | int | None = None) -> html.Div: """Dropdown element for option selection. Args: label: The title that goes above the dropdown. id: A unique selector for this element. options: A list of dictionaries of labels and values. - value: Optional default value. + value: The default selected value. """ return html.Div( className="dropdown-wrapper", children=[ - html.Label(label), - dcc.Dropdown( + html.Label(label, htmlFor=str(id)), + dmc.Select( id=id, - options=options, - value=value if value else options[0]["value"], - clearable=False, - searchable=False, + data=options, + value=value if value is not None else options[0]["value"], + allowDeselect=False, ), ], ) @@ -139,46 +138,97 @@ def checklist(label: str, id: str, options: list, values: list, inline: bool = T return html.Div( className="checklist-wrapper", children=[ - html.Label(label), - dcc.Checklist( + dmc.CheckboxGroup( id=id, className=f"checklist{' checklist--inline' if inline else ''}", - inline=inline, - options=options, + label=label, value=values, + children=dmc.Group( + [ + dmc.Checkbox( + label=option["label"], value=option["value"], color=THEME_COLOR + ) + for option in options + ], + ), ), ], ) -def radio(label: str, id: str, options: list, value: int, inline: bool = True) -> html.Div: - """Radio element for option selection. +def checkbox(label: str, id: str, checked: bool) -> html.Div: + """Checkbox element. Args: - label: The title that goes above the radio. + label: The title that goes above the checkbox. id: A unique selector for this element. - options: A list of dictionaries of labels and values. - value: The value of the radio that should be preselected. - inline: Whether the options are displayed beside or below each other. + checked: Whether the checkbox is checked or not. """ return html.Div( - className="radio-wrapper", + className="checkbox-wrapper", children=[ - html.Label(label), - dcc.RadioItems( + dmc.Checkbox( id=id, - className=f"radio{' radio--inline' if inline else ''}", - inline=inline, - options=options, - value=value, + label=label, + checked=checked, + color=THEME_COLOR, + ) + ], + ) + + +def input(label: str, id: str, configs: dict, type: str = "number") -> html.Div: + """Input element for either text or number input. + + Args: + label: The title that goes above the input. + id: A unique selector for this element. + configs: A dictionary of configurations for the input element. + type: The type of input, either "number" or "text". + """ + return html.Div( + className="input-wrapper", + children=[ + html.Label(label, htmlFor=id), + ( + dmc.TextInput( + id=id, + **configs, + ) + if type == "text" + else dmc.NumberInput( + id=id, + **configs, + ) ), ], ) +def generate_options(options: list | EnumMeta | dict) -> list[dict]: + """Format options for dropdowns, checklists, radios, etc. + + Args: + options: A list, EnumMeta, or dictionary of options to format. + + Returns: + A list of dictionaries with "label" and "value" keys for each option. + """ + if isinstance(options, EnumMeta): + return [{"label": option.label, "value": f"{option.value}"} for option in options] + + if isinstance(options, dict): + return [{"label": f"{key}", "value": f"{value}"} for key, value in options.items()] + + return [{"label": f"{option}", "value": f"{option}"} for option in options] + + def generate_model_data(model_data: dict) -> html.Div: """Display model data. + Args: + model_data: A dictionary containing the model data to display. + Returns: html.Div: A Div containing the model data associated with the selected model. """ @@ -202,18 +252,13 @@ def generate_model_data(model_data: dict) -> html.Div: ) -def generate_options(options_list: list) -> list[dict]: - """Generates options for dropdowns, checklists, radios, etc.""" - return [{"label": label, "value": i} for i, label in enumerate(options_list)] - - def generate_train_tab() -> html.Div: """Settings for training the model. Returns: - html.Div: A Div containing the settings for latents and save file name. + A Div containing the settings for latents and save file name. """ - qpu_options = [{"label": qpu, "value": qpu} for qpu in SOLVERS] + qpu_options = generate_options(SOLVERS) return html.Div( className="settings", @@ -234,12 +279,11 @@ def generate_train_tab() -> html.Div: {"type": "n-epochs", "index": 0}, SLIDER_EPOCHS, ), - html.Label("Save to File Name"), + html.Label("Save to File Name", htmlFor="file-name"), html.Div( [ - dcc.Input( + dmc.TextInput( id="file-name", - type="text", required=True, ), html.P( @@ -258,7 +302,7 @@ def generate_generate_tab() -> html.Div: """Settings for generating. Returns: - html.Div: A Div containing the settings for selecting the training file and other settings. + A Div containing the settings for selecting the training file and other settings. """ return html.Div( @@ -270,11 +314,10 @@ def generate_generate_tab() -> html.Div: generate_options(["No Models Found (please train and save a model)"]), ), html.Div(id="model-details"), - checklist( - "", + checkbox( + "Tune Parameters", "tune-params", - generate_options(["Tune Parameters"]), - [], + checked=False, ), html.Div( [ @@ -311,7 +354,7 @@ def generate_progress_bar(index: int) -> html.Div: ), ], id={"type": "progress-wrapper", "index": index}, - className="visibility-hidden", + className="display-none", ) @@ -321,38 +364,38 @@ def generate_settings_form() -> dcc.Tabs: Returns: dcc.Tabs: Tabs containing settings for training and generation. """ - return dcc.Tabs( + return dmc.Tabs( id="setting-tabs", value="generate-tab", - mobile_breakpoint=0, + color="white", children=[ - dcc.Tab( - label="Train", - id="train-tab", - className="tab", + html.Header( + className="banner", children=[ - generate_train_tab(), - html.Div( + dmc.TabsList( [ - generate_run_buttons("Train", "Cancel Training"), - generate_progress_bar(0), + dmc.TabsTab("Train", value="train-tab", id="train-tab"), + dmc.TabsTab( + "Generate", + value="generate-tab", + id="generate-tab", + ), ] ), ], ), - dcc.Tab( - label="Generate", - id="generate-tab", + dmc.TabsPanel( + value="train-tab", + children=[ + generate_train_tab(), + generate_run_buttons("Train", "Cancel Training"), + ], + ), + dmc.TabsPanel( value="generate-tab", - className="tab", children=[ generate_generate_tab(), - html.Div( - [ - generate_run_buttons("Generate", "Cancel Generation"), - generate_progress_bar(1), - ] - ), + generate_run_buttons("Generate", "Cancel Generation"), ], ), ], @@ -365,16 +408,15 @@ def generate_run_buttons(run_text: str, cancel_text: str) -> html.Div: className="button-group", children=[ html.Button( + run_text, id=f'{"-".join(run_text.lower().split(" "))}-button', - children=run_text, - n_clicks=0, - disabled=False, + className="button", ), html.Button( + cancel_text, id=f'{"-".join(cancel_text.lower().split(" "))}-button', - children=cancel_text, - n_clicks=0, - className="display-none", + className="button", + style={"display": "none"}, ), ], ) @@ -400,8 +442,7 @@ def generate_problem_details_table(details: dict) -> html.Table: def generate_latent_vector( - latent_start: list[int]=LATENT_DIAGRAM_START, - latent_end: int=LATENT_DIAGRAM_END + latent_start: list[int] = LATENT_DIAGRAM_START, latent_end: int = LATENT_DIAGRAM_END ) -> list: """Generate the visual +/- ones vector @@ -413,18 +454,13 @@ def generate_latent_vector( A list containing the visuals for the first few +/- ones and the last +/- one. """ latent_start_html = [ - html.Div( - one, className=f"latent-{'plus' if one > 0 else 'minus'}" - ) for one in latent_start + html.Div(one, className=f"latent-{'plus' if one > 0 else 'minus'}") for one in latent_start ] return [ *latent_start_html, html.Div("..."), - html.Div( - latent_end, - className=f"latent-{'plus' if latent_end > 0 else 'minus'}" - ), + html.Div(latent_end, className=f"latent-{'plus' if latent_end > 0 else 'minus'}"), ] @@ -441,7 +477,7 @@ def generate_graph(type: str) -> list: return dcc.Loading( parent_className="graph", type="circle", - color=THEME_COLOR_SECONDARY, + color=THEME_COLOR, overlay_style={"visibility": "visible"}, delay_show=100, children=[ @@ -457,7 +493,7 @@ def generate_graph(type: str) -> list: className="graph", id=f"{type}-graph-wrapper", ), - ] + ], ) @@ -473,17 +509,19 @@ def generate_tooltip(title: str, description: str, target: str) -> list: A tooltip. """ - return dbc.Tooltip( - children=html.Div( + return dmc.Tooltip( + label=html.Div( [ html.H5(title), html.P(description), ], - className="dbc-tooltip-content" ), - className="dbc-tooltip", - target=target, - delay={"show": 0, "hide": 100}, + target=f"#{target}", + multiline=True, + w=300, + color="white", + withArrow=True, + arrowSize=16, ) @@ -492,6 +530,13 @@ def create_interface(): return html.Div( id="app-container", children=[ + html.A( # Skip link for accessibility + "Skip to main content", + href="#main-content", + id="skip-to-main", + className="skip-link", + tabIndex=1, + ), # Below are any temporary storage items, e.g., for sharing data between callbacks. dcc.Store(id="has-loaded-diagram"), dcc.Store(id="last-trained-model"), @@ -516,10 +561,10 @@ def create_interface(): ) ], ), - html.Div(className="banner", children=[html.Img(src=THUMBNAIL)]), # Settings and results columns - html.Div( + html.Main( className="columns-main", + id="main-content", children=[ # Left column html.Div( @@ -537,140 +582,245 @@ def create_interface(): html.H1(MAIN_HEADER), html.P(DESCRIPTION), ], - className="header-wrapper", + className="title-section", + ), + html.Div( + [ + html.Div( + [ + html.Div( + [ + generate_settings_form(), + ], + className="settings-and-buttons", + ), + html.Div( + [ + generate_progress_bar(0), + generate_progress_bar(1), + ], + className="progress-bars", + ), + ], + className="settings-and-buttons-wrapper", + ), + # Left column collapse button + html.Div( + html.Button( + id={ + "type": "collapse-trigger", + "index": 0, + }, + className="left-column-collapse", + title="Collapse sidebar", + children=[ + html.Div(className="collapse-arrow") + ], + **{"aria-expanded": "true"}, + ), + ), + ], + className="form-section", ), - generate_settings_form(), ], ) ], ), - # Left column collapse button - html.Div( - html.Button( - id={"type": "collapse-trigger", "index": 0}, - className="left-column-collapse", - children=[html.Div(className="collapse-arrow")], - ), - ), ], ), # Right column html.Div( className="right-column", children=[ - dcc.Tabs( + dmc.Tabs( id="tabs", value="input-tab", - mobile_breakpoint=0, + color="white", children=[ - dcc.Tab( - label="Machine Learning Model", - id="input-tab", - value="input-tab", # used for switching tabs programatically - className="tab", + html.Header( + className="banner", children=[ - html.Div( + html.Nav( [ - html.Img( - src=STEP_1_FILE, - id="step-1-input-img", + dmc.TabsList( + [ + dmc.TabsTab("Model", value="input-tab"), + dmc.TabsTab( + "Generated Images", + value="results-tab", + id="results-tab", + disabled=True, + ), + dmc.TabsTab( + "Loss Graphs", + value="loss-tab", + id="loss-tab", + disabled=True, + ), + ] ), - html.Div([ - html.Div(className="forward-arrow"), - html.Img( - src=STEP_2_FILE, - id="step-2-encode-img", - ), - ], className="graph-model-itermediate-step"), + ] + ), + html.Img(src=THUMBNAIL, alt="D-Wave logo"), + ], + ), + dmc.TabsPanel( + value="input-tab", + tabIndex="12", + children=[ + html.Div( + className="tab-content-wrapper", + children=[ html.Div( [ - generate_graph("qpu"), - generate_graph("encoded"), - html.Div([ - html.Div(id="arrow-left-pointer-events"), # Only here to act as the pointer event for the hover - html.Div(id="arrow-right-pointer-events"), # Only here to act as the pointer event for the hover - html.Div(className="arrow-left", id="arrow-left"), - html.Div(className="arrow-right", id="arrow-right"), - ], className="latent-loss-arrows"), - html.Div([ - html.Div(generate_latent_vector(), id="latent-space-vector"), - html.Div([html.Div(), html.Div()], className="curly-brace"), - html.Div("256", id="latent-diagram-size") - - ], className="latent-vector-diagram", id="latent-vector-diagram"), + html.Img( + src=STEP_1_FILE, + id="step-1-input-img", + alt="Input image from the MNIST dataset", + ), + html.Div( + [ + html.Div( + className="forward-arrow" + ), + html.Img( + src=STEP_2_FILE, + id="step-2-encode-img", + alt="Encoding image", + ), + ], + className="graph-model-intermediate-step", + ), + html.Div( + [ + generate_graph("qpu"), + generate_graph("encoded"), + html.Div( + [ + html.Div( + id="arrow-left-pointer-events" + ), # Only here to act as the pointer event for the hover + html.Div( + id="arrow-right-pointer-events" + ), # Only here to act as the pointer event for the hover + html.Div( + className="arrow-left", + id="arrow-left", + ), + html.Div( + className="arrow-right", + id="arrow-right", + ), + ], + className="latent-loss-arrows", + ), + html.Div( + [ + html.Div( + generate_latent_vector(), + id="latent-space-vector", + ), + html.Div( + [ + html.Div(), + html.Div(), + ], + className="curly-brace", + ), + html.Div( + "256", + id="latent-diagram-size", + ), + ], + className="latent-vector-diagram", + id="latent-vector-diagram", + ), + ], + className="latent-space-graph-wrapper", + ), + html.Div( + [ + html.Div( + className="forward-arrow" + ), + html.Img( + src=STEP_4_FILE, + id="step-4-decode-img", + alt="Decoding image", + ), + ], + className="graph-model-intermediate-step", + ), + html.Img( + src=STEP_5_FILE_DEFAULT, + id="step-5-output-img", + alt="Recreated output image", + ), ], - className="latent-space-graph-wrapper", + className="graph-model-wrapper", + ), + generate_tooltip( + "Input Image", + "An input image from the MNIST dataset.", + "step-1-input-img", + ), + generate_tooltip( + "Encoding", + "Each collection of 4 pixels represents a feature of the input image.", + "step-2-encode-img", + ), + generate_tooltip( + "Quantum Computer Sample", + "The quantum computer is sampled to obtain a new list of +/- 1s. These +/- 1s can be decoded to create a new never before seen image.", + "qpu-graph-wrapper", + ), + generate_tooltip( + "Mapping of Latent +/- 1s onto the Quantum Computer", + "Each +/- 1 of the latent representation is mapped to a qubit on the quantum computer. This allows for a comparison between the quantum computer and the latent representation.", + "encoded-graph-wrapper", + ), + generate_tooltip( + "Negative Log-Likelihood (NLL)", + "NLL is a function that trains the quantum computer by comparing the quantum computer samples to the encoded images. This helps the quantum computer generate new +/- 1s that more accurately describe the encoded image.", + "arrow-left-pointer-events", + ), + generate_tooltip( + "Max Mean Discrepancy (MMD)", + "MMD is a function that trains the encoder to encode data into +/- 1s that more closely match the quantum computer's +/- 1s. NLL and MMD alternate to make the output of the quantum computer and the encoder as similar as possible.", + "arrow-right-pointer-events", + ), + generate_tooltip( + "Latent Representation", + "The encoded latent representation of the image. The number of +/- 1s is determined by the size of the latent space that was selected during training.", + "latent-vector-diagram", + ), + generate_tooltip( + "Decoding", + "Each collection of 4 pixels represents a feature of the output image.", + "step-4-decode-img", + ), + generate_tooltip( + "Output Image", + "The image decoded from the latent +/- 1s. The quality of the image can be impacted by the number of epochs, the size of the latent space, the batch size, and the QPU used.", + "step-5-output-img", ), - html.Div([ - html.Div(className="forward-arrow"), - html.Img(src=STEP_4_FILE, id="step-4-decode-img"), - ], className="graph-model-itermediate-step"), - html.Img(src=STEP_5_FILE_DEFAULT, id="step-5-output-img"), ], - className="graph-model-wrapper" - ), - generate_tooltip( - "Input Image", - "An input image from the MNIST dataset.", - "step-1-input-img", - ), - generate_tooltip( - "Encoding", - "Each collection of 4 pixels represents a feature of the input image.", - "step-2-encode-img", - ), - generate_tooltip( - "Quantum Computer Sample", - "The quantum computer is sampled to obtain a new list of +/- 1s. These +/- 1s can be decoded to create a new never before seen image.", - "qpu-graph-wrapper", - ), - generate_tooltip( - "Mapping of Latent +/- 1s onto the Quantum Computer", - "Each +/- 1 of the latent representation is mapped to a qubit on the quantum computer. This allows for a comparison between the quantum computer and the latent representation.", - "encoded-graph-wrapper", - ), - generate_tooltip( - "Negative Log-Likelihood (NLL)", - "NLL is a function that trains the quantum computer by comparing the quantum computer samples to the encoded images. This helps the quantum computer generate new +/- 1s that more accurately describe the encoded image.", - "arrow-left-pointer-events", - ), - generate_tooltip( - "Max Mean Discrepancy (MMD)", - "MMD is a function that trains the encoder to encode data into +/- 1s that more closely match the quantum computer's +/- 1s. NLL and MMD alternate to make the output of the quantum computer and the encoder as similar as possible.", - "arrow-right-pointer-events", - ), - generate_tooltip( - "Latent Representation", - "The encoded latent representation of the image. The number of +/- 1s is determined by the size of the latent space that was selected during training.", - "latent-vector-diagram", - ), - generate_tooltip( - "Decoding", - "Each collection of 4 pixels represents a feature of the output image.", - "step-4-decode-img", - ), - generate_tooltip( - "Output Image", - "The image decoded from the latent +/- 1s. The quality of the image can be impacted by the number of epochs, the size of the latent space, the batch size, and the QPU used.", - "step-5-output-img", - ), + ) ], ), - dcc.Tab( - label="Generated Images", - id="results-tab", - className="tab", - disabled=True, + dmc.TabsPanel( + value="results-tab", + tabIndex="13", children=[ html.Div( - className="tab-content-results", + className="tab-content-wrapper", children=[ html.Div( className="graph-wrapper-flex", children=[ html.Div( [ - html.H4("Generated"), + html.H3("Generated"), html.Div( dcc.Graph( id="fig-output", @@ -685,7 +835,7 @@ def create_interface(): ), html.Div( [ - html.H4( + html.H3( "Reconstructed Comparison" ), html.Div( @@ -707,19 +857,17 @@ def create_interface(): ) ], ), - dcc.Tab( - label="Loss Graphs", - id="loss-tab", - className="tab", - disabled=True, + dmc.TabsPanel( + value="loss-tab", + tabIndex="13", children=[ html.Div( - className="tab-content-results", + className="tab-content-wrapper", children=[ html.Div( className="graph-wrapper", children=[ - html.H4( + html.H3( "Mean Squared Error Loss (MSE)" ), html.Div( @@ -732,7 +880,7 @@ def create_interface(): ), className="graph", ), - html.H4("Total Loss (MSE + MMD)"), + html.H3("Total Loss (MSE + MMD)"), html.Div( dcc.Graph( id="fig-total-loss", diff --git a/requirements.txt b/requirements.txt index cd4504d..1980445 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -dash[diskcache]~=3.2 -dash-bootstrap-components~=2.0 -dwave-ocean-sdk~=9.0 +dash[diskcache]~=4.3 +dash-mantine-components~=2.5 +dwave-ocean-sdk~=9.4 dwave-pytorch-plugin~=0.3 einops~=0.8 matplotlib~=3.10 diff --git a/src/model_wrapper.py b/src/model_wrapper.py index 19596a1..a440f8f 100755 --- a/src/model_wrapper.py +++ b/src/model_wrapper.py @@ -420,19 +420,28 @@ def generate_loss_plot( fig_mse = go.Figure() fig_total = go.Figure() - fig_mse.add_trace(go.Scatter(x=list(range(len(mse_losses))), y=mse_losses)) - fig_total.add_trace(go.Scatter(x=list(range(len(mse_losses))), y=dvae_losses)) - - # Update xaxis properties - fig_mse.update_xaxes(title_text="Batch") - fig_mse.update_yaxes(title_text="Loss") + fig_mse.add_trace( + go.Scatter(x=list(range(len(mse_losses))), y=mse_losses, line=dict(color="#2A7DE1")) + ) + fig_total.add_trace( + go.Scatter(x=list(range(len(mse_losses))), y=dvae_losses, line=dict(color="#2A7DE1")) + ) - # Update yaxis properties - fig_total.update_xaxes(title_text="Batch") - fig_total.update_yaxes(title_text="Loss") + dark_axis = dict( + gridcolor="rgba(255, 255, 255, 0.15)", + zerolinecolor="rgba(255, 255, 255, 0.3)", + title_font=dict(size=16), + ) - fig_mse.update_layout(margin={"t": 0, "l": 0, "b": 0, "r": 0}) - fig_total.update_layout(margin={"t": 0, "l": 0, "b": 0, "r": 0}) + for fig in (fig_mse, fig_total): + fig.update_xaxes(title_text="Batch", **dark_axis) + fig.update_yaxes(title_text="Loss", **dark_axis) + fig.update_layout( + margin={"t": 0, "l": 0, "b": 0, "r": 0}, + paper_bgcolor="rgba(0, 0, 0, 0)", + plot_bgcolor="rgba(0, 0, 0, 0)", + font=dict(color="#f5f7fb"), + ) if save_to_file_mse: with open(save_to_file_mse, "w") as f: @@ -444,7 +453,7 @@ def generate_loss_plot( return fig_mse, fig_total - def generate_reconstucted_samples( + def generate_reconstructed_samples( self, sharpen: bool = False, save_to_file: str = "" ) -> go.Figure: """Generate reconstructed images from training data. diff --git a/src/utils/callback_helpers.py b/src/utils/callback_helpers.py index 5686cc0..a246691 100644 --- a/src/utils/callback_helpers.py +++ b/src/utils/callback_helpers.py @@ -19,17 +19,17 @@ import time from pathlib import Path from typing import Optional -import torch import networkx as nx -import dwave_networkx as dnx -from dwave.system import DWaveSampler +import torch +from dwave.graphs import drawing, chimera_graph, pegasus_graph, zephyr_graph from dwave.plugins.torch.models import DiscreteVariationalAutoencoder -from src.model_wrapper import get_dataset +from dwave.system import DWaveSampler from plotly import graph_objects as go from torchvision.utils import save_image -from demo_configs import GENERATE_NEW_MODEL_DIAGRAM, GRAPH_COLORS, SHARPEN_OUTPUT, THEME_COLOR_SECONDARY +from demo_configs import GENERATE_NEW_MODEL_DIAGRAM, GRAPH_COLORS, SHARPEN_OUTPUT +from src.model_wrapper import get_dataset from src.utils.common import get_graph_mapping, greedy_get_subgraph MODEL_PATH = Path("models") @@ -39,7 +39,7 @@ IMAGE_RECON_FILE_PREFIX = "reconstructed_epoch_" LOSS_PREFIX = "loss_" -MODEL_DIAGRAM_PATH = "assets/model_diagram/" +MODEL_DIAGRAM_PATH = "static/model_diagram/" LATENT_ENCODED_FILE = MODEL_DIAGRAM_PATH + "latent_encoded.json" LATENT_QPU_FILE = MODEL_DIAGRAM_PATH + "latent_qpu.json" STEP_1_FILE = MODEL_DIAGRAM_PATH + "step_1_input.png" @@ -67,6 +67,7 @@ def get_example_image(index: int = 0) -> torch.Tensor: return example_image + def create_model_files( model: DiscreteVariationalAutoencoder, file_name: str, @@ -125,17 +126,9 @@ def generate_model_diagram(model: DiscreteVariationalAutoencoder, example_image: json.dump(discretes[0, 0].tolist(), f) step_4 = model._dvae.decoder.merge_batch_dim_and_replica_dim( - model._dvae.decoder.make_2x2_images( - model._dvae.decoder.increase_latent_dim(discretes) - ) - ) - save_image( - step_4[0].unsqueeze(1), - STEP_4_FILE, - normalize=True, - scale_each=True, - padding=1 + model._dvae.decoder.make_2x2_images(model._dvae.decoder.increase_latent_dim(discretes)) ) + save_image(step_4[0].unsqueeze(1), STEP_4_FILE, normalize=True, scale_each=True, padding=1) step_5 = model._dvae.decoder(discretes) save_image(step_5[0], STEP_5_FILE) @@ -208,7 +201,7 @@ def execute_training( sharpen=SHARPEN_OUTPUT, save_to_file=f"{JSON_FILE_DIR}/{IMAGE_GEN_FILE_PREFIX}{epoch+1}.json", ) - fig_reconstructed = model.generate_reconstucted_samples( + fig_reconstructed = model.generate_reconstructed_samples( sharpen=SHARPEN_OUTPUT, save_to_file=f"{JSON_FILE_DIR}/{IMAGE_RECON_FILE_PREFIX}{epoch+1}.json", ) @@ -279,7 +272,7 @@ def get_node_trace( except Exception: # Expected when QPU or latents setting is updated print( "Accurate latent color mapping not available for the requested graph nodes.", - "Generating random data." + "Generating random data.", ) random.seed(10) rand_nodes = [random.randint(0, 1) for _ in G.nodes()] @@ -303,7 +296,13 @@ def get_node_trace( return node_trace -def get_fig(G: nx.Graph, node_coords: dict[int, tuple], mapping: dict[int, int], file_name: str, show_edges: bool=True) -> go.Figure: +def get_fig( + G: nx.Graph, + node_coords: dict[int, tuple], + mapping: dict[int, int], + file_name: str, + show_edges: bool = True, +) -> go.Figure: """Generate a Plotly fig of a graph with highlighted subgraph. Args: @@ -319,7 +318,7 @@ def get_fig(G: nx.Graph, node_coords: dict[int, tuple], mapping: dict[int, int], data = [] if show_edges: - edge_trace = get_edge_trace(G, node_coords, THEME_COLOR_SECONDARY, 0.3) + edge_trace = get_edge_trace(G, node_coords, "#2A7DE1", 0.3) data.append(edge_trace) node_trace = get_node_trace(G, node_coords, mapping, file_name) @@ -367,11 +366,13 @@ def generate_model_fig( qpu_topology = qpu.properties["topology"]["type"] if qpu_topology == "pegasus": - node_coords = dnx.drawing.pegasus_layout(dnx.pegasus_graph(qpu_shape), crosses=True) + node_coords = drawing.pegasus_layout( + pegasus_graph(qpu_shape), crosses=True + ) elif qpu_topology == "zephyr": - node_coords = dnx.drawing.zephyr_layout(dnx.zephyr_graph(qpu_shape)) + node_coords = drawing.zephyr_layout(zephyr_graph(qpu_shape)) elif qpu_topology == "chimera": - node_coords = dnx.drawing.chimera_layout(dnx.chimera_graph(qpu_shape)) + node_coords = drawing.chimera_layout(chimera_graph(qpu_shape)) else: raise ValueError(f"Unknown QPU topology: {qpu_topology}") diff --git a/static/demo.png b/static/demo.png index e97ff22..57de4f8 100644 Binary files a/static/demo.png and b/static/demo.png differ diff --git a/static/dwave_logo.svg b/static/dwave_logo.svg index ff46dbd..ba7ed8f 100644 --- a/static/dwave_logo.svg +++ b/static/dwave_logo.svg @@ -1 +1,32 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/model_diagram/step_5_output_default.png b/static/model_diagram/step_5_output_default.png similarity index 100% rename from assets/model_diagram/step_5_output_default.png rename to static/model_diagram/step_5_output_default.png