PyMieSimX 0.1.0__py3-none-any.whl
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.
- PyMieSimX/__init__.py +5 -0
- PyMieSimX/__main__.py +35 -0
- PyMieSimX/gui/__init__.py +5 -0
- PyMieSimX/gui/assets/card-state.js +41 -0
- PyMieSimX/gui/assets/dashboard.css +3352 -0
- PyMieSimX/gui/assets/default-settings.json +32 -0
- PyMieSimX/gui/assets/logo.png +0 -0
- PyMieSimX/gui/assets/pymiesim-favicon.svg +6 -0
- PyMieSimX/gui/assets/pymiesim-logo-dark.svg +15 -0
- PyMieSimX/gui/assets/pymiesim-logo.svg +39 -0
- PyMieSimX/gui/components/__init__.py +5 -0
- PyMieSimX/gui/components/cards.py +64 -0
- PyMieSimX/gui/defaults.py +18 -0
- PyMieSimX/gui/interface.py +688 -0
- PyMieSimX/gui/layout.py +329 -0
- PyMieSimX/gui/pages/__init__.py +9 -0
- PyMieSimX/gui/pages/citation.py +66 -0
- PyMieSimX/gui/pages/documentation.py +132 -0
- PyMieSimX/gui/pages/experiment/__init__.py +5 -0
- PyMieSimX/gui/pages/experiment/page.py +49 -0
- PyMieSimX/gui/pages/experiment/sections/__init__.py +5 -0
- PyMieSimX/gui/pages/experiment/sections/configuration.py +42 -0
- PyMieSimX/gui/pages/home.py +152 -0
- PyMieSimX/gui/pages/install_local.py +110 -0
- PyMieSimX/gui/pages/settings.py +87 -0
- PyMieSimX/gui/pages/single/__init__.py +5 -0
- PyMieSimX/gui/pages/single/page.py +28 -0
- PyMieSimX/gui/pages/single/sections/__init__.py +6 -0
- PyMieSimX/gui/pages/single/sections/representations.py +38 -0
- PyMieSimX/gui/pages/single/sections/setup.py +27 -0
- PyMieSimX/gui/parsing.py +149 -0
- PyMieSimX/gui/schemas.py +118 -0
- PyMieSimX/gui/services.py +735 -0
- pymiesimx-0.1.0.dist-info/METADATA +90 -0
- pymiesimx-0.1.0.dist-info/RECORD +39 -0
- pymiesimx-0.1.0.dist-info/WHEEL +5 -0
- pymiesimx-0.1.0.dist-info/entry_points.txt +2 -0
- pymiesimx-0.1.0.dist-info/licenses/LICENSE +21 -0
- pymiesimx-0.1.0.dist-info/top_level.txt +1 -0
PyMieSimX/gui/layout.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""Layout helpers for the experiment dashboard."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dash import dcc, html
|
|
6
|
+
|
|
7
|
+
from PyMieSimX.gui.defaults import DEFAULT_APPLICATION_SETTINGS, DEFAULT_PLOT_SETTINGS
|
|
8
|
+
from PyMieSimX.gui.schemas import FieldSpec, SECTION_FIELDS, SINGLE_SCATTERER_FIELDS, SINGLE_SOURCE_FIELDS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
THEME_LIGHT = "https://cdn.jsdelivr.net/npm/bootswatch@5.3.6/dist/flatly/bootstrap.min.css"
|
|
12
|
+
THEME_DARK = "https://cdn.jsdelivr.net/npm/bootswatch@5.3.6/dist/slate/bootstrap.min.css"
|
|
13
|
+
|
|
14
|
+
PLOT_CONFIG = {
|
|
15
|
+
"displayModeBar": "always",
|
|
16
|
+
"displaylogo": False,
|
|
17
|
+
"scrollZoom": True,
|
|
18
|
+
"doubleClick": "reset+autosize",
|
|
19
|
+
"modeBarButtonsToAdd": ["zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d"],
|
|
20
|
+
"toImageButtonOptions": {"format": "png", "filename": "pymiesim_plot", "scale": 2},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _plot_options_card(prefix: str, settings: dict, representation: str | None = None, projection: str | None = None, projection_options: list[dict] | None = None):
|
|
25
|
+
"""Build the neutral, per-plot controls shown beneath each graph."""
|
|
26
|
+
values = {
|
|
27
|
+
"x_scale": "linear",
|
|
28
|
+
"log_y": False,
|
|
29
|
+
"font_size": 14,
|
|
30
|
+
"line_width": 2,
|
|
31
|
+
"show_legend": True,
|
|
32
|
+
"show_grid": True,
|
|
33
|
+
"show_title": True,
|
|
34
|
+
**(settings or {}),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def dropdown(name: str, options: list[dict], value):
|
|
38
|
+
return dcc.Dropdown(
|
|
39
|
+
id=f"plot-{prefix}-{name}",
|
|
40
|
+
options=options,
|
|
41
|
+
value=value,
|
|
42
|
+
clearable=False,
|
|
43
|
+
searchable=False,
|
|
44
|
+
optionHeight=34,
|
|
45
|
+
maxHeight=170,
|
|
46
|
+
persistence=True,
|
|
47
|
+
persistence_type="session",
|
|
48
|
+
className="dashboard-dropdown plot-option-control",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def number(name: str, value, minimum, maximum, step):
|
|
52
|
+
return dcc.Input(
|
|
53
|
+
id=f"plot-{prefix}-{name}",
|
|
54
|
+
type="number",
|
|
55
|
+
value=value,
|
|
56
|
+
min=minimum,
|
|
57
|
+
max=maximum,
|
|
58
|
+
step=step,
|
|
59
|
+
placeholder=str(value),
|
|
60
|
+
persistence=True,
|
|
61
|
+
persistence_type="session",
|
|
62
|
+
className="field-input plot-option-control plot-number-control",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
fields = []
|
|
66
|
+
is_structured_map = representation in {"stokes", "stokes_q", "stokes_u", "stokes_v", "spf", "farfields"} or str(representation or "").startswith("nearfields")
|
|
67
|
+
is_polar = prefix == "single" and projection == "polar_1d"
|
|
68
|
+
is_3d = prefix == "single" and projection in {"3d", "3d_radial"}
|
|
69
|
+
|
|
70
|
+
if is_polar:
|
|
71
|
+
fields.append(("Radial scale", dropdown("y-scale", [{"label": "Linear", "value": False}, {"label": "Logarithmic", "value": True}], bool(values["log_y"]))))
|
|
72
|
+
elif not is_structured_map and not is_3d:
|
|
73
|
+
fields.extend([
|
|
74
|
+
("X scale", dropdown("x-scale", [{"label": "Linear", "value": "linear"}, {"label": "Logarithmic", "value": "log"}], values["x_scale"])),
|
|
75
|
+
("Y scale", dropdown("y-scale", [{"label": "Linear", "value": False}, {"label": "Logarithmic", "value": True}], bool(values["log_y"]))),
|
|
76
|
+
])
|
|
77
|
+
fields.append(("Font size", number("font-size", values["font_size"], 8, 32, 1)))
|
|
78
|
+
if not is_structured_map and not is_3d:
|
|
79
|
+
fields.append(("Line width", number("line-width", values["line_width"], 0.5, 8, 0.5)))
|
|
80
|
+
has_legend = not (prefix == "single" and (is_structured_map or is_3d))
|
|
81
|
+
if has_legend:
|
|
82
|
+
fields.append(("Legend", dropdown("legend", [{"label": "Show", "value": True}, {"label": "Hide", "value": False}], bool(values["show_legend"]))))
|
|
83
|
+
fields.append(("Grid", dropdown("grid", [{"label": "Show", "value": True}, {"label": "Hide", "value": False}], bool(values["show_grid"]))))
|
|
84
|
+
if prefix == "experiment":
|
|
85
|
+
fields.insert(2, ("Projection", dropdown("projection", projection_options or [{"label": "Cartesian", "value": "cartesian"}], projection or "cartesian")))
|
|
86
|
+
return html.Section(
|
|
87
|
+
className="plot-options-card",
|
|
88
|
+
children=[
|
|
89
|
+
html.Div(className="plot-options-title", children="Plot options"),
|
|
90
|
+
html.Div(className="plot-options-grid", children=[html.Div(className="plot-option-field", children=[html.Label(label), control]) for label, control in fields]),
|
|
91
|
+
],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _x_axis_card(default_measure_options: list[str]):
|
|
96
|
+
"""Build the post-plot X/Y axis selectors, separate from run controls."""
|
|
97
|
+
return html.Section(
|
|
98
|
+
className="graph-axis-card",
|
|
99
|
+
children=[
|
|
100
|
+
html.Div("Graph axes", className="graph-axis-title"),
|
|
101
|
+
html.Div(
|
|
102
|
+
className="graph-axis-fields",
|
|
103
|
+
children=[
|
|
104
|
+
html.Div(
|
|
105
|
+
className="graph-axis-field",
|
|
106
|
+
children=[
|
|
107
|
+
html.Label("X axis", htmlFor="x-axis-select"),
|
|
108
|
+
dcc.Dropdown(
|
|
109
|
+
id="x-axis-select",
|
|
110
|
+
className="dashboard-dropdown graph-axis-control",
|
|
111
|
+
options=[],
|
|
112
|
+
placeholder="Detected from fields with multiple values",
|
|
113
|
+
searchable=False,
|
|
114
|
+
optionHeight=38,
|
|
115
|
+
maxHeight=200,
|
|
116
|
+
persistence=True,
|
|
117
|
+
persistence_type="session",
|
|
118
|
+
),
|
|
119
|
+
],
|
|
120
|
+
),
|
|
121
|
+
html.Div(
|
|
122
|
+
className="graph-axis-field",
|
|
123
|
+
children=[
|
|
124
|
+
html.Label("Y axis", htmlFor="measure-select"),
|
|
125
|
+
dcc.Dropdown(
|
|
126
|
+
id="measure-select",
|
|
127
|
+
className="dashboard-dropdown graph-axis-control",
|
|
128
|
+
options=[{"label": measure, "value": measure} for measure in default_measure_options],
|
|
129
|
+
value=default_measure_options[0] if default_measure_options else None,
|
|
130
|
+
clearable=False,
|
|
131
|
+
searchable=False,
|
|
132
|
+
optionHeight=38,
|
|
133
|
+
maxHeight=200,
|
|
134
|
+
persistence=True,
|
|
135
|
+
persistence_type="session",
|
|
136
|
+
),
|
|
137
|
+
],
|
|
138
|
+
),
|
|
139
|
+
],
|
|
140
|
+
),
|
|
141
|
+
],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def create_layout(default_measure_options: list[str]):
|
|
146
|
+
"""Create a route-driven application shell containing only Home initially."""
|
|
147
|
+
return html.Div(
|
|
148
|
+
className="app-shell",
|
|
149
|
+
children=[
|
|
150
|
+
dcc.Location(id="url", refresh=False),
|
|
151
|
+
dcc.Store(id="experiment-result"),
|
|
152
|
+
dcc.Store(id="single-result"),
|
|
153
|
+
dcc.Download(id="csv-download"),
|
|
154
|
+
dcc.Download(id="single-csv-download"),
|
|
155
|
+
dcc.Store(id="home-visit-count", data=0, storage_type="local"),
|
|
156
|
+
dcc.Store(id="experiment-run-count", data=0, storage_type="local"),
|
|
157
|
+
dcc.Store(id="single-run-count", data=0, storage_type="local"),
|
|
158
|
+
dcc.Store(id="theme-store", data={"theme": DEFAULT_APPLICATION_SETTINGS["theme"]}, storage_type="session"),
|
|
159
|
+
dcc.Store(id="plot-settings-store", data={
|
|
160
|
+
"particle_explorer": dict(DEFAULT_PLOT_SETTINGS),
|
|
161
|
+
"parameter_sweep": dict(DEFAULT_PLOT_SETTINGS),
|
|
162
|
+
}, storage_type="session"),
|
|
163
|
+
html.Link(id="theme-link", rel="stylesheet", href=THEME_LIGHT),
|
|
164
|
+
html.Div(
|
|
165
|
+
className="dashboard-frame",
|
|
166
|
+
children=[
|
|
167
|
+
_build_sidebar(),
|
|
168
|
+
html.Main(
|
|
169
|
+
id="page-content",
|
|
170
|
+
className="dashboard-main",
|
|
171
|
+
children=[],
|
|
172
|
+
),
|
|
173
|
+
],
|
|
174
|
+
),
|
|
175
|
+
],
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def build_application_footer() -> html.Footer:
|
|
180
|
+
"""Build the global attribution footer shown below every page."""
|
|
181
|
+
return html.Footer(
|
|
182
|
+
[
|
|
183
|
+
"RosettaX is developed and maintained by ",
|
|
184
|
+
html.A(
|
|
185
|
+
"Martin Poinsinet de Sivry-Houle",
|
|
186
|
+
href="mailto:martin.poinsinet.de.sivry@gmail.com",
|
|
187
|
+
style={
|
|
188
|
+
"textDecoration": "none",
|
|
189
|
+
"fontWeight": "600",
|
|
190
|
+
},
|
|
191
|
+
),
|
|
192
|
+
", at the Amsterdam Vesicle Center.",
|
|
193
|
+
],
|
|
194
|
+
style={
|
|
195
|
+
"fontSize": "0.88rem",
|
|
196
|
+
"opacity": 0.72,
|
|
197
|
+
"textAlign": "center",
|
|
198
|
+
"paddingTop": "12px",
|
|
199
|
+
"paddingBottom": "8px",
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def build_page_with_footer(page):
|
|
205
|
+
"""Wrap a routed page with the shared RosettaX-style footer."""
|
|
206
|
+
return html.Div(
|
|
207
|
+
children=[page, build_application_footer()],
|
|
208
|
+
style={
|
|
209
|
+
"flex": "1 0 auto",
|
|
210
|
+
"display": "flex",
|
|
211
|
+
"flexDirection": "column",
|
|
212
|
+
"gap": "24px",
|
|
213
|
+
},
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _build_sidebar():
|
|
218
|
+
"""Create the dashboard sidebar."""
|
|
219
|
+
return html.Aside(
|
|
220
|
+
className="dashboard-sidebar",
|
|
221
|
+
children=[
|
|
222
|
+
html.Div(
|
|
223
|
+
className="sidebar-brand",
|
|
224
|
+
children=[
|
|
225
|
+
dcc.Link(
|
|
226
|
+
html.Img(id="sidebar-logo", src="/assets/pymiesim-logo.svg", alt="PyMieSim home", className="sidebar-logo"),
|
|
227
|
+
href="/",
|
|
228
|
+
refresh=False,
|
|
229
|
+
className="sidebar-logo-link",
|
|
230
|
+
),
|
|
231
|
+
],
|
|
232
|
+
),
|
|
233
|
+
html.Nav(
|
|
234
|
+
className="sidebar-nav",
|
|
235
|
+
children=[
|
|
236
|
+
_sidebar_link("Home", "/"),
|
|
237
|
+
_sidebar_link("Particle Explorer", "/single"),
|
|
238
|
+
_sidebar_link("Parameter Sweep", "/experiment"),
|
|
239
|
+
_sidebar_link("Documentation", "/documentation"),
|
|
240
|
+
_sidebar_link("Settings", "/settings"),
|
|
241
|
+
],
|
|
242
|
+
),
|
|
243
|
+
],
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def build_theme_selector():
|
|
248
|
+
"""Build the persisted Light/Dark selector used by the application shell."""
|
|
249
|
+
return dcc.Dropdown(
|
|
250
|
+
id="theme-mode",
|
|
251
|
+
options=[
|
|
252
|
+
{"label": "Dark", "value": "dark"},
|
|
253
|
+
{"label": "Light", "value": "light"},
|
|
254
|
+
],
|
|
255
|
+
value=DEFAULT_APPLICATION_SETTINGS["theme"],
|
|
256
|
+
clearable=False,
|
|
257
|
+
searchable=False,
|
|
258
|
+
optionHeight=38,
|
|
259
|
+
maxHeight=200,
|
|
260
|
+
persistence=True,
|
|
261
|
+
persistence_type="session",
|
|
262
|
+
className="theme-mode-select",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _sidebar_link(label: str, href: str):
|
|
267
|
+
"""Build a single sidebar anchor."""
|
|
268
|
+
# Keep route-based IDs stable while allowing user-facing labels to evolve.
|
|
269
|
+
link_id = f"sidebar-link-{href.strip('/') or 'home'}"
|
|
270
|
+
return dcc.Link(label, id=link_id, href=href, refresh=False, className="sidebar-link active" if label == "Home" else "sidebar-link")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def render_fields(section: str, section_type: str):
|
|
274
|
+
"""Render all fields for one selected section type."""
|
|
275
|
+
schemas = {"single-source": SINGLE_SOURCE_FIELDS, "single-scatterer": SINGLE_SCATTERER_FIELDS}
|
|
276
|
+
field_specs = schemas[section][section_type] if section in schemas else SECTION_FIELDS[section][section_type]
|
|
277
|
+
|
|
278
|
+
if not field_specs:
|
|
279
|
+
return html.Div("This parameter sweep runs without a detector set.", className="empty-section")
|
|
280
|
+
|
|
281
|
+
return html.Div(className="field-grid", children=[render_field(section, field_spec) for field_spec in field_specs])
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def render_field(section: str, field_spec: FieldSpec):
|
|
285
|
+
"""Render one schema field as a labeled text input."""
|
|
286
|
+
persistence = "parameter-sweep-defaults-v2" if section in {"source", "scatterer", "detector"} else True
|
|
287
|
+
default_class = " field-input-default" if section == "detector" and field_spec.name in {"polarization_filter", "medium"} else ""
|
|
288
|
+
return html.Div(
|
|
289
|
+
className="field-block",
|
|
290
|
+
children=[
|
|
291
|
+
html.Label(_format_field_label(field_spec)),
|
|
292
|
+
dcc.Input(
|
|
293
|
+
id={"kind": "field", "section": section, "name": field_spec.name},
|
|
294
|
+
type="text",
|
|
295
|
+
value=field_spec.default,
|
|
296
|
+
debounce=False,
|
|
297
|
+
placeholder=field_spec.placeholder or field_spec.default,
|
|
298
|
+
className=f"field-input{default_class}",
|
|
299
|
+
persistence=persistence,
|
|
300
|
+
persistence_type="session",
|
|
301
|
+
),
|
|
302
|
+
],
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def render_summary_cards(summary: list[dict[str, str]]):
|
|
307
|
+
"""Render compact summary cards for one experiment run."""
|
|
308
|
+
if not summary:
|
|
309
|
+
return [html.Div(className="summary-card", children=[html.Span("No result yet"), html.Strong("Run a parameter sweep")])]
|
|
310
|
+
|
|
311
|
+
return [
|
|
312
|
+
html.Div(className="summary-card", children=[html.Span(item["label"]), html.Strong(item["value"])])
|
|
313
|
+
for item in summary
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _build_default_help_text(field_spec: FieldSpec) -> str:
|
|
318
|
+
"""Return a fallback field description."""
|
|
319
|
+
if field_spec.unit is None:
|
|
320
|
+
return "Scalar values, comma-separated values, or start:end:count are supported when meaningful."
|
|
321
|
+
|
|
322
|
+
return f"Values are interpreted in {field_spec.unit}."
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _format_field_label(field_spec: FieldSpec) -> str:
|
|
326
|
+
"""Put the unit beside the label so cards stay compact."""
|
|
327
|
+
if field_spec.unit is None:
|
|
328
|
+
return field_spec.label
|
|
329
|
+
return f"{field_spec.label} [{field_spec.unit}]"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Page modules for the PyMieSim Dash application."""
|
|
2
|
+
|
|
3
|
+
from .documentation import build_documentation_page
|
|
4
|
+
from .citation import build_citation_page
|
|
5
|
+
from .home import build_home_page
|
|
6
|
+
from .install_local import build_install_local_page
|
|
7
|
+
from .settings import build_settings_page
|
|
8
|
+
|
|
9
|
+
__all__ = ["build_citation_page", "build_documentation_page", "build_home_page", "build_install_local_page", "build_settings_page"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Citation page composition."""
|
|
2
|
+
|
|
3
|
+
from dash import html
|
|
4
|
+
|
|
5
|
+
from PyMieSimX.gui.components import Card
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
PYMIESIM_DOI = "10.1364/OPTCON.473102"
|
|
9
|
+
PYMIESIM_ARTICLE_URL = "https://opg.optica.org/optcon/fulltext.cfm?uri=optcon-2-3-520"
|
|
10
|
+
PYMIESIM_BIBTEX = """@article{PoinsinetdeSivry-Houle:23,
|
|
11
|
+
author = {Martin Poinsinet de Sivry-Houle and Nicolas Godbout and Caroline Boudoux},
|
|
12
|
+
journal = {Opt. Continuum},
|
|
13
|
+
title = {PyMieSim: an open-source library for fast and flexible far-field Mie scattering simulations},
|
|
14
|
+
volume = {2},
|
|
15
|
+
number = {3},
|
|
16
|
+
pages = {520--534},
|
|
17
|
+
year = {2023},
|
|
18
|
+
doi = {10.1364/OPTCON.473102},
|
|
19
|
+
}"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_citation_page():
|
|
23
|
+
"""Build the citation page shown by the home-page citation button."""
|
|
24
|
+
return html.Div(
|
|
25
|
+
className="page-content-stack",
|
|
26
|
+
children=[
|
|
27
|
+
html.Section(
|
|
28
|
+
className="page-hero documentation-page-hero",
|
|
29
|
+
children=[
|
|
30
|
+
html.P("PyMieSim reference", className="eyebrow"),
|
|
31
|
+
html.H1("Cite PyMieSim"),
|
|
32
|
+
html.P(
|
|
33
|
+
"Use this reference when PyMieSim contributes to a scientific workflow, analysis, or publication.",
|
|
34
|
+
className="hero-text",
|
|
35
|
+
),
|
|
36
|
+
],
|
|
37
|
+
),
|
|
38
|
+
html.Section(
|
|
39
|
+
className=Card.classes(color="blue", extra="panel documentation-card"),
|
|
40
|
+
children=[
|
|
41
|
+
html.Div(className="card-header panel-header", children=[html.H2("BibTeX")]),
|
|
42
|
+
html.Div(className="card-body", children=[
|
|
43
|
+
html.Div("Copy this reference into your bibliography file.", className="home-info-copy"),
|
|
44
|
+
html.Pre(PYMIESIM_BIBTEX, className="citation-bibtex"),
|
|
45
|
+
]),
|
|
46
|
+
],
|
|
47
|
+
),
|
|
48
|
+
html.Section(
|
|
49
|
+
className=Card.classes(color="blue", extra="panel documentation-card"),
|
|
50
|
+
children=[
|
|
51
|
+
html.Div(className="card-header panel-header", children=[html.H2("Publication")]),
|
|
52
|
+
html.Div(className="card-body", children=[
|
|
53
|
+
html.Div(f"DOI: {PYMIESIM_DOI}", className="home-info-copy"),
|
|
54
|
+
html.A("View publication", href=PYMIESIM_ARTICLE_URL, target="_blank", rel="noopener noreferrer", className="inline-action"),
|
|
55
|
+
]),
|
|
56
|
+
],
|
|
57
|
+
),
|
|
58
|
+
html.Section(
|
|
59
|
+
className=Card.classes(color="blue", extra="panel documentation-card"),
|
|
60
|
+
children=[
|
|
61
|
+
html.Div(className="card-header panel-header", children=[html.H2("Citation guidance")]),
|
|
62
|
+
html.Div(className="card-body", children=[html.P("Please cite the publication above when using PyMieSim in academic work.")]),
|
|
63
|
+
],
|
|
64
|
+
),
|
|
65
|
+
],
|
|
66
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Documentation hub page composition."""
|
|
2
|
+
|
|
3
|
+
from dash import html
|
|
4
|
+
|
|
5
|
+
from PyMieSimX.gui.components import Card
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_documentation_page():
|
|
9
|
+
"""Build the in-app guide to PyMieSim workflows and vocabulary."""
|
|
10
|
+
return html.Div(
|
|
11
|
+
className="page-content-stack documentation-page",
|
|
12
|
+
children=[
|
|
13
|
+
html.Section(
|
|
14
|
+
className="page-hero documentation-page-hero",
|
|
15
|
+
children=[
|
|
16
|
+
html.P("PyMieSim reference", className="eyebrow"),
|
|
17
|
+
html.H1("Documentation"),
|
|
18
|
+
html.P("A practical guide to building optical setups, running sweeps, reading representations, and entering values efficiently.", className="hero-text"),
|
|
19
|
+
html.Div(
|
|
20
|
+
className="documentation-hero-actions",
|
|
21
|
+
children=[
|
|
22
|
+
html.A("Start a parameter sweep →", href="/experiment", className="home-button home-button-primary"),
|
|
23
|
+
html.A("Open Particle Explorer →", href="/single", className="home-button home-button-outline"),
|
|
24
|
+
],
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
),
|
|
28
|
+
html.Section(
|
|
29
|
+
className=Card.classes(color="blue", extra="panel documentation-quickstart"),
|
|
30
|
+
children=[
|
|
31
|
+
html.Div(className="card-header panel-header", children=[html.H2("Quick start")]),
|
|
32
|
+
html.Div(
|
|
33
|
+
className="card-body documentation-quickstart-body",
|
|
34
|
+
children=[
|
|
35
|
+
_step("01", "Choose a workspace", "Use Parameter Sweep for parameter sweeps and detector coupling. Use Particle Explorer to inspect one optical setup."),
|
|
36
|
+
_step("02", "Configure the objects", "Pick a source, scatterer, and—when needed—a detector. Each card exposes the fields supported by that model."),
|
|
37
|
+
_step("03", "Enter values or sweeps", "Use one value for a focused run, a comma-separated list for selected values, or start:end:count for an evenly spaced sweep."),
|
|
38
|
+
_step("04", "Render and inspect", "Run the calculation, select the X axis when several fields vary, and use Settings to tune the resulting plots."),
|
|
39
|
+
],
|
|
40
|
+
),
|
|
41
|
+
],
|
|
42
|
+
),
|
|
43
|
+
html.Div(
|
|
44
|
+
className="documentation-grid documentation-topic-grid",
|
|
45
|
+
children=[
|
|
46
|
+
_guide_card("yellow", "Parameter Sweep", "Sweep a parameter space", "Build source, scatterer, and detector sets, then evaluate a measure over every combination.", ["X-axis is inferred from fields with multiple values.", "Detector-free runs are supported.", "Results can be exported as CSV."], "/experiment", "Open Parameter Sweep"),
|
|
47
|
+
_guide_card("blue", "Particle Explorer", "Understand one setup", "Inspect angular amplitudes, polarization, phase functions, or far-field intensity for one source and scatterer.", ["Choose a representation.", "Control angular sampling.", "Render the figure on demand."], "/single", "Open Particle Explorer"),
|
|
48
|
+
_guide_card("cyan", "Plot settings", "Make figures your own", "Set typography, line and marker sizes, grid visibility, legend visibility, and light or dark plot styling.", ["Preferences are saved in this browser.", "Settings apply to both workspaces.", "The application theme is controlled here."], "/settings", "Open Settings"),
|
|
49
|
+
_guide_card("purple", "Citation", "Reference the project", "Find the publication details and ready-to-copy BibTeX entry for work that uses PyMieSim.", ["Publication DOI is included.", "BibTeX is formatted for direct copying.", "Citation guidance is kept with the reference."], "/citation", "View Citation"),
|
|
50
|
+
],
|
|
51
|
+
),
|
|
52
|
+
html.Div(
|
|
53
|
+
className="documentation-columns",
|
|
54
|
+
children=[
|
|
55
|
+
_syntax_card(),
|
|
56
|
+
_vocabulary_card(),
|
|
57
|
+
],
|
|
58
|
+
),
|
|
59
|
+
html.Section(
|
|
60
|
+
className=Card.classes(color="green", extra="panel documentation-models"),
|
|
61
|
+
children=[
|
|
62
|
+
html.Div(className="card-header panel-header", children=[html.H2("Supported model families")]),
|
|
63
|
+
html.Div(
|
|
64
|
+
className="card-body documentation-model-grid",
|
|
65
|
+
children=[
|
|
66
|
+
_model_group("Sources", "Gaussian", "Plane wave"),
|
|
67
|
+
_model_group("Scatterers", "Sphere", "Infinite cylinder", "Core-shell"),
|
|
68
|
+
_model_group("Detectors", "Photodiode", "Coherent mode", "No detector"),
|
|
69
|
+
_model_group("Representations", "S1 / S2", "Stokes", "SPF", "Far-field"),
|
|
70
|
+
],
|
|
71
|
+
),
|
|
72
|
+
],
|
|
73
|
+
),
|
|
74
|
+
html.Section(
|
|
75
|
+
className=Card.classes(color="blue", extra="panel documentation-note"),
|
|
76
|
+
children=[
|
|
77
|
+
html.Div(className="card-header panel-header", children=[html.H2("Need more detail?")]),
|
|
78
|
+
html.Div(className="card-body documentation-note-body", children=[html.P("The dashboard is designed to keep the common path visible: configure an object, run a calculation, then refine the plot. For local installation instructions and release links, open the installation guide."), html.A("Open installation guide →", href="/documentation/install-local", className="inline-action")]),
|
|
79
|
+
],
|
|
80
|
+
),
|
|
81
|
+
],
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _step(number: str, title: str, description: str):
|
|
86
|
+
return html.Div(className="documentation-step", children=[html.Span(number, className="documentation-step-number"), html.Div([html.H3(title), html.P(description)])])
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _guide_card(color: str, eyebrow: str, title: str, description: str, bullets: list[str], href: str, action: str):
|
|
90
|
+
return html.Section(
|
|
91
|
+
className=Card.classes(color=color, extra="panel documentation-guide-card"),
|
|
92
|
+
children=[
|
|
93
|
+
html.Div(className="card-header panel-header", children=[html.Span(eyebrow, className="documentation-card-kicker"), html.H2(title)]),
|
|
94
|
+
html.Div(className="card-body", children=[html.P(description), html.Ul([html.Li(item) for item in bullets], className="documentation-bullet-list"), html.A(f"{action} →", href=href, className="inline-action")]),
|
|
95
|
+
],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _syntax_card():
|
|
100
|
+
examples = (
|
|
101
|
+
("600", "One scalar value"),
|
|
102
|
+
("600,800,1000", "An explicit list"),
|
|
103
|
+
("400:1400:8", "Eight evenly spaced values"),
|
|
104
|
+
("LP01,HG11", "A list of names or modes"),
|
|
105
|
+
)
|
|
106
|
+
return html.Section(
|
|
107
|
+
className=Card.classes(color="orange", extra="panel documentation-detail-card"),
|
|
108
|
+
children=[
|
|
109
|
+
html.Div(className="card-header panel-header", children=[html.H2("Field syntax")]),
|
|
110
|
+
html.Div(className="card-body", children=[html.P("Most text fields accept compact batch input. The same notation works for optical quantities, material values, angles, and named modes."), html.Div([html.Div(className="documentation-syntax-row", children=[html.Code(value), html.Span(description)]) for value, description in examples], className="documentation-syntax-list"), html.P("Keep the X axis on a field that actually varies if you want a clean one-dimensional plot.", className="documentation-callout")]),
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _vocabulary_card():
|
|
116
|
+
rows = (
|
|
117
|
+
("Source", "The incident illumination, such as Gaussian or plane wave."),
|
|
118
|
+
("Scatterer", "The object being simulated, such as a sphere or core-shell particle."),
|
|
119
|
+
("Detector", "The collection model used for detector-specific measures and coupling."),
|
|
120
|
+
("Measure", "The quantity computed from the selected source, scatterer, and detector."),
|
|
121
|
+
)
|
|
122
|
+
return html.Section(
|
|
123
|
+
className=Card.classes(color="purple", extra="panel documentation-detail-card"),
|
|
124
|
+
children=[
|
|
125
|
+
html.Div(className="card-header panel-header", children=[html.H2("Core vocabulary")]),
|
|
126
|
+
html.Div(className="card-body documentation-definition-list", children=[html.Div([html.Strong(term), html.Span(description)]) for term, description in rows]),
|
|
127
|
+
],
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _model_group(title: str, *models: str):
|
|
132
|
+
return html.Div(className="documentation-model-group", children=[html.H3(title), html.Div([html.Span(model, className="documentation-model-pill") for model in models], className="documentation-model-pills")])
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Parameter Sweep page composition."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dash import dcc, html
|
|
6
|
+
|
|
7
|
+
from PyMieSimX.gui.components import HeaderCard
|
|
8
|
+
from PyMieSimX.gui.layout import PLOT_CONFIG, _plot_options_card, _x_axis_card
|
|
9
|
+
|
|
10
|
+
from .sections import build_detector_section, build_scatterer_section, build_source_section
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_experiment_page(default_measure_options: list[str], plot_settings: dict | None = None):
|
|
14
|
+
"""Build the isolated Parameter Sweep workspace."""
|
|
15
|
+
settings = (plot_settings or {}).get("parameter_sweep", {})
|
|
16
|
+
return html.Div(
|
|
17
|
+
className="tab-content-stack experiment-tab-content",
|
|
18
|
+
children=[
|
|
19
|
+
HeaderCard(
|
|
20
|
+
"Parameter Sweep Lab",
|
|
21
|
+
"Configure source, scatterer, and detector sets, then run parameter sweeps through the compiled engine directly from Dash.",
|
|
22
|
+
[
|
|
23
|
+
("01", "Configure source", "Choose the source family and sweep its optical parameters.", "yellow"),
|
|
24
|
+
("02", "Configure scatterer", "Set particle geometry, material, and medium values.", "blue"),
|
|
25
|
+
("03", "Configure detector", "Select collection geometry and compute the sweep response.", "orange"),
|
|
26
|
+
],
|
|
27
|
+
color="green",
|
|
28
|
+
).render(),
|
|
29
|
+
html.Section(
|
|
30
|
+
id="configure",
|
|
31
|
+
className="workspace",
|
|
32
|
+
children=[
|
|
33
|
+
html.Section(
|
|
34
|
+
className="control-column",
|
|
35
|
+
children=[html.Div(className="set-panel-grid", children=[build_source_section(), build_scatterer_section(), build_detector_section()])],
|
|
36
|
+
),
|
|
37
|
+
html.Section(
|
|
38
|
+
className="result-column",
|
|
39
|
+
children=[
|
|
40
|
+
html.Section(className="panel graph-panel", children=[dcc.Loading(id="result-graph-loading", type="circle", color="#4f8df7", custom_spinner=html.Div("Computing…", className="plot-computing-indicator"), delay_show=150, delay_hide=150, children=html.Div(className="plot-loading-target", children=[dcc.Graph(id="result-graph", config=PLOT_CONFIG), html.Div(id="experiment-computation-status", style={"display": "none"})]))]),
|
|
41
|
+
_x_axis_card(default_measure_options),
|
|
42
|
+
html.Div(id="experiment-plot-options-container", children=[_plot_options_card("experiment", settings)]),
|
|
43
|
+
html.Div(className="export-actions", children=[html.Button("Export CSV", id="export-csv", n_clicks=0, className="run-button export-button")]),
|
|
44
|
+
],
|
|
45
|
+
),
|
|
46
|
+
],
|
|
47
|
+
),
|
|
48
|
+
],
|
|
49
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Source, scatterer, and detector configuration cards."""
|
|
2
|
+
|
|
3
|
+
from dash import dcc, html
|
|
4
|
+
|
|
5
|
+
from PyMieSimX.gui.components import Card
|
|
6
|
+
from PyMieSimX.gui.defaults import DEFAULT_WORKSPACE_SETTINGS
|
|
7
|
+
from PyMieSimX.gui.schemas import SECTION_FIELDS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_source_section():
|
|
11
|
+
return _build_section("Source Set", "source-type", "source-fields", SECTION_FIELDS["source"], DEFAULT_WORKSPACE_SETTINGS["parameter_sweep"]["source_type"], color="yellow", info="Configure the optical source and its wavelength, polarization, and amplitude values.")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_scatterer_section():
|
|
15
|
+
return _build_section("Scatterer Set", "scatterer-type", "scatterer-fields", SECTION_FIELDS["scatterer"], DEFAULT_WORKSPACE_SETTINGS["parameter_sweep"]["scatterer_type"], color="blue", info="Configure particle geometry, material, and surrounding medium values.")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_detector_section():
|
|
19
|
+
return _build_section("Detector Set", "detector-type", "detector-fields", SECTION_FIELDS["detector"], DEFAULT_WORKSPACE_SETTINGS["parameter_sweep"]["detector_type"], detector=True, color="cyan", info="Configure detector collection geometry, sampling, offsets, and mode settings.")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _build_section(title, selector_id, fields_id, choices, default, detector=False, color="blue", info=""):
|
|
23
|
+
options = [{"label": "No detector" if detector and key == "None" else key.replace("Set", ""), "value": key} for key in choices]
|
|
24
|
+
persistence_key = "parameter-sweep-detector-default-v3" if detector else "parameter-sweep-defaults-v2"
|
|
25
|
+
detector_alert = (
|
|
26
|
+
html.Div(
|
|
27
|
+
"A detector is required to compute coupling. Select a detector to enable it.",
|
|
28
|
+
id="detector-coupling-alert",
|
|
29
|
+
className="detector-coupling-alert",
|
|
30
|
+
)
|
|
31
|
+
if detector
|
|
32
|
+
else None
|
|
33
|
+
)
|
|
34
|
+
return html.Details(
|
|
35
|
+
id=f"{selector_id}-card",
|
|
36
|
+
className=Card.classes(color=color, extra="workflow-card"),
|
|
37
|
+
open=True,
|
|
38
|
+
children=[
|
|
39
|
+
html.Summary(children=[html.Span(title), html.Span("i", className="workflow-info-button", title=info, **{"aria-label": info})]),
|
|
40
|
+
html.Div(className="workflow-card-body", children=[dcc.Dropdown(id=selector_id, className="dashboard-dropdown", options=options, value=default, clearable=False, searchable=False, optionHeight=38, maxHeight=200, persistence=persistence_key, persistence_type="session"), detector_alert, html.Div(id=fields_id, className="panel-body")]),
|
|
41
|
+
],
|
|
42
|
+
)
|