PyMieSim 3.6.0__cp313-cp313-win_amd64.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.
Files changed (101) hide show
  1. PyMieSim/__init__.py +16 -0
  2. PyMieSim/__main__.py +9 -0
  3. PyMieSim/_version.py +21 -0
  4. PyMieSim/binary/__init__.py +0 -0
  5. PyMieSim/binary/interface_detector.cp310-win_amd64.pyd +0 -0
  6. PyMieSim/binary/interface_detector.cp311-win_amd64.pyd +0 -0
  7. PyMieSim/binary/interface_detector.cp312-win_amd64.pyd +0 -0
  8. PyMieSim/binary/interface_detector.cp313-win_amd64.pyd +0 -0
  9. PyMieSim/binary/interface_experiment.cp310-win_amd64.pyd +0 -0
  10. PyMieSim/binary/interface_experiment.cp311-win_amd64.pyd +0 -0
  11. PyMieSim/binary/interface_experiment.cp312-win_amd64.pyd +0 -0
  12. PyMieSim/binary/interface_experiment.cp313-win_amd64.pyd +0 -0
  13. PyMieSim/binary/interface_scatterer.cp310-win_amd64.pyd +0 -0
  14. PyMieSim/binary/interface_scatterer.cp311-win_amd64.pyd +0 -0
  15. PyMieSim/binary/interface_scatterer.cp312-win_amd64.pyd +0 -0
  16. PyMieSim/binary/interface_scatterer.cp313-win_amd64.pyd +0 -0
  17. PyMieSim/binary/interface_sets.cp310-win_amd64.pyd +0 -0
  18. PyMieSim/binary/interface_sets.cp311-win_amd64.pyd +0 -0
  19. PyMieSim/binary/interface_sets.cp312-win_amd64.pyd +0 -0
  20. PyMieSim/binary/interface_sets.cp313-win_amd64.pyd +0 -0
  21. PyMieSim/binary/interface_source.cp310-win_amd64.pyd +0 -0
  22. PyMieSim/binary/interface_source.cp311-win_amd64.pyd +0 -0
  23. PyMieSim/binary/interface_source.cp312-win_amd64.pyd +0 -0
  24. PyMieSim/binary/interface_source.cp313-win_amd64.pyd +0 -0
  25. PyMieSim/binary/libcpp_coordinates.a +0 -0
  26. PyMieSim/binary/libcpp_detector.a +0 -0
  27. PyMieSim/binary/libcpp_experiment.a +0 -0
  28. PyMieSim/binary/libcpp_fibonacci.a +0 -0
  29. PyMieSim/binary/libcpp_mode_field.a +0 -0
  30. PyMieSim/binary/libcpp_sets.a +0 -0
  31. PyMieSim/binary/libcpp_source.a +0 -0
  32. PyMieSim/directories.py +31 -0
  33. PyMieSim/experiment/__init__.py +1 -0
  34. PyMieSim/experiment/dataframe_subclass.py +220 -0
  35. PyMieSim/experiment/detector/__init__.py +2 -0
  36. PyMieSim/experiment/detector/base.py +169 -0
  37. PyMieSim/experiment/detector/coherent_mode.py +50 -0
  38. PyMieSim/experiment/detector/photodiode.py +52 -0
  39. PyMieSim/experiment/scatterer/__init__.py +4 -0
  40. PyMieSim/experiment/scatterer/base.py +98 -0
  41. PyMieSim/experiment/scatterer/core_shell.py +82 -0
  42. PyMieSim/experiment/scatterer/cylinder.py +63 -0
  43. PyMieSim/experiment/scatterer/sphere.py +66 -0
  44. PyMieSim/experiment/setup.py +356 -0
  45. PyMieSim/experiment/source/__init__.py +2 -0
  46. PyMieSim/experiment/source/base.py +85 -0
  47. PyMieSim/experiment/source/gaussian.py +60 -0
  48. PyMieSim/experiment/source/planewave.py +69 -0
  49. PyMieSim/experiment/utils.py +132 -0
  50. PyMieSim/gui/__init__.py +0 -0
  51. PyMieSim/gui/helper.py +60 -0
  52. PyMieSim/gui/interface.py +136 -0
  53. PyMieSim/gui/section.py +606 -0
  54. PyMieSim/mesh.py +368 -0
  55. PyMieSim/polarization.py +174 -0
  56. PyMieSim/single/__init__.py +48 -0
  57. PyMieSim/single/detector/__init__.py +2 -0
  58. PyMieSim/single/detector/base.py +271 -0
  59. PyMieSim/single/detector/coherent.py +99 -0
  60. PyMieSim/single/detector/uncoherent.py +105 -0
  61. PyMieSim/single/representations.py +734 -0
  62. PyMieSim/single/scatterer/__init__.py +4 -0
  63. PyMieSim/single/scatterer/base.py +405 -0
  64. PyMieSim/single/scatterer/core_shell.py +126 -0
  65. PyMieSim/single/scatterer/cylinder.py +113 -0
  66. PyMieSim/single/scatterer/sphere.py +108 -0
  67. PyMieSim/single/source/__init__.py +3 -0
  68. PyMieSim/single/source/base.py +7 -0
  69. PyMieSim/single/source/gaussian.py +137 -0
  70. PyMieSim/single/source/planewave.py +97 -0
  71. PyMieSim/special_functions.py +81 -0
  72. PyMieSim/units.py +130 -0
  73. PyMieSim/validation_data/bohren_huffman/figure_810.csv +245 -0
  74. PyMieSim/validation_data/bohren_huffman/figure_87.csv +2 -0
  75. PyMieSim/validation_data/bohren_huffman/figure_88.csv +2 -0
  76. PyMieSim/validation_data/pymiescatt/example_coreshell_0.csv +41 -0
  77. PyMieSim/validation_data/pymiescatt/example_coreshell_1.csv +401 -0
  78. PyMieSim/validation_data/pymiescatt/example_shpere_0.csv +51 -0
  79. PyMieSim/validation_data/pymiescatt/example_shpere_1.csv +801 -0
  80. PyMieSim/validation_data/pymiescatt/example_shpere_2.csv +41 -0
  81. PyMieSim/validation_data/pymiescatt/example_shpere_3.csv +401 -0
  82. PyMieSim/validation_data/pymiescatt/example_sphere_0.csv +51 -0
  83. PyMieSim/validation_data/pymiescatt/example_sphere_1.csv +801 -0
  84. PyMieSim/validation_data/pymiescatt/example_sphere_2.csv +41 -0
  85. PyMieSim/validation_data/pymiescatt/example_sphere_3.csv +401 -0
  86. PyMieSim/validation_data/pymiescatt/validation_Qsca.csv +800 -0
  87. PyMieSim/validation_data/pymiescatt/validation_Qsca_coreshell_1.csv +400 -0
  88. PyMieSim/validation_data/pymiescatt/validation_Qsca_coreshell_2.csv +400 -0
  89. PyMieSim/validation_data/pymiescatt/validation_Qsca_medium.csv +800 -0
  90. PyMieSim/validation_data/pymiescatt/validation_coreshell.csv +81 -0
  91. PyMieSim/validation_data/pymiescatt/validation_sphere.csv +801 -0
  92. lib/libZBessel.a +0 -0
  93. lib/lib_ZBessel.a +0 -0
  94. lib/libcpp_base_scatterer.a +0 -0
  95. lib/libcpp_coreshell.a +0 -0
  96. lib/libcpp_cylinder.a +0 -0
  97. lib/libcpp_sphere.a +0 -0
  98. pymiesim-3.6.0.dist-info/METADATA +246 -0
  99. pymiesim-3.6.0.dist-info/RECORD +101 -0
  100. pymiesim-3.6.0.dist-info/WHEEL +5 -0
  101. pymiesim-3.6.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ import numpy
4
+ from pydantic import field_validator
5
+ import pint_pandas
6
+ from dataclasses import fields
7
+ from PyMieSim.polarization import BasePolarization, Linear
8
+ from PyMieSim.units import Quantity, meter, watt, AU, degree
9
+
10
+
11
+ class BaseSource:
12
+ """
13
+ Base class for light sources in PyMieSim experiments.
14
+ """
15
+
16
+ @field_validator('wavelength', mode='plain')
17
+ def _validate_length(cls, value):
18
+ """
19
+ Ensures that diameter is Quantity objects with length units.
20
+ """
21
+ if not isinstance(value, Quantity) or not value.check(meter):
22
+ raise ValueError(f"{value} must be a Quantity with meters units [meter].")
23
+
24
+ return numpy.atleast_1d(value)
25
+
26
+ @field_validator('optical_power', mode='plain')
27
+ def _validate_power(cls, value):
28
+ """
29
+ Ensure that arrays are properly converted to numpy arrays.
30
+ """
31
+ if not isinstance(value, Quantity) or not value.check(watt):
32
+ raise ValueError(f"{value} must be a Quantity with power units [watt].")
33
+
34
+ if not isinstance(value, numpy.ndarray):
35
+ value = numpy.atleast_1d(value)
36
+
37
+ return value
38
+
39
+ @field_validator('NA', mode='plain')
40
+ def _validate_arbitrary_units(cls, value):
41
+ """
42
+ Ensure that arrays are properly converted to numpy arrays.
43
+ """
44
+ if not isinstance(value, Quantity) or not value.check(AU):
45
+ raise ValueError(f"{value} must be a Quantity with arbitrary units [AU].")
46
+
47
+ if not isinstance(value, numpy.ndarray):
48
+ value = numpy.atleast_1d(value)
49
+
50
+ return value
51
+
52
+ @field_validator('polarization', mode='before')
53
+ def _validate_polarization(cls, value):
54
+ """
55
+ Ensures that polarization is well defined.
56
+ """
57
+ if (not isinstance(value, Quantity) or not value.check(degree)) and not isinstance(value, BasePolarization):
58
+ raise ValueError(f"{value} must be a Quantity with degree units [degree] or a Polarization instance.")
59
+
60
+ if isinstance(value, BasePolarization):
61
+ return value
62
+
63
+ if isinstance(value, Quantity):
64
+ return Linear(value)
65
+
66
+ def _generate_mapping(self) -> None:
67
+ """
68
+ Constructs a table of the scatterer's properties formatted for data visualization.
69
+ This method populates the `mapping` dictionary with user-friendly descriptions and formats of the scatterer properties.
70
+
71
+ Returns:
72
+ list: A list of visual representations for each property in the `mapping` dictionary that has been populated.
73
+ """
74
+ for attr in [f.name for f in fields(self) if f.name != 'source']:
75
+ values = getattr(self, attr)
76
+
77
+ if values is None:
78
+ continue
79
+
80
+ if hasattr(values, 'magnitude'):
81
+ magnitude = values.magnitude
82
+ units = values.units
83
+ self.mapping["source:" + attr] = pint_pandas.PintArray(magnitude, dtype=units)
84
+ else:
85
+ self.mapping["source:" + attr] = [repr(m) for m in values]
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ from typing import Union
4
+ from PyMieSim.single.source import Gaussian as _ # noqa:F401 # Necessary for pybind11 binding initialization
5
+ from pydantic.dataclasses import dataclass
6
+ from PyMieSim.units import Quantity
7
+ from PyMieSim.experiment.source.base import BaseSource
8
+ from PyMieSim.experiment.utils import config_dict, Sequential
9
+ from PyMieSim.polarization import BasePolarization, Linear
10
+ from PyMieSim.binary.interface_sets import CppGaussianSourceSet
11
+
12
+ @dataclass(config=config_dict)
13
+ class Gaussian(BaseSource, Sequential):
14
+ """
15
+ Represents a Gaussian light source with a specified numerical aperture and optical power.
16
+
17
+ Inherits from BaseSource and adds specific attributes for Gaussian sources.
18
+
19
+ Parameters
20
+ ----------
21
+ wavelength : Quantity
22
+ The wavelength(s) of the light source.
23
+ polarization : Union[UnitPolarizationAngle, Quantity]
24
+ Polarization state of the light field, if float is given it is assumed Linear polarization of angle theta.
25
+ NA : Quantity
26
+ The numerical aperture(s) of the Gaussian source.
27
+ optical_power : Quantity
28
+ The optical power of the source, in Watts.
29
+ """
30
+ NA: Quantity
31
+ optical_power: Quantity
32
+ wavelength: Quantity
33
+ polarization: Union[BasePolarization, Quantity]
34
+
35
+ def _generate_binding(self) -> None:
36
+ """
37
+ Prepares the keyword arguments for the C++ binding based on the scatterer's properties. This
38
+ involves evaluating material indices and organizing them into a dictionary for the C++ interface.
39
+
40
+ """
41
+ self.mapping = {}
42
+
43
+ if not isinstance(self.polarization, BasePolarization):
44
+ self.polarization = Linear(self.polarization)
45
+
46
+ self.binding_kwargs = dict(
47
+ wavelength=self.wavelength,
48
+ jones_vector=self.polarization.element,
49
+ NA=self.NA,
50
+ optical_power=self.optical_power,
51
+ is_sequential=self.is_sequential
52
+ )
53
+
54
+ self.binding = CppGaussianSourceSet(
55
+ wavelength=self.wavelength.to('meter').magnitude,
56
+ jones_vector=self.polarization.element,
57
+ NA=self.NA.to('dimensionless').magnitude,
58
+ optical_power=self.optical_power.to('watt').magnitude,
59
+ is_sequential=self.is_sequential
60
+ )
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ from typing import Union
4
+ from PyMieSim.single.source import PlaneWave as _ # noqa:F401 # Necessary for pybind11 binding initialization
5
+ import numpy
6
+ from pydantic.dataclasses import dataclass
7
+ from pydantic import field_validator
8
+ from PyMieSim.units import Quantity
9
+ from PyMieSim.experiment.source.base import BaseSource
10
+ from PyMieSim.polarization import BasePolarization, Linear
11
+ from PyMieSim.experiment.utils import config_dict, Sequential
12
+ from PyMieSim.binary.interface_sets import CppPlaneWaveSourceSet
13
+
14
+
15
+ @dataclass(config=config_dict)
16
+ class PlaneWave(BaseSource, Sequential):
17
+ """
18
+ Represents a Plane Wave light source with a specified amplitude.
19
+
20
+ Inherits from BaseSource and specifies amplitude directly.
21
+
22
+ Parameters
23
+ ----------
24
+ wavelength : Quantity
25
+ The wavelength(s) of the light source.
26
+ polarization : Union[UnitPolarizationAngle, Quantity]
27
+ Polarization state of the light field, if float is given it is assumed Linear polarization of angle theta.
28
+ amplitude : Quantity
29
+ The amplitude of the plane wave, in Volt/Meter.
30
+ """
31
+ amplitude: Quantity
32
+ wavelength: Quantity
33
+ polarization: Union[BasePolarization, Quantity]
34
+
35
+ @field_validator('amplitude', mode='before')
36
+ def _validate_array(cls, value):
37
+ """Ensure that arrays are properly converted to numpy arrays."""
38
+ if not isinstance(value, numpy.ndarray):
39
+ value = numpy.atleast_1d(value)
40
+
41
+ return value
42
+
43
+ def _generate_binding(self) -> None:
44
+ """
45
+ Prepares the keyword arguments for the C++ binding based on the scatterer's properties. This
46
+ involves evaluating material indices and organizing them into a dictionary for the C++ interface.
47
+
48
+ Returns
49
+ -------
50
+ None
51
+ """
52
+ self.mapping = {}
53
+
54
+ if not isinstance(self.polarization, BasePolarization):
55
+ self.polarization = Linear(self.polarization)
56
+
57
+ self.binding_kwargs = dict(
58
+ wavelength=self.wavelength,
59
+ jones_vector=self.polarization.element,
60
+ amplitude=self.amplitude,
61
+ is_sequential=self.is_sequential
62
+ )
63
+
64
+ self.binding = CppPlaneWaveSourceSet(
65
+ wavelength=self.wavelength.to('meter').magnitude,
66
+ jones_vector=self.polarization.element,
67
+ amplitude=self.amplitude.to('volt/meter').magnitude,
68
+ is_sequential=self.is_sequential
69
+ )
@@ -0,0 +1,132 @@
1
+ import numpy as np
2
+ from typing import Union, List, Dict, Optional, TypeVar
3
+ from PyMieSim.units import Quantity
4
+ from PyOptik.material.base_class import BaseMaterial
5
+
6
+
7
+ T = TypeVar('T')
8
+
9
+ from pydantic import ConfigDict
10
+
11
+ # Configuration dictionary for the Pydantic dataclass
12
+ config_dict = ConfigDict(
13
+ kw_only=True,
14
+ slots=True,
15
+ extra='forbid',
16
+ arbitrary_types_allowed=True
17
+ )
18
+
19
+
20
+ def broadcast_params(total_size: Optional[int] = None, **kwargs: Union[T, List[T]]) -> Dict[str, np.ndarray]:
21
+ """
22
+ Broadcast scalar or single-element parameters to NumPy arrays of a given target size.
23
+
24
+ Each parameter is first converted to a 1D NumPy array. If a parameter is given as a scalar
25
+ or an array/list of length one, it will be repeated (broadcast) to match the target size.
26
+ The target size is determined by `total_size` if provided, or as the maximum size among all
27
+ parameters otherwise. If any parameter is provided as an array of length greater than one and
28
+ its length does not match the target size, a ValueError is raised.
29
+
30
+ Parameters
31
+ ----------
32
+ total_size : int, optional
33
+ The explicit target size for broadcasting. Must be a positive integer if provided.
34
+ If not provided, the target size is determined as the maximum size among the input parameters.
35
+ **kwargs
36
+ Keyword arguments mapping parameter names to values, each of which may be a scalar or a list/array.
37
+
38
+ Returns
39
+ -------
40
+ dict
41
+ A dictionary with the same keys as the input parameters, where each value is a NumPy array
42
+ of length equal to the target size.
43
+
44
+ Raises
45
+ ------
46
+ ValueError
47
+ If any parameter provided as a list/array has a length greater than one that does not match
48
+ the target size, or if total_size is provided and is less than the maximum size among the inputs.
49
+ AssertionError
50
+ If total_size is provided and is not a positive integer.
51
+ """
52
+ if total_size is not None:
53
+ assert isinstance(total_size, int) and total_size > 0, "total_size must be a positive integer"
54
+
55
+ # Convert each parameter to a 1D NumPy array.
56
+ arrays = {name: np.atleast_1d(val) for name, val in kwargs.items()}
57
+
58
+ # Determine the target size.
59
+ target_size = total_size or max(arr.size for arr in arrays.values())
60
+
61
+ # Ensure no provided array of size > 1 mismatches the explicit target_size.
62
+ for name, arr in arrays.items():
63
+ if arr.size > 1 and arr.size != target_size:
64
+ raise ValueError(f"Inconsistent sizes: '{name}' has size {arr.size} but expected 1 or {target_size}.")
65
+
66
+ # Broadcast any array of size 1 to the target size.
67
+ for name, arr in arrays.items():
68
+ if arr.size == 1:
69
+ arrays[name] = np.full(target_size, arr.item())
70
+ if isinstance(arr, Quantity):
71
+ arrays[name] *= arr.units
72
+
73
+ return arrays
74
+
75
+
76
+ class Sequential:
77
+ is_sequential = False
78
+
79
+ @classmethod
80
+ def build_sequential(cls, total_size: Optional[int] = None, **kwargs) -> object:
81
+ """
82
+ Broadcast scalar or single-element parameters to NumPy arrays of uniform length
83
+ and construct a new instance of the class.
84
+
85
+ Each parameter passed via keyword arguments may be provided as a scalar or as a list/array.
86
+ Scalars or single-element lists/arrays will be repeated to match the length of the longest
87
+ parameter. If any parameter is given as a list/array with more than one element, its length
88
+ must equal the maximum length among all parameters (or the explicit `total_size` if provided);
89
+ otherwise, a ValueError is raised.
90
+
91
+ Additionally, if a keyword argument 'source' is provided, it is removed from the
92
+ broadcasted parameters and passed directly to the class constructor.
93
+
94
+ Parameters
95
+ ----------
96
+ total_size : int, optional
97
+ The explicit target size for broadcasting. Must be a positive integer if provided.
98
+ If not specified, the target size is determined by the maximum size among the input parameters.
99
+ **kwargs : dict
100
+ Arbitrary keyword arguments mapping parameter names to values. Each value may be a scalar
101
+ or a list/array of values. Examples include parameters such as `source`, `diameter`,
102
+ `property`, or `medium_property`.
103
+
104
+ Returns
105
+ -------
106
+ object
107
+ A new instance of the class with all provided parameters broadcasted as NumPy arrays
108
+ of equal length. If 'source' is provided, it is passed directly to the class constructor.
109
+
110
+ Raises
111
+ ------
112
+ ValueError
113
+ If any parameter provided as a list/array has a length greater than one that does not
114
+ match the target size.
115
+ """
116
+ is_material = any([
117
+ isinstance(v, BaseMaterial) for v in kwargs.values()
118
+ ])
119
+
120
+ assert not is_material, "For the moment no Material can be defined material property for sequential computing, one should use refractive index property. See documentation online."
121
+ source = kwargs.pop("source", None)
122
+ kwargs = broadcast_params(total_size=total_size, **kwargs)
123
+
124
+ if source is not None:
125
+ instance = cls(source=source, **kwargs)
126
+ else:
127
+ instance = cls(**kwargs)
128
+
129
+ instance.is_sequential = True
130
+
131
+ return instance
132
+
File without changes
PyMieSim/gui/helper.py ADDED
@@ -0,0 +1,60 @@
1
+ from PyMieSim.experiment.source.gaussian import Gaussian
2
+ from PyMieSim.experiment.scatterer.sphere import Sphere
3
+ from PyMieSim.experiment.detector.photodiode import Photodiode
4
+ from PyMieSim.experiment import Setup
5
+ from PyMieSim.units import nanometer, degree, milliwatt, AU, RIU
6
+ import numpy
7
+
8
+
9
+ length_units = nanometer
10
+ power_units = milliwatt
11
+ angle_units = degree
12
+
13
+
14
+ def parse_string_to_array_or_float(input_str):
15
+ """
16
+ Parse a string to return either a numpy array or a float.
17
+
18
+ If the string is in the format 'start:end:count', return np.linspace(start, end, count).
19
+ If the string is a comma-separated list, return a numpy array.
20
+ If the string is a single numeric value, return it as a float.
21
+ """
22
+ try:
23
+ if ":" in input_str:
24
+ start, end, count = map(float, input_str.split(":"))
25
+ return numpy.linspace(start, end, int(count))
26
+ elif "," in input_str:
27
+ return numpy.array(list(map(float, input_str.split(","))))
28
+ else:
29
+ return float(input_str)
30
+ except ValueError:
31
+ raise ValueError("Invalid input string format. Expected 'start:end:count', a comma-separated list, or a single numeric value.")
32
+
33
+
34
+ def interface(source_kwargs: dict, scatterer_kwargs: dict, detector_kwargs: dict, measure: str, **kwargs):
35
+ source = Gaussian(
36
+ wavelength=parse_string_to_array_or_float(source_kwargs['wavelength']) * length_units,
37
+ polarization=parse_string_to_array_or_float(source_kwargs['polarization']) * angle_units,
38
+ NA=parse_string_to_array_or_float(source_kwargs['NA']) * AU,
39
+ optical_power=parse_string_to_array_or_float(source_kwargs['optical_power']) * milliwatt
40
+ )
41
+
42
+ scatterer = Sphere(
43
+ diameter=parse_string_to_array_or_float(scatterer_kwargs['diameter']) * length_units,
44
+ property=parse_string_to_array_or_float(scatterer_kwargs['property']) * RIU,
45
+ medium_property=parse_string_to_array_or_float(scatterer_kwargs['medium_property']) * RIU,
46
+ source=source
47
+ )
48
+
49
+ detector = Photodiode(
50
+ NA=parse_string_to_array_or_float(detector_kwargs['NA']) * AU,
51
+ gamma_offset=parse_string_to_array_or_float(detector_kwargs['gamma_offset']) * angle_units,
52
+ phi_offset=parse_string_to_array_or_float(detector_kwargs['phi_offset']) * angle_units,
53
+ sampling=parse_string_to_array_or_float(detector_kwargs['sampling']) * AU,
54
+ )
55
+
56
+ setup = Setup(source=source, scatterer=scatterer, detector=detector)
57
+
58
+ dataframe = setup.get(measure, **kwargs)
59
+
60
+ return dataframe
@@ -0,0 +1,136 @@
1
+ from dash import Dash, html, dcc
2
+ import matplotlib.pyplot as plt
3
+ import io
4
+ import base64
5
+ import webbrowser
6
+ from PyMieSim.gui.section import SourceSection, ScattererSection, DetectorSection, MeasureSection
7
+ from PyMieSim.gui.helper import interface
8
+
9
+ dcc_store_id = "input-store"
10
+
11
+ class OpticalSetupGUI:
12
+ """
13
+ A class to manage the overall GUI for the optical simulation interface.
14
+
15
+ This class integrates various sections, including Source, Scatterer, Detector, and Measure sections,
16
+ and sets up the layout and callbacks for the Dash application.
17
+ """
18
+
19
+ def __init__(self):
20
+ """
21
+ Initialize the Dash app and other settings.
22
+
23
+ This method sets up the Dash application, initializes the individual sections, and prepares the layout
24
+ and callbacks.
25
+ """
26
+ plt.switch_backend('Agg')
27
+ self.app = Dash(__name__, external_stylesheets=["https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"])
28
+
29
+ self.source_section = SourceSection(self.app)
30
+ self.scatterer_section = ScattererSection(self.app)
31
+ self.detector_section = DetectorSection(self.app)
32
+ self.measure_section = MeasureSection(self.app, self.scatterer_section, self.source_section, self.detector_section)
33
+
34
+ self.setup_layout()
35
+ self.setup_callbacks()
36
+
37
+ def create_plot(self, measure: str, xaxis: str):
38
+ """
39
+ Generate a matplotlib plot and return it as a base64 image.
40
+
41
+ Parameters
42
+ ----------
43
+ measure : str
44
+ The type of measure to plot (e.g., Qsca, Qext).
45
+ xaxis : str
46
+ The parameter to plot on the x-axis.
47
+
48
+ Returns
49
+ -------
50
+ str
51
+ A base64-encoded string of the generated plot image.
52
+ """
53
+ data = interface(
54
+ source_kwargs=self.source_section.data,
55
+ scatterer_kwargs=self.scatterer_section.data,
56
+ detector_kwargs=self.detector_section.data,
57
+ measure=measure
58
+ )
59
+
60
+ data.plot(x=xaxis)
61
+
62
+ buf = io.BytesIO()
63
+ plt.savefig(buf, format='png')
64
+ buf.seek(0)
65
+ encoded_image = base64.b64encode(buf.read()).decode('utf-8')
66
+ buf.close()
67
+ return f"data:image/png;base64,{encoded_image}"
68
+
69
+ def save_func(self, filename: str, measure: str):
70
+ """
71
+ Save the simulation data to a CSV file.
72
+
73
+ Parameters
74
+ ----------
75
+ filename : str
76
+ The name of the file to save the data.
77
+ measure : str
78
+ The type of measure to save.
79
+ xaxis : str
80
+ The parameter to include on the x-axis in the saved data.
81
+ """
82
+ return interface(
83
+ source_kwargs=self.source_section.data,
84
+ scatterer_kwargs=self.scatterer_section.data,
85
+ detector_kwargs=self.detector_section.data,
86
+ measure=measure,
87
+ add_units=False
88
+ )
89
+
90
+ def setup_layout(self):
91
+ """
92
+ Define the layout of the Dash app.
93
+
94
+ This method organizes the sections and visualization components in the application layout.
95
+ """
96
+ self.app.layout = html.Div([
97
+ dcc.Store(id=dcc_store_id), # Store to save input values
98
+ html.Div([
99
+ html.H1("Optical Simulation Interface", style={'text-align': 'center'}),
100
+ *self.source_section.create(),
101
+ *self.scatterer_section.create(),
102
+ *self.detector_section.create(),
103
+ ], style={'width': '40%', 'float': 'left', 'padding': '10px'}),
104
+
105
+ html.Div([
106
+ html.H1("Visualization", style={'text-align': 'center'}),
107
+ self.measure_section.create(),
108
+ html.Img(id="plot-image", style={'width': '100%', 'height': 'auto', 'margin-top': '20px'})
109
+ ], style={'width': '55%', 'float': 'right', 'padding': '10px', 'border-left': '1px solid black'})
110
+ ])
111
+
112
+ def setup_callbacks(self):
113
+ """
114
+ Set up Dash callbacks for the GUI.
115
+
116
+ This method links user interactions with the relevant functions to update the plot and save data.
117
+ """
118
+ self.measure_section.update_callbacks(self.create_plot, self.save_func)
119
+
120
+ def run(self, host: str = "0.0.0.0", port: str = "8050", open_browser: bool = False, debug: bool = False):
121
+ """
122
+ Run the Dash app.
123
+
124
+ This method starts the Dash server and opens the application in the default web browser.
125
+ """
126
+ if open_browser:
127
+ webbrowser.open(f"http://{host}:{port}/", new=2)
128
+ self.app.run_server(debug=debug)
129
+
130
+ else:
131
+ self.app.run_server(debug=debug, host=host, port=port)
132
+
133
+
134
+ if __name__ == '__main__':
135
+ _gui = OpticalSetupGUI()
136
+ _gui.run(host='0.0.0.0', port='8050', open_browser=False, debug=True)