PyMieSimX 0.1.4__tar.gz → 0.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of PyMieSimX might be problematic. Click here for more details.
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.github/dependabot.yml +1 -2
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.github/workflows/deploy_anaconda.yml +2 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.github/workflows/quality.yml +9 -9
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.github/workflows/tests.yml +4 -3
- pymiesimx-0.1.6/AGENTS.md +81 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PKG-INFO +25 -2
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/dashboard.css +81 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/callbacks.py +12 -1
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/computation.py +37 -6
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/home.py +39 -5
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/usage_metrics.py +6 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX.egg-info/PKG-INFO +25 -2
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX.egg-info/SOURCES.txt +2 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX.egg-info/scm_file_list.json +2 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX.egg-info/scm_version.json +2 -2
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/README.rst +24 -1
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/conda.recipe/meta.yaml +3 -1
- pymiesimx-0.1.6/tests/browser/test_dashboard.py +132 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/tests/gui/test_dash_callbacks.py +66 -0
- pymiesimx-0.1.6/tests/gui/test_home.py +41 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/tests/gui/test_services.py +3 -1
- pymiesimx-0.1.4/tests/browser/test_dashboard.py +0 -50
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.coveragerc +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.flake8 +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.github/workflows/deploy_PyPi.yml +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.github/workflows/deploy_coverage.yml +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.gitignore +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/.pre-commit-config.yaml +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/CONTRIBUTING.md +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/Dockerfile +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/LICENSE +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/__init__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/__main__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/api.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/application/__init__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/application/wsgi.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/__init__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/admin.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/card-state.js +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/default-settings.json +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/home-spf-radial.gif +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/home-spf-radial.png +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/logo.png +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/materials-stock.json +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/pymiesim-favicon.svg +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/pymiesim-logo-dark.svg +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/assets/pymiesim-logo.svg +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/callback_helpers.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/components/__init__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/components/cards.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/defaults.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/experiment_service.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/interface.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/jobs.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/layout.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/material_catalog.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/models.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/__init__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/citation.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/documentation.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/experiment/__init__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/experiment/page.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/experiment/sections/__init__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/experiment/sections/configuration.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/field_syntax.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/install_local.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/sellmeier.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/settings.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/single/__init__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/single/page.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/single/sections/__init__.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/single/sections/representations.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/pages/single/sections/setup.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/parsing.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/plotting.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/schemas.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/services.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/single_service.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX/gui/validation.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX.egg-info/dependency_links.txt +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX.egg-info/entry_points.txt +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX.egg-info/requires.txt +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/PyMieSimX.egg-info/top_level.txt +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/makefile +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/pyproject.toml +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/pytest.ini +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/setup.cfg +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/tests/gui/cli.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/tests/gui/test_admin.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/tests/gui/test_api.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/tests/gui/test_usage_metrics.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/tools/generate_home_spf.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/tools/next_release_version.py +0 -0
- {pymiesimx-0.1.4 → pymiesimx-0.1.6}/tools/release_tag.py +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
name: Publish Conda package
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
+
workflow_dispatch:
|
|
4
5
|
push:
|
|
5
6
|
tags: ["v*"]
|
|
6
7
|
|
|
@@ -16,5 +17,6 @@ jobs:
|
|
|
16
17
|
package_name: pymiesimx
|
|
17
18
|
recipe_path: conda.recipe
|
|
18
19
|
test_import: PyMieSimX
|
|
20
|
+
test_command: "python -c 'import PyMieSimX' && pymiesimx --help"
|
|
19
21
|
secrets:
|
|
20
22
|
ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }}
|
|
@@ -16,12 +16,12 @@ jobs:
|
|
|
16
16
|
matrix:
|
|
17
17
|
python-version: ["3.11", "3.13"]
|
|
18
18
|
steps:
|
|
19
|
-
-
|
|
20
|
-
|
|
19
|
+
- name: Set up Python and CI dependencies
|
|
20
|
+
uses: MartinPdeS/MPSActions/actions/setup-python@0f5927acf88b25b5d253421e508387897096a2cc
|
|
21
21
|
with:
|
|
22
|
-
python-version: ${{ matrix.python-version }}
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
python-version: "${{ matrix.python-version }}"
|
|
23
|
+
install-command: >-
|
|
24
|
+
python -m pip install --upgrade pip && python -m pip install -e ".[testing,dev]"
|
|
25
25
|
- name: Initialize material catalog
|
|
26
26
|
run: python -m PyOptik setup --no-progress
|
|
27
27
|
- name: Ruff
|
|
@@ -34,12 +34,12 @@ jobs:
|
|
|
34
34
|
browser-tests:
|
|
35
35
|
runs-on: ubuntu-latest
|
|
36
36
|
steps:
|
|
37
|
-
-
|
|
38
|
-
|
|
37
|
+
- name: Set up Python and CI dependencies
|
|
38
|
+
uses: MartinPdeS/MPSActions/actions/setup-python@0f5927acf88b25b5d253421e508387897096a2cc
|
|
39
39
|
with:
|
|
40
40
|
python-version: "3.11"
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
install-command: >-
|
|
42
|
+
python -m pip install --upgrade pip && python -m pip install -e ".[testing,browser]"
|
|
43
43
|
- name: Initialize material catalog
|
|
44
44
|
run: python -m PyOptik setup --no-progress
|
|
45
45
|
- name: Browser workflow tests
|
|
@@ -13,10 +13,11 @@ jobs:
|
|
|
13
13
|
tests:
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
|
-
-
|
|
17
|
-
|
|
16
|
+
- name: Set up Python and CI dependencies
|
|
17
|
+
uses: MartinPdeS/MPSActions/actions/setup-python@0f5927acf88b25b5d253421e508387897096a2cc
|
|
18
18
|
with:
|
|
19
19
|
python-version: "3.11"
|
|
20
|
-
|
|
20
|
+
install-command: >-
|
|
21
|
+
python -m pip install --upgrade pip && python -m pip install -e ".[testing]"
|
|
21
22
|
- run: python -m PyOptik setup --no-progress
|
|
22
23
|
- run: python -m pytest -q
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Working on PyMieSimX
|
|
2
|
+
|
|
3
|
+
PyMieSimX provides a Dash GUI and Python API for PyMieSim. The hosted service is
|
|
4
|
+
https://pymiesim.onrender.com/. Keep browser use, local installation, and the
|
|
5
|
+
Python API working together.
|
|
6
|
+
|
|
7
|
+
## Repository map
|
|
8
|
+
|
|
9
|
+
- `PyMieSimX/api.py` exposes the computational API. Preserve compatibility for
|
|
10
|
+
single-measure callers when extending sweep results.
|
|
11
|
+
- `PyMieSimX/gui/computation.py`, `parsing.py`, `validation.py`, and `schemas.py`
|
|
12
|
+
handle scientific inputs, calculations, result serialization, and plotting.
|
|
13
|
+
Delegate scientific solvers to PyMieSim and optical materials to PyOptik.
|
|
14
|
+
- `PyMieSimX/gui/jobs.py` manages bounded background sweep jobs;
|
|
15
|
+
`callbacks.py` connects job state, validation, plotting, and exports to Dash.
|
|
16
|
+
- `PyMieSimX/gui/pages/`, `components/`, and `layout.py` compose the interface.
|
|
17
|
+
CSS and browser assets live in `PyMieSimX/gui/assets/`.
|
|
18
|
+
- `PyMieSimX/application/wsgi.py` is the hosted-server entry point;
|
|
19
|
+
`PyMieSimX/__main__.py` provides the `pymiesimx` launcher.
|
|
20
|
+
- `tests/gui/` covers Python behavior and Dash callbacks. `tests/browser/`
|
|
21
|
+
exercises the real browser workflow separately.
|
|
22
|
+
|
|
23
|
+
## Setup and checks
|
|
24
|
+
|
|
25
|
+
Use Python 3.11 or newer and a virtual environment. Reuse an existing suitable
|
|
26
|
+
environment when available; do not modify sibling repositories to set up this
|
|
27
|
+
one.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
python -m pip install -e ".[testing,dev,browser]"
|
|
31
|
+
python -m PyOptik setup --no-progress
|
|
32
|
+
make quality PYTHON=python
|
|
33
|
+
python -m pytest
|
|
34
|
+
python -m pytest tests/browser --config-file=pytest.ini --headless --cov=PyMieSimX --cov-report=term --cov-fail-under=0
|
|
35
|
+
git diff --check
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Browser tests need Chrome and a compatible ChromeDriver. The default pytest
|
|
39
|
+
suite runs `tests/gui` and enforces the coverage threshold in `.coveragerc`;
|
|
40
|
+
the separate browser command intentionally disables that threshold. Use a
|
|
41
|
+
writable `MPLCONFIGDIR` when the default Matplotlib cache is unavailable.
|
|
42
|
+
|
|
43
|
+
Run checks relevant to the change. For UI interaction changes, run the browser
|
|
44
|
+
suite as well as affected callback tests. Wait for observable UI state and CSS
|
|
45
|
+
transitions; avoid fixed sleeps, JavaScript clicks that bypass actual hit
|
|
46
|
+
testing, or removing assertions to make tests pass. Use explicit viewport sizes
|
|
47
|
+
when testing desktop and responsive layouts.
|
|
48
|
+
|
|
49
|
+
## Implementation conventions
|
|
50
|
+
|
|
51
|
+
- Use four spaces and follow the existing Python style. Keep Ruff and the
|
|
52
|
+
scoped Mypy checks in `make quality` passing.
|
|
53
|
+
- Keep parsing and scientific computation separate from Dash rendering so the
|
|
54
|
+
public Python API remains usable without constructing a GUI.
|
|
55
|
+
- Preserve physical units, dataframe index information, and all selected
|
|
56
|
+
measures through serialization, plotting, and CSV export.
|
|
57
|
+
- Validate sweep and result-size limits before expensive work where possible.
|
|
58
|
+
Keep queued/running/completed/error states visible and make Run recover after
|
|
59
|
+
validation failures, missing jobs, cancellation, and computation errors.
|
|
60
|
+
- Keep usage metrics optional for local runs and protect the administration
|
|
61
|
+
page with its server-side token. Never commit database URLs or secrets.
|
|
62
|
+
- Add regression coverage for behavior changes; use asymmetric data when
|
|
63
|
+
checking axis orientation and multiple measures when checking sweep output.
|
|
64
|
+
|
|
65
|
+
## Documentation, packaging, and CI
|
|
66
|
+
|
|
67
|
+
- `README.rst` uses reStructuredText. Check new sections and links render
|
|
68
|
+
correctly, including heading underline lengths.
|
|
69
|
+
- Dependencies and the console entry point are declared in `pyproject.toml`.
|
|
70
|
+
Keep `conda.recipe/meta.yaml` consistent with them. Conda builds are
|
|
71
|
+
platform-specific for Python 3.11–3.13 on Linux, macOS, and Windows; retain
|
|
72
|
+
the Windows dependency selectors and explicit `pymiesimx` entry point.
|
|
73
|
+
- The Conda workflow supports manual build verification. Publishing to Conda
|
|
74
|
+
and PyPI occurs on version-tag pushes. Verify the installed Conda artifact,
|
|
75
|
+
including both import and CLI startup, rather than only the checkout.
|
|
76
|
+
- Versions come from Git tags through setuptools-scm. Use the existing release
|
|
77
|
+
tools/Makefile when a release is requested; a normal commit/push does not
|
|
78
|
+
imply creating a release tag.
|
|
79
|
+
- Inspect failing Actions job logs and fix the underlying cause. After pushing
|
|
80
|
+
a CI fix, check the run for the new commit and report any remaining failures.
|
|
81
|
+
- Preserve unrelated working-tree changes and commit only the requested work.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyMieSimX
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Graphical Dash interface for PyMieSim.
|
|
5
5
|
Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -36,6 +36,20 @@ PyMieSimX is the standalone graphical interface for `PyMieSim
|
|
|
36
36
|
configuring optical setups, running parameter sweeps, exploring individual
|
|
37
37
|
particles, and exporting results.
|
|
38
38
|
|
|
39
|
+
Try the live webapp
|
|
40
|
+
----------------------
|
|
41
|
+
|
|
42
|
+
**Run Mie scattering simulations in your browser—no installation required.**
|
|
43
|
+
|
|
44
|
+
.. image:: https://img.shields.io/badge/Launch_Webapp-Open_in_browser-2672d6?style=for-the-badge
|
|
45
|
+
:target: https://pymiesim.onrender.com/
|
|
46
|
+
:alt: Launch the PyMieSimX webapp in your browser
|
|
47
|
+
|
|
48
|
+
`Launch the PyMieSimX webapp <https://pymiesim.onrender.com/>`_ to configure
|
|
49
|
+
optical setups, run parameter sweeps, explore individual particles, and download
|
|
50
|
+
your results as CSV files. The hosted service provides direct access to the
|
|
51
|
+
graphical interface; local installation is also available below.
|
|
52
|
+
|
|
39
53
|
Source-model note
|
|
40
54
|
-----------------
|
|
41
55
|
|
|
@@ -157,10 +171,19 @@ PyMieSimX writes namespaced counters to the shared ``metrics_counters`` table:
|
|
|
157
171
|
backend is explicitly configured and available. Local installations do not
|
|
158
172
|
persist usage counters.
|
|
159
173
|
|
|
174
|
+
To show a small **Webapp visits** box in the homepage's top card, set this
|
|
175
|
+
environment variable on the Render service and redeploy::
|
|
176
|
+
|
|
177
|
+
SHOW_METRIC=true
|
|
178
|
+
|
|
179
|
+
The box is hidden by default and requires the PostgreSQL configuration above.
|
|
180
|
+
It shows the total home-page visit count, including repeat visits, rather than
|
|
181
|
+
unique visitors. If the database is unavailable, the box stays hidden.
|
|
182
|
+
|
|
160
183
|
Administration page
|
|
161
184
|
-------------------
|
|
162
185
|
|
|
163
|
-
|
|
186
|
+
Detailed usage counters are shown on the hidden, token-protected administration
|
|
164
187
|
page. Configure a server-side token with::
|
|
165
188
|
|
|
166
189
|
PYMIESIMX_ADMIN_TOKEN=<a long random value>
|
|
@@ -3865,6 +3865,87 @@ html .single-workspace .representation-card {
|
|
|
3865
3865
|
flex-basis: auto;
|
|
3866
3866
|
}
|
|
3867
3867
|
}
|
|
3868
|
+
.home-page-hero {
|
|
3869
|
+
display: flex;
|
|
3870
|
+
align-items: center;
|
|
3871
|
+
justify-content: space-between;
|
|
3872
|
+
gap: 24px;
|
|
3873
|
+
flex-wrap: wrap;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
.home-hero-copy {
|
|
3877
|
+
flex: 1 1 360px;
|
|
3878
|
+
min-width: 0;
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
.home-hero-actions {
|
|
3882
|
+
display: flex;
|
|
3883
|
+
align-items: center;
|
|
3884
|
+
justify-content: flex-end;
|
|
3885
|
+
flex-wrap: wrap;
|
|
3886
|
+
gap: 14px;
|
|
3887
|
+
margin-left: auto;
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
.home-visit-metric {
|
|
3891
|
+
display: flex;
|
|
3892
|
+
flex-direction: column;
|
|
3893
|
+
align-items: center;
|
|
3894
|
+
gap: 3px;
|
|
3895
|
+
padding: 9px 16px;
|
|
3896
|
+
border: 1px solid #cddcec;
|
|
3897
|
+
border-radius: 9px;
|
|
3898
|
+
background: #f3f7fc;
|
|
3899
|
+
color: #2f4358;
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
.home-visit-label {
|
|
3903
|
+
font-size: .72rem;
|
|
3904
|
+
font-weight: 600;
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
.home-visit-value {
|
|
3908
|
+
font-size: 1.2rem;
|
|
3909
|
+
line-height: 1.2;
|
|
3910
|
+
font-variant-numeric: tabular-nums;
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
.home-support-button {
|
|
3914
|
+
display: inline-flex;
|
|
3915
|
+
align-items: center;
|
|
3916
|
+
gap: 9px;
|
|
3917
|
+
flex: 0 0 auto;
|
|
3918
|
+
margin-left: auto;
|
|
3919
|
+
padding: 10px 17px;
|
|
3920
|
+
border: 1px solid #e8c56b;
|
|
3921
|
+
border-radius: 9px;
|
|
3922
|
+
background: linear-gradient(135deg, #fff8e3, #ffe8a9);
|
|
3923
|
+
color: #704912;
|
|
3924
|
+
font-size: .92rem;
|
|
3925
|
+
font-weight: 700;
|
|
3926
|
+
text-decoration: none;
|
|
3927
|
+
white-space: nowrap;
|
|
3928
|
+
box-shadow: 0 3px 9px rgba(151, 104, 23, .10);
|
|
3929
|
+
transition: background 150ms ease, box-shadow 150ms ease;
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
.home-support-button:hover {
|
|
3933
|
+
background: #ffe49a;
|
|
3934
|
+
color: #58350b;
|
|
3935
|
+
text-decoration: none;
|
|
3936
|
+
box-shadow: 0 4px 12px rgba(151, 104, 23, .18);
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
.home-support-button:focus-visible {
|
|
3940
|
+
outline: 3px solid #2672d6;
|
|
3941
|
+
outline-offset: 3px;
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
.home-support-icon {
|
|
3945
|
+
color: #ad6515;
|
|
3946
|
+
font-size: 1.1rem;
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3868
3949
|
.admin-page {
|
|
3869
3950
|
max-width: 1180px;
|
|
3870
3951
|
margin: 0 auto;
|
|
@@ -213,7 +213,7 @@ def register_callbacks(app: Dash, default_measure_options: list[str]) -> None:
|
|
|
213
213
|
active["experiment"] += " active"
|
|
214
214
|
return build_page_with_footer(build_experiment_page(default_measure_options, plot_settings or {})), *(active[key] for key in ("home", "experiment", "single", "documentation", "settings")), home_visits
|
|
215
215
|
active["home"] += " active"
|
|
216
|
-
return build_page_with_footer(build_home_page()), *(active[key] for key in ("home", "experiment", "single", "documentation", "settings")), home_visits
|
|
216
|
+
return build_page_with_footer(build_home_page(home_visits=home_visits)), *(active[key] for key in ("home", "experiment", "single", "documentation", "settings")), home_visits
|
|
217
217
|
|
|
218
218
|
@app.callback(
|
|
219
219
|
Output("admin-home-page-visits", "children"),
|
|
@@ -690,6 +690,10 @@ def register_callbacks(app: Dash, default_measure_options: list[str]) -> None:
|
|
|
690
690
|
State("single-include-incident-field", "value"),
|
|
691
691
|
State("single-run-count", "data"),
|
|
692
692
|
prevent_initial_call=True,
|
|
693
|
+
running=[
|
|
694
|
+
(Output("single-graph-loading", "display"), "show", "auto"),
|
|
695
|
+
(Output("run-single-button", "disabled"), True, False),
|
|
696
|
+
],
|
|
693
697
|
)
|
|
694
698
|
def _run_single(
|
|
695
699
|
_run_clicks: int,
|
|
@@ -732,6 +736,13 @@ def register_callbacks(app: Dash, default_measure_options: list[str]) -> None:
|
|
|
732
736
|
LOGGER.exception("Failed to record PyMieSimX particle-explorer metric.")
|
|
733
737
|
return execution.result, execution.run_count
|
|
734
738
|
|
|
739
|
+
@app.callback(
|
|
740
|
+
Output("result-graph-loading", "display"),
|
|
741
|
+
Input("run-experiment-button", "disabled"),
|
|
742
|
+
)
|
|
743
|
+
def _show_experiment_computing(run_disabled: bool):
|
|
744
|
+
return "show" if run_disabled else "auto"
|
|
745
|
+
|
|
735
746
|
@app.callback(
|
|
736
747
|
Output("single-representation", "options"),
|
|
737
748
|
Output("single-representation", "value"),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
5
|
import json
|
|
6
|
+
from base64 import b64decode
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
from math import prod
|
|
8
9
|
from typing import Any, Dict
|
|
@@ -336,7 +337,8 @@ def build_single_figure(
|
|
|
336
337
|
colorscale = PYMIESIM_BLUE_BLACK_RED
|
|
337
338
|
x = np.asarray(nearfields.u.to(ureg.nanometer).magnitude, dtype=float)
|
|
338
339
|
y = np.asarray(nearfields.v.to(ureg.nanometer).magnitude, dtype=float)
|
|
339
|
-
|
|
340
|
+
# PyMieSim indexes fields as (u, v); Plotly indexes z as (y, x).
|
|
341
|
+
heatmap_kwargs = {"x": x, "y": y, "z": values.T, "colorscale": colorscale, "colorbar": {"title": "V/m"}}
|
|
340
342
|
if not is_absolute:
|
|
341
343
|
maximum = float(np.max(np.abs(values)))
|
|
342
344
|
maximum = maximum if maximum > 0 else 1.0
|
|
@@ -732,6 +734,16 @@ def export_result_to_csv(result: dict[str, Any] | None) -> str:
|
|
|
732
734
|
return frame.to_csv(index=False)
|
|
733
735
|
|
|
734
736
|
|
|
737
|
+
def _plotly_array(value: Any) -> np.ndarray:
|
|
738
|
+
"""Read plain arrays and Plotly 6's base64 typed-array payloads."""
|
|
739
|
+
if isinstance(value, dict) and "bdata" in value:
|
|
740
|
+
array = np.frombuffer(b64decode(value["bdata"]), dtype=value["dtype"])
|
|
741
|
+
if "shape" in value:
|
|
742
|
+
array = array.reshape(tuple(int(size) for size in value["shape"].split(",")))
|
|
743
|
+
return array
|
|
744
|
+
return np.asarray(value)
|
|
745
|
+
|
|
746
|
+
|
|
735
747
|
def export_single_result_to_csv(result: dict[str, Any] | None) -> str:
|
|
736
748
|
"""Serialize a Particle Explorer figure payload to CSV text."""
|
|
737
749
|
if not result or not result.get("figure"):
|
|
@@ -742,17 +754,36 @@ def export_single_result_to_csv(result: dict[str, Any] | None) -> str:
|
|
|
742
754
|
rows: list[dict[str, Any]] = []
|
|
743
755
|
for trace in traces:
|
|
744
756
|
name = trace.get("name", "value")
|
|
757
|
+
if trace.get("type") == "scatterpolar":
|
|
758
|
+
theta_values = _plotly_array(trace.get("theta", []))
|
|
759
|
+
r_values = _plotly_array(trace.get("r", []))
|
|
760
|
+
rows.extend({"series": name, "theta": theta, "r": radius} for theta, radius in zip(theta_values, r_values))
|
|
761
|
+
continue
|
|
762
|
+
|
|
745
763
|
if "z" in trace:
|
|
746
|
-
z_values =
|
|
747
|
-
x_values = trace
|
|
748
|
-
y_values = trace
|
|
764
|
+
z_values = _plotly_array(trace["z"])
|
|
765
|
+
x_values = _plotly_array(trace["x"]) if "x" in trace else np.arange(z_values.shape[1])
|
|
766
|
+
y_values = _plotly_array(trace["y"]) if "y" in trace else np.arange(z_values.shape[0])
|
|
767
|
+
if trace.get("type") == "surface":
|
|
768
|
+
# Surface coordinates may be full grids; z is geometry, while
|
|
769
|
+
# surfacecolor carries the computed optical field/intensity.
|
|
770
|
+
values = _plotly_array(trace.get("surfacecolor", trace["z"]))
|
|
771
|
+
for y_index, x_index in np.ndindex(z_values.shape):
|
|
772
|
+
rows.append({
|
|
773
|
+
"series": name,
|
|
774
|
+
"x": x_values[y_index, x_index] if x_values.ndim == 2 else x_values[x_index],
|
|
775
|
+
"y": y_values[y_index, x_index] if y_values.ndim == 2 else y_values[y_index],
|
|
776
|
+
"z": z_values[y_index, x_index],
|
|
777
|
+
"value": values[y_index, x_index],
|
|
778
|
+
})
|
|
779
|
+
continue
|
|
749
780
|
for y_index, y_value in enumerate(y_values):
|
|
750
781
|
for x_index, x_value in enumerate(x_values):
|
|
751
782
|
rows.append({"series": name, "x": x_value, "y": y_value, "value": z_values[y_index, x_index]})
|
|
752
783
|
continue
|
|
753
784
|
|
|
754
|
-
y_values = trace.get("y", [])
|
|
755
|
-
x_values = trace
|
|
785
|
+
y_values = _plotly_array(trace.get("y", []))
|
|
786
|
+
x_values = _plotly_array(trace["x"]) if "x" in trace else np.arange(len(y_values))
|
|
756
787
|
rows.extend({"series": name, "x": x_value, "value": y_value} for x_value, y_value in zip(x_values, y_values))
|
|
757
788
|
|
|
758
789
|
return pd.DataFrame(rows).to_csv(index=False) if rows else ""
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"""Home page composition."""
|
|
2
2
|
|
|
3
3
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
from math import isfinite
|
|
4
5
|
|
|
5
6
|
from dash import dcc, html
|
|
6
7
|
import numpy as np
|
|
7
8
|
import plotly.graph_objects as go
|
|
8
9
|
|
|
9
10
|
from PyMieSimX.gui.components import Card
|
|
11
|
+
from PyMieSimX.gui.usage_metrics import show_public_metrics
|
|
10
12
|
|
|
11
|
-
def build_home_page():
|
|
13
|
+
def build_home_page(*, home_visits: int | float | None = None):
|
|
12
14
|
"""Build the landing page and its workflow cards."""
|
|
13
15
|
return html.Div(
|
|
14
16
|
className="page-content-stack",
|
|
@@ -23,10 +25,29 @@ def build_home_page():
|
|
|
23
25
|
html.Section(
|
|
24
26
|
className="page-hero home-page-hero",
|
|
25
27
|
children=[
|
|
26
|
-
html.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
html.Div(
|
|
29
|
+
[
|
|
30
|
+
html.H1("PyMieSim"),
|
|
31
|
+
html.P(
|
|
32
|
+
"An open-source library for fast and flexible far-field Mie scattering simulations.",
|
|
33
|
+
className="hero-text",
|
|
34
|
+
),
|
|
35
|
+
],
|
|
36
|
+
className="home-hero-copy",
|
|
37
|
+
),
|
|
38
|
+
html.Div(
|
|
39
|
+
[
|
|
40
|
+
_visit_metric(home_visits),
|
|
41
|
+
html.A(
|
|
42
|
+
[html.Span("\u2665", className="home-support-icon", **{"aria-hidden": "true"}), "Support Developer"],
|
|
43
|
+
id="home-support-developer",
|
|
44
|
+
href="https://github.com/sponsors/MartinPdeS",
|
|
45
|
+
target="_blank",
|
|
46
|
+
rel="noopener noreferrer",
|
|
47
|
+
className="home-support-button",
|
|
48
|
+
),
|
|
49
|
+
],
|
|
50
|
+
className="home-hero-actions",
|
|
30
51
|
),
|
|
31
52
|
],
|
|
32
53
|
),
|
|
@@ -58,6 +79,19 @@ def build_home_page():
|
|
|
58
79
|
],
|
|
59
80
|
)
|
|
60
81
|
|
|
82
|
+
def _visit_metric(home_visits: int | float | None):
|
|
83
|
+
"""Show an available server count without displaying local or failed metrics."""
|
|
84
|
+
if not show_public_metrics() or home_visits is None or not isfinite(home_visits) or home_visits < 0:
|
|
85
|
+
return None
|
|
86
|
+
return html.Div(
|
|
87
|
+
[
|
|
88
|
+
html.Span("Webapp visits", className="home-visit-label"),
|
|
89
|
+
html.Strong(f"{int(home_visits):,}", id="home-public-visit-count", className="home-visit-value"),
|
|
90
|
+
],
|
|
91
|
+
className="home-visit-metric",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
61
95
|
def _package_version() -> str:
|
|
62
96
|
try:
|
|
63
97
|
return version("PyMieSimX")
|
|
@@ -15,6 +15,7 @@ except Exception: # pragma: no cover - exercised through fallback behavior.
|
|
|
15
15
|
LOGGER = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
17
|
METRICS_BACKEND_ENV_VAR = "PYMIESIMX_USAGE_METRICS_BACKEND"
|
|
18
|
+
SHOW_METRIC_ENV_VAR = "SHOW_METRIC"
|
|
18
19
|
METRICS_DATABASE_URL_ENV_VAR = "PYMIESIMX_USAGE_METRICS_DATABASE_URL"
|
|
19
20
|
DATABASE_URL_ENV_VAR = "DATABASE_URL"
|
|
20
21
|
METRICS_TABLE = "metrics_counters"
|
|
@@ -48,6 +49,11 @@ class UsageMetrics:
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
def show_public_metrics() -> bool:
|
|
53
|
+
"""Expose the visit counter only when explicitly enabled for a hosted backend."""
|
|
54
|
+
return os.getenv(SHOW_METRIC_ENV_VAR, "false").strip().lower() in {"true", "1", "yes", "on"} and _use_postgres_backend()
|
|
55
|
+
|
|
56
|
+
|
|
51
57
|
def load_usage_metrics() -> UsageMetrics:
|
|
52
58
|
"""Load hosted counters, or return unavailable values when run locally."""
|
|
53
59
|
if _use_postgres_backend():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyMieSimX
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Graphical Dash interface for PyMieSim.
|
|
5
5
|
Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -36,6 +36,20 @@ PyMieSimX is the standalone graphical interface for `PyMieSim
|
|
|
36
36
|
configuring optical setups, running parameter sweeps, exploring individual
|
|
37
37
|
particles, and exporting results.
|
|
38
38
|
|
|
39
|
+
Try the live webapp
|
|
40
|
+
----------------------
|
|
41
|
+
|
|
42
|
+
**Run Mie scattering simulations in your browser—no installation required.**
|
|
43
|
+
|
|
44
|
+
.. image:: https://img.shields.io/badge/Launch_Webapp-Open_in_browser-2672d6?style=for-the-badge
|
|
45
|
+
:target: https://pymiesim.onrender.com/
|
|
46
|
+
:alt: Launch the PyMieSimX webapp in your browser
|
|
47
|
+
|
|
48
|
+
`Launch the PyMieSimX webapp <https://pymiesim.onrender.com/>`_ to configure
|
|
49
|
+
optical setups, run parameter sweeps, explore individual particles, and download
|
|
50
|
+
your results as CSV files. The hosted service provides direct access to the
|
|
51
|
+
graphical interface; local installation is also available below.
|
|
52
|
+
|
|
39
53
|
Source-model note
|
|
40
54
|
-----------------
|
|
41
55
|
|
|
@@ -157,10 +171,19 @@ PyMieSimX writes namespaced counters to the shared ``metrics_counters`` table:
|
|
|
157
171
|
backend is explicitly configured and available. Local installations do not
|
|
158
172
|
persist usage counters.
|
|
159
173
|
|
|
174
|
+
To show a small **Webapp visits** box in the homepage's top card, set this
|
|
175
|
+
environment variable on the Render service and redeploy::
|
|
176
|
+
|
|
177
|
+
SHOW_METRIC=true
|
|
178
|
+
|
|
179
|
+
The box is hidden by default and requires the PostgreSQL configuration above.
|
|
180
|
+
It shows the total home-page visit count, including repeat visits, rather than
|
|
181
|
+
unique visitors. If the database is unavailable, the box stays hidden.
|
|
182
|
+
|
|
160
183
|
Administration page
|
|
161
184
|
-------------------
|
|
162
185
|
|
|
163
|
-
|
|
186
|
+
Detailed usage counters are shown on the hidden, token-protected administration
|
|
164
187
|
page. Configure a server-side token with::
|
|
165
188
|
|
|
166
189
|
PYMIESIMX_ADMIN_TOKEN=<a long random value>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
.flake8
|
|
3
3
|
.gitignore
|
|
4
4
|
.pre-commit-config.yaml
|
|
5
|
+
AGENTS.md
|
|
5
6
|
CONTRIBUTING.md
|
|
6
7
|
Dockerfile
|
|
7
8
|
LICENSE
|
|
@@ -82,6 +83,7 @@ tests/gui/cli.py
|
|
|
82
83
|
tests/gui/test_admin.py
|
|
83
84
|
tests/gui/test_api.py
|
|
84
85
|
tests/gui/test_dash_callbacks.py
|
|
86
|
+
tests/gui/test_home.py
|
|
85
87
|
tests/gui/test_services.py
|
|
86
88
|
tests/gui/test_usage_metrics.py
|
|
87
89
|
tools/generate_home_spf.py
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
".github/workflows/tests.yml",
|
|
11
11
|
".gitignore",
|
|
12
12
|
".pre-commit-config.yaml",
|
|
13
|
+
"AGENTS.md",
|
|
13
14
|
"CONTRIBUTING.md",
|
|
14
15
|
"Dockerfile",
|
|
15
16
|
"LICENSE",
|
|
@@ -76,6 +77,7 @@
|
|
|
76
77
|
"tests/gui/test_admin.py",
|
|
77
78
|
"tests/gui/test_api.py",
|
|
78
79
|
"tests/gui/test_dash_callbacks.py",
|
|
80
|
+
"tests/gui/test_home.py",
|
|
79
81
|
"tests/gui/test_services.py",
|
|
80
82
|
"tests/gui/test_usage_metrics.py",
|
|
81
83
|
"tools/generate_home_spf.py",
|
|
@@ -6,6 +6,20 @@ PyMieSimX is the standalone graphical interface for `PyMieSim
|
|
|
6
6
|
configuring optical setups, running parameter sweeps, exploring individual
|
|
7
7
|
particles, and exporting results.
|
|
8
8
|
|
|
9
|
+
Try the live webapp
|
|
10
|
+
----------------------
|
|
11
|
+
|
|
12
|
+
**Run Mie scattering simulations in your browser—no installation required.**
|
|
13
|
+
|
|
14
|
+
.. image:: https://img.shields.io/badge/Launch_Webapp-Open_in_browser-2672d6?style=for-the-badge
|
|
15
|
+
:target: https://pymiesim.onrender.com/
|
|
16
|
+
:alt: Launch the PyMieSimX webapp in your browser
|
|
17
|
+
|
|
18
|
+
`Launch the PyMieSimX webapp <https://pymiesim.onrender.com/>`_ to configure
|
|
19
|
+
optical setups, run parameter sweeps, explore individual particles, and download
|
|
20
|
+
your results as CSV files. The hosted service provides direct access to the
|
|
21
|
+
graphical interface; local installation is also available below.
|
|
22
|
+
|
|
9
23
|
Source-model note
|
|
10
24
|
-----------------
|
|
11
25
|
|
|
@@ -127,10 +141,19 @@ PyMieSimX writes namespaced counters to the shared ``metrics_counters`` table:
|
|
|
127
141
|
backend is explicitly configured and available. Local installations do not
|
|
128
142
|
persist usage counters.
|
|
129
143
|
|
|
144
|
+
To show a small **Webapp visits** box in the homepage's top card, set this
|
|
145
|
+
environment variable on the Render service and redeploy::
|
|
146
|
+
|
|
147
|
+
SHOW_METRIC=true
|
|
148
|
+
|
|
149
|
+
The box is hidden by default and requires the PostgreSQL configuration above.
|
|
150
|
+
It shows the total home-page visit count, including repeat visits, rather than
|
|
151
|
+
unique visitors. If the database is unavailable, the box stays hidden.
|
|
152
|
+
|
|
130
153
|
Administration page
|
|
131
154
|
-------------------
|
|
132
155
|
|
|
133
|
-
|
|
156
|
+
Detailed usage counters are shown on the hidden, token-protected administration
|
|
134
157
|
page. Configure a server-side token with::
|
|
135
158
|
|
|
136
159
|
PYMIESIMX_ADMIN_TOKEN=<a long random value>
|
|
@@ -7,7 +7,8 @@ source:
|
|
|
7
7
|
|
|
8
8
|
build:
|
|
9
9
|
number: 0
|
|
10
|
-
|
|
10
|
+
entry_points:
|
|
11
|
+
- pymiesimx = PyMieSimX.__main__:main
|
|
11
12
|
script: "{{ PYTHON }} -m pip install . -vv --no-deps --no-build-isolation"
|
|
12
13
|
|
|
13
14
|
requirements:
|
|
@@ -46,6 +47,7 @@ test:
|
|
|
46
47
|
- pytest >=8.0
|
|
47
48
|
- pytest-cov >=5.0
|
|
48
49
|
commands:
|
|
50
|
+
- pymiesimx --help
|
|
49
51
|
- python -m pytest --config-file=./pytest.ini
|
|
50
52
|
source_files:
|
|
51
53
|
- tests
|