├── .gitattributes ├── .gitignore ├── .readthedocs.yaml ├── LICENSE.lic ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst ├── listings.rst ├── make.bat ├── modules.rst ├── notebooks.rst ├── notebooks │ ├── GDS_operations.ipynb │ ├── Generate_masks.ipynb │ ├── Hologram_masks.ipynb │ ├── Import_export.ipynb │ ├── Metasurfaces_masks.ipynb │ └── Propagate_masks.ipynb ├── pyMOE.rst ├── requirements.txt ├── source │ ├── conf.py │ ├── index.rst │ ├── modules.rst │ ├── pyMOE.rst │ └── requirements.txt ├── test.txt └── tests.rst ├── notebooks ├── 1 - Generate_masks │ └── Generate_masks.ipynb ├── 2 - Metasurfaces │ ├── Metasurfaces_masks.ipynb │ ├── circle.gds │ ├── dvar_638nm_p445nm.txt │ ├── phaserad_638nm_p445nm.txt │ ├── rectangle.gds │ └── triangle.gds ├── 3 - Import_export │ ├── Import_export.ipynb │ └── calibration.csv ├── 4 - GDS_operations │ └── GDS_operations.ipynb ├── 5 - Holograms │ ├── Hologram_masks.ipynb │ └── target.png └── 6 - Propagation │ └── Propagate_masks.ipynb ├── pyMOE ├── __init__.py ├── aperture.py ├── dither.py ├── export.py ├── field.py ├── gds_klops.py ├── gdsconverter.py ├── generate.py ├── holograms.py ├── importing.py ├── metas.py ├── plotting.py ├── propagate.py ├── sag_functions.py └── utils.py ├── requirements.txt └── tests ├── __init__.py ├── test_aperture.py ├── test_basic.py ├── test_gdsconverter.py ├── test_generate.py ├── test_holograms.py └── test_imports.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.py linguist-language=python 2 | *.ipynb linguist-documentation -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | 132 | 133 | *.gds 134 | *.dxf 135 | *.stl 136 | *.png 137 | *.svg 138 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools you might need 9 | build: 10 | os: "ubuntu-22.04" 11 | tools: 12 | python: "3.9" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the "docs/" directory with Sphinx 19 | sphinx: 20 | configuration: docs/conf.py 21 | 22 | # Optionally build your docs in additional formats such as PDF and ePub 23 | # formats: 24 | # - pdf 25 | # - epub 26 | 27 | # Optional but recommended, declare the Python requirements required 28 | # to build your documentation 29 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 30 | python: 31 | install: 32 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /LICENSE.lic: -------------------------------------------------------------------------------- 1 | 2 | CeCILL-B FREE SOFTWARE LICENSE AGREEMENT 3 | 4 | 5 | Notice 6 | 7 | This Agreement is a Free Software license agreement that is the result 8 | of discussions between its authors in order to ensure compliance with 9 | the two main principles guiding its drafting: 10 | 11 | * firstly, compliance with the principles governing the distribution 12 | of Free Software: access to source code, broad rights granted to 13 | users, 14 | * secondly, the election of a governing law, French law, with which 15 | it is conformant, both as regards the law of torts and 16 | intellectual property law, and the protection that it offers to 17 | both authors and holders of the economic rights over software. 18 | 19 | The authors of the CeCILL-B (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) 20 | license are: 21 | 22 | Commissariat à l'Energie Atomique - CEA, a public scientific, technical 23 | and industrial research establishment, having its principal place of 24 | business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France. 25 | 26 | Centre National de la Recherche Scientifique - CNRS, a public scientific 27 | and technological establishment, having its principal place of business 28 | at 3 rue Michel-Ange, 75794 Paris cedex 16, France. 29 | 30 | Institut National de Recherche en Informatique et en Automatique - 31 | INRIA, a public scientific and technological establishment, having its 32 | principal place of business at Domaine de Voluceau, Rocquencourt, BP 33 | 105, 78153 Le Chesnay cedex, France. 34 | 35 | 36 | Preamble 37 | 38 | This Agreement is an open source software license intended to give users 39 | significant freedom to modify and redistribute the software licensed 40 | hereunder. 41 | 42 | The exercising of this freedom is conditional upon a strong obligation 43 | of giving credits for everybody that distributes a software 44 | incorporating a software ruled by the current license so as all 45 | contributions to be properly identified and acknowledged. 46 | 47 | In consideration of access to the source code and the rights to copy, 48 | modify and redistribute granted by the license, users are provided only 49 | with a limited warranty and the software's author, the holder of the 50 | economic rights, and the successive licensors only have limited liability. 51 | 52 | In this respect, the risks associated with loading, using, modifying 53 | and/or developing or reproducing the software by the user are brought to 54 | the user's attention, given its Free Software status, which may make it 55 | complicated to use, with the result that its use is reserved for 56 | developers and experienced professionals having in-depth computer 57 | knowledge. Users are therefore encouraged to load and test the 58 | suitability of the software as regards their requirements in conditions 59 | enabling the security of their systems and/or data to be ensured and, 60 | more generally, to use and operate it in the same conditions of 61 | security. This Agreement may be freely reproduced and published, 62 | provided it is not altered, and that no provisions are either added or 63 | removed herefrom. 64 | 65 | This Agreement may apply to any or all software for which the holder of 66 | the economic rights decides to submit the use thereof to its provisions. 67 | 68 | 69 | Article 1 - DEFINITIONS 70 | 71 | For the purpose of this Agreement, when the following expressions 72 | commence with a capital letter, they shall have the following meaning: 73 | 74 | Agreement: means this license agreement, and its possible subsequent 75 | versions and annexes. 76 | 77 | Software: means the software in its Object Code and/or Source Code form 78 | and, where applicable, its documentation, "as is" when the Licensee 79 | accepts the Agreement. 80 | 81 | Initial Software: means the Software in its Source Code and possibly its 82 | Object Code form and, where applicable, its documentation, "as is" when 83 | it is first distributed under the terms and conditions of the Agreement. 84 | 85 | Modified Software: means the Software modified by at least one 86 | Contribution. 87 | 88 | Source Code: means all the Software's instructions and program lines to 89 | which access is required so as to modify the Software. 90 | 91 | Object Code: means the binary files originating from the compilation of 92 | the Source Code. 93 | 94 | Holder: means the holder(s) of the economic rights over the Initial 95 | Software. 96 | 97 | Licensee: means the Software user(s) having accepted the Agreement. 98 | 99 | Contributor: means a Licensee having made at least one Contribution. 100 | 101 | Licensor: means the Holder, or any other individual or legal entity, who 102 | distributes the Software under the Agreement. 103 | 104 | Contribution: means any or all modifications, corrections, translations, 105 | adaptations and/or new functions integrated into the Software by any or 106 | all Contributors, as well as any or all Internal Modules. 107 | 108 | Module: means a set of sources files including their documentation that 109 | enables supplementary functions or services in addition to those offered 110 | by the Software. 111 | 112 | External Module: means any or all Modules, not derived from the 113 | Software, so that this Module and the Software run in separate address 114 | spaces, with one calling the other when they are run. 115 | 116 | Internal Module: means any or all Module, connected to the Software so 117 | that they both execute in the same address space. 118 | 119 | Parties: mean both the Licensee and the Licensor. 120 | 121 | These expressions may be used both in singular and plural form. 122 | 123 | 124 | Article 2 - PURPOSE 125 | 126 | The purpose of the Agreement is the grant by the Licensor to the 127 | Licensee of a non-exclusive, transferable and worldwide license for the 128 | Software as set forth in Article 5 hereinafter for the whole term of the 129 | protection granted by the rights over said Software. 130 | 131 | 132 | Article 3 - ACCEPTANCE 133 | 134 | 3.1 The Licensee shall be deemed as having accepted the terms and 135 | conditions of this Agreement upon the occurrence of the first of the 136 | following events: 137 | 138 | * (i) loading the Software by any or all means, notably, by 139 | downloading from a remote server, or by loading from a physical 140 | medium; 141 | * (ii) the first time the Licensee exercises any of the rights 142 | granted hereunder. 143 | 144 | 3.2 One copy of the Agreement, containing a notice relating to the 145 | characteristics of the Software, to the limited warranty, and to the 146 | fact that its use is restricted to experienced users has been provided 147 | to the Licensee prior to its acceptance as set forth in Article 3.1 148 | hereinabove, and the Licensee hereby acknowledges that it has read and 149 | understood it. 150 | 151 | 152 | Article 4 - EFFECTIVE DATE AND TERM 153 | 154 | 155 | 4.1 EFFECTIVE DATE 156 | 157 | The Agreement shall become effective on the date when it is accepted by 158 | the Licensee as set forth in Article 3.1. 159 | 160 | 161 | 4.2 TERM 162 | 163 | The Agreement shall remain in force for the entire legal term of 164 | protection of the economic rights over the Software. 165 | 166 | 167 | Article 5 - SCOPE OF RIGHTS GRANTED 168 | 169 | The Licensor hereby grants to the Licensee, who accepts, the following 170 | rights over the Software for any or all use, and for the term of the 171 | Agreement, on the basis of the terms and conditions set forth hereinafter. 172 | 173 | Besides, if the Licensor owns or comes to own one or more patents 174 | protecting all or part of the functions of the Software or of its 175 | components, the Licensor undertakes not to enforce the rights granted by 176 | these patents against successive Licensees using, exploiting or 177 | modifying the Software. If these patents are transferred, the Licensor 178 | undertakes to have the transferees subscribe to the obligations set 179 | forth in this paragraph. 180 | 181 | 182 | 5.1 RIGHT OF USE 183 | 184 | The Licensee is authorized to use the Software, without any limitation 185 | as to its fields of application, with it being hereinafter specified 186 | that this comprises: 187 | 188 | 1. permanent or temporary reproduction of all or part of the Software 189 | by any or all means and in any or all form. 190 | 191 | 2. loading, displaying, running, or storing the Software on any or 192 | all medium. 193 | 194 | 3. entitlement to observe, study or test its operation so as to 195 | determine the ideas and principles behind any or all constituent 196 | elements of said Software. This shall apply when the Licensee 197 | carries out any or all loading, displaying, running, transmission 198 | or storage operation as regards the Software, that it is entitled 199 | to carry out hereunder. 200 | 201 | 202 | 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS 203 | 204 | The right to make Contributions includes the right to translate, adapt, 205 | arrange, or make any or all modifications to the Software, and the right 206 | to reproduce the resulting software. 207 | 208 | The Licensee is authorized to make any or all Contributions to the 209 | Software provided that it includes an explicit notice that it is the 210 | author of said Contribution and indicates the date of the creation thereof. 211 | 212 | 213 | 5.3 RIGHT OF DISTRIBUTION 214 | 215 | In particular, the right of distribution includes the right to publish, 216 | transmit and communicate the Software to the general public on any or 217 | all medium, and by any or all means, and the right to market, either in 218 | consideration of a fee, or free of charge, one or more copies of the 219 | Software by any means. 220 | 221 | The Licensee is further authorized to distribute copies of the modified 222 | or unmodified Software to third parties according to the terms and 223 | conditions set forth hereinafter. 224 | 225 | 226 | 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION 227 | 228 | The Licensee is authorized to distribute true copies of the Software in 229 | Source Code or Object Code form, provided that said distribution 230 | complies with all the provisions of the Agreement and is accompanied by: 231 | 232 | 1. a copy of the Agreement, 233 | 234 | 2. a notice relating to the limitation of both the Licensor's 235 | warranty and liability as set forth in Articles 8 and 9, 236 | 237 | and that, in the event that only the Object Code of the Software is 238 | redistributed, the Licensee allows effective access to the full Source 239 | Code of the Software at a minimum during the entire period of its 240 | distribution of the Software, it being understood that the additional 241 | cost of acquiring the Source Code shall not exceed the cost of 242 | transferring the data. 243 | 244 | 245 | 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE 246 | 247 | If the Licensee makes any Contribution to the Software, the resulting 248 | Modified Software may be distributed under a license agreement other 249 | than this Agreement subject to compliance with the provisions of Article 250 | 5.3.4. 251 | 252 | 253 | 5.3.3 DISTRIBUTION OF EXTERNAL MODULES 254 | 255 | When the Licensee has developed an External Module, the terms and 256 | conditions of this Agreement do not apply to said External Module, that 257 | may be distributed under a separate license agreement. 258 | 259 | 260 | 5.3.4 CREDITS 261 | 262 | Any Licensee who may distribute a Modified Software hereby expressly 263 | agrees to: 264 | 265 | 1. indicate in the related documentation that it is based on the 266 | Software licensed hereunder, and reproduce the intellectual 267 | property notice for the Software, 268 | 269 | 2. ensure that written indications of the Software intended use, 270 | intellectual property notice and license hereunder are included in 271 | easily accessible format from the Modified Software interface, 272 | 273 | 3. mention, on a freely accessible website describing the Modified 274 | Software, at least throughout the distribution term thereof, that 275 | it is based on the Software licensed hereunder, and reproduce the 276 | Software intellectual property notice, 277 | 278 | 4. where it is distributed to a third party that may distribute a 279 | Modified Software without having to make its source code 280 | available, make its best efforts to ensure that said third party 281 | agrees to comply with the obligations set forth in this Article . 282 | 283 | If the Software, whether or not modified, is distributed with an 284 | External Module designed for use in connection with the Software, the 285 | Licensee shall submit said External Module to the foregoing obligations. 286 | 287 | 288 | 5.3.5 COMPATIBILITY WITH THE CeCILL AND CeCILL-C LICENSES 289 | 290 | Where a Modified Software contains a Contribution subject to the CeCILL 291 | license, the provisions set forth in Article 5.3.4 shall be optional. 292 | 293 | A Modified Software may be distributed under the CeCILL-C license. In 294 | such a case the provisions set forth in Article 5.3.4 shall be optional. 295 | 296 | 297 | Article 6 - INTELLECTUAL PROPERTY 298 | 299 | 300 | 6.1 OVER THE INITIAL SOFTWARE 301 | 302 | The Holder owns the economic rights over the Initial Software. Any or 303 | all use of the Initial Software is subject to compliance with the terms 304 | and conditions under which the Holder has elected to distribute its work 305 | and no one shall be entitled to modify the terms and conditions for the 306 | distribution of said Initial Software. 307 | 308 | The Holder undertakes that the Initial Software will remain ruled at 309 | least by this Agreement, for the duration set forth in Article 4.2. 310 | 311 | 312 | 6.2 OVER THE CONTRIBUTIONS 313 | 314 | The Licensee who develops a Contribution is the owner of the 315 | intellectual property rights over this Contribution as defined by 316 | applicable law. 317 | 318 | 319 | 6.3 OVER THE EXTERNAL MODULES 320 | 321 | The Licensee who develops an External Module is the owner of the 322 | intellectual property rights over this External Module as defined by 323 | applicable law and is free to choose the type of agreement that shall 324 | govern its distribution. 325 | 326 | 327 | 6.4 JOINT PROVISIONS 328 | 329 | The Licensee expressly undertakes: 330 | 331 | 1. not to remove, or modify, in any manner, the intellectual property 332 | notices attached to the Software; 333 | 334 | 2. to reproduce said notices, in an identical manner, in the copies 335 | of the Software modified or not. 336 | 337 | The Licensee undertakes not to directly or indirectly infringe the 338 | intellectual property rights of the Holder and/or Contributors on the 339 | Software and to take, where applicable, vis-à-vis its staff, any and all 340 | measures required to ensure respect of said intellectual property rights 341 | of the Holder and/or Contributors. 342 | 343 | 344 | Article 7 - RELATED SERVICES 345 | 346 | 7.1 Under no circumstances shall the Agreement oblige the Licensor to 347 | provide technical assistance or maintenance services for the Software. 348 | 349 | However, the Licensor is entitled to offer this type of services. The 350 | terms and conditions of such technical assistance, and/or such 351 | maintenance, shall be set forth in a separate instrument. Only the 352 | Licensor offering said maintenance and/or technical assistance services 353 | shall incur liability therefor. 354 | 355 | 7.2 Similarly, any Licensor is entitled to offer to its licensees, under 356 | its sole responsibility, a warranty, that shall only be binding upon 357 | itself, for the redistribution of the Software and/or the Modified 358 | Software, under terms and conditions that it is free to decide. Said 359 | warranty, and the financial terms and conditions of its application, 360 | shall be subject of a separate instrument executed between the Licensor 361 | and the Licensee. 362 | 363 | 364 | Article 8 - LIABILITY 365 | 366 | 8.1 Subject to the provisions of Article 8.2, the Licensee shall be 367 | entitled to claim compensation for any direct loss it may have suffered 368 | from the Software as a result of a fault on the part of the relevant 369 | Licensor, subject to providing evidence thereof. 370 | 371 | 8.2 The Licensor's liability is limited to the commitments made under 372 | this Agreement and shall not be incurred as a result of in particular: 373 | (i) loss due the Licensee's total or partial failure to fulfill its 374 | obligations, (ii) direct or consequential loss that is suffered by the 375 | Licensee due to the use or performance of the Software, and (iii) more 376 | generally, any consequential loss. In particular the Parties expressly 377 | agree that any or all pecuniary or business loss (i.e. loss of data, 378 | loss of profits, operating loss, loss of customers or orders, 379 | opportunity cost, any disturbance to business activities) or any or all 380 | legal proceedings instituted against the Licensee by a third party, 381 | shall constitute consequential loss and shall not provide entitlement to 382 | any or all compensation from the Licensor. 383 | 384 | 385 | Article 9 - WARRANTY 386 | 387 | 9.1 The Licensee acknowledges that the scientific and technical 388 | state-of-the-art when the Software was distributed did not enable all 389 | possible uses to be tested and verified, nor for the presence of 390 | possible defects to be detected. In this respect, the Licensee's 391 | attention has been drawn to the risks associated with loading, using, 392 | modifying and/or developing and reproducing the Software which are 393 | reserved for experienced users. 394 | 395 | The Licensee shall be responsible for verifying, by any or all means, 396 | the suitability of the product for its requirements, its good working 397 | order, and for ensuring that it shall not cause damage to either persons 398 | or properties. 399 | 400 | 9.2 The Licensor hereby represents, in good faith, that it is entitled 401 | to grant all the rights over the Software (including in particular the 402 | rights set forth in Article 5). 403 | 404 | 9.3 The Licensee acknowledges that the Software is supplied "as is" by 405 | the Licensor without any other express or tacit warranty, other than 406 | that provided for in Article 9.2 and, in particular, without any warranty 407 | as to its commercial value, its secured, safe, innovative or relevant 408 | nature. 409 | 410 | Specifically, the Licensor does not warrant that the Software is free 411 | from any error, that it will operate without interruption, that it will 412 | be compatible with the Licensee's own equipment and software 413 | configuration, nor that it will meet the Licensee's requirements. 414 | 415 | 9.4 The Licensor does not either expressly or tacitly warrant that the 416 | Software does not infringe any third party intellectual property right 417 | relating to a patent, software or any other property right. Therefore, 418 | the Licensor disclaims any and all liability towards the Licensee 419 | arising out of any or all proceedings for infringement that may be 420 | instituted in respect of the use, modification and redistribution of the 421 | Software. Nevertheless, should such proceedings be instituted against 422 | the Licensee, the Licensor shall provide it with technical and legal 423 | assistance for its defense. Such technical and legal assistance shall be 424 | decided on a case-by-case basis between the relevant Licensor and the 425 | Licensee pursuant to a memorandum of understanding. The Licensor 426 | disclaims any and all liability as regards the Licensee's use of the 427 | name of the Software. No warranty is given as regards the existence of 428 | prior rights over the name of the Software or as regards the existence 429 | of a trademark. 430 | 431 | 432 | Article 10 - TERMINATION 433 | 434 | 10.1 In the event of a breach by the Licensee of its obligations 435 | hereunder, the Licensor may automatically terminate this Agreement 436 | thirty (30) days after notice has been sent to the Licensee and has 437 | remained ineffective. 438 | 439 | 10.2 A Licensee whose Agreement is terminated shall no longer be 440 | authorized to use, modify or distribute the Software. However, any 441 | licenses that it may have granted prior to termination of the Agreement 442 | shall remain valid subject to their having been granted in compliance 443 | with the terms and conditions hereof. 444 | 445 | 446 | Article 11 - MISCELLANEOUS 447 | 448 | 449 | 11.1 EXCUSABLE EVENTS 450 | 451 | Neither Party shall be liable for any or all delay, or failure to 452 | perform the Agreement, that may be attributable to an event of force 453 | majeure, an act of God or an outside cause, such as defective 454 | functioning or interruptions of the electricity or telecommunications 455 | networks, network paralysis following a virus attack, intervention by 456 | government authorities, natural disasters, water damage, earthquakes, 457 | fire, explosions, strikes and labor unrest, war, etc. 458 | 459 | 11.2 Any failure by either Party, on one or more occasions, to invoke 460 | one or more of the provisions hereof, shall under no circumstances be 461 | interpreted as being a waiver by the interested Party of its right to 462 | invoke said provision(s) subsequently. 463 | 464 | 11.3 The Agreement cancels and replaces any or all previous agreements, 465 | whether written or oral, between the Parties and having the same 466 | purpose, and constitutes the entirety of the agreement between said 467 | Parties concerning said purpose. No supplement or modification to the 468 | terms and conditions hereof shall be effective as between the Parties 469 | unless it is made in writing and signed by their duly authorized 470 | representatives. 471 | 472 | 11.4 In the event that one or more of the provisions hereof were to 473 | conflict with a current or future applicable act or legislative text, 474 | said act or legislative text shall prevail, and the Parties shall make 475 | the necessary amendments so as to comply with said act or legislative 476 | text. All other provisions shall remain effective. Similarly, invalidity 477 | of a provision of the Agreement, for any reason whatsoever, shall not 478 | cause the Agreement as a whole to be invalid. 479 | 480 | 481 | 11.5 LANGUAGE 482 | 483 | The Agreement is drafted in both French and English and both versions 484 | are deemed authentic. 485 | 486 | 487 | Article 12 - NEW VERSIONS OF THE AGREEMENT 488 | 489 | 12.1 Any person is authorized to duplicate and distribute copies of this 490 | Agreement. 491 | 492 | 12.2 So as to ensure coherence, the wording of this Agreement is 493 | protected and may only be modified by the authors of the License, who 494 | reserve the right to periodically publish updates or new versions of the 495 | Agreement, each with a separate number. These subsequent versions may 496 | address new issues encountered by Free Software. 497 | 498 | 12.3 Any Software distributed under a given version of the Agreement may 499 | only be subsequently distributed under the same version of the Agreement 500 | or a subsequent version. 501 | 502 | 503 | Article 13 - GOVERNING LAW AND JURISDICTION 504 | 505 | 13.1 The Agreement is governed by French law. The Parties agree to 506 | endeavor to seek an amicable solution to any disagreements or disputes 507 | that may arise during the performance of the Agreement. 508 | 509 | 13.2 Failing an amicable solution within two (2) months as from their 510 | occurrence, and unless emergency proceedings are necessary, the 511 | disagreements or disputes shall be referred to the Paris Courts having 512 | jurisdiction, by the more diligent Party. 513 | 514 | 515 | Version 1.0 dated 2006-09-05. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyMOE 2 | Python library for designing and producing masks for Micro Optical Elements. 3 | 4 | 5 | # Features 6 | Mask design features: 7 | * Designing multi-layer (grayscale) masks from analytical and/or numerical (e.g. Gerchberg–Saxton) methods 8 | * Designing single layer (binary) dithered masks from grayscale masks 9 | * Designing rudimentary metasurfaces masks 10 | * Numerical scalar propagation methods (Rayleigh-Sommerfeld, Fresnel, Fraunhofer) of electric fields from (grayscale) masks 11 | 12 | Mask production features: 13 | * Image file (binary/grayscale) <-> CAD files input-output conversion 14 | * Operations with/within mask files (e.g. change layer numbers, make intances, CAD file import, merge shapes, ...) 15 | 16 | 17 | # Getting started 18 | 19 | For background, theory, package structure, practical results and discussion of package's performance, please refer to the publication: https://doi.org/10.1016/j.cpc.2024.109331 20 | 21 | For a practical walk through on how to use the package, besides the aforementioned publication, please follow the example notebooks inside `notebooks\` directory. For running we recommend having Jupyter Notebook or Lab with a Python distribution >3.7, as well as required dependencies. 22 | 23 | 24 | # Documentation 25 | 26 | For documentation please visit https://pymoe-doc.readthedocs.io 27 | 28 | 29 | # Dependencies 30 | 31 | A list of dependencies (with versions) is at https://github.com/INLnano/pyMOE/blob/main/requirements.txt . To use the package straighforwardly please make sure you have these dependencies (and versions) installed. 32 | 33 | 34 | # Test 35 | 36 | From root folder run 37 | ``` 38 | pytest 39 | ``` 40 | 41 | 42 | # Citing pyMOE 43 | 44 | If pyMOE was useful for you, please cite the companion paper published in Computer Physics Communications (https://doi.org/10.1016/j.cpc.2024.109331). 45 | 46 | Bibtex entry: \ 47 | @article{CUNHA2024109331,\ 48 | author = {Joao Cunha and José Queiroz and Carlos Silva and Fabio Gentile and Diogo E. Aguiam},\ 49 | title = {pyMOE: Mask Design and Modelling for Micro Optical Elements and Flat Optics},\ 50 | journal = {Computer Physics Communications},\ 51 | pages = {109331},\ 52 | year = {2024},\ 53 | issn = {0010-4655},\ 54 | doi = {https://doi.org/10.1016/j.cpc.2024.109331} 55 | } 56 | 57 | 58 | # Zenodo repository 59 | 60 | pyMOE releases are also mirrored in Zenodo at https://zenodo.org/records/12737201. 61 | 62 | 63 | # Interactions and Contributions 64 | 65 | To interact/contribute please use the following: 66 | * **Email contact**: Please feel free to contact via email - for email addresses, please use the email(s) provided at https://doi.org/10.1016/j.cpc.2024.109331. 67 | * **Issues page**: Please feel free to post suggestions, bug reports, code malfunctions, or requests in the Issues page (https://github.com/INLnano/pyMOE/issues). Those will be cathegorized and addressed in due time. 68 | * **Code contributions**: To contribute, please (1) fork the repository, (2) commit your code changes in your forked repository, and (3) make a pull request *to a dev-(...) branch* of this pyMOE repository (*pull requests directly to pyMOE's main branch will not be accepted*). -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = 'pyMOE' 10 | copyright = '2023-2024, J. Cunha, D. E. Aguiam' 11 | author = 'J. Cunha, D. E. Aguiam' 12 | release = 'v1.4.2' 13 | import os # line 13 14 | import sys # line 14 15 | import sphinx_rtd_theme 16 | 17 | 18 | sys.path.insert(0, os.path.abspath('../pyMOE/')) 19 | sys.path.insert(0, os.path.abspath('..')) 20 | sys.path.insert(0, os.path.abspath('../..')) 21 | # -- General configuration --------------------------------------------------- 22 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 23 | 24 | extensions = ['sphinx.ext.autodoc', 'sphinx_rtd_theme', 'nbsphinx', 'sphinx_gallery.load_style'] 25 | 26 | #pdf_documents = [('index', u'rst2pdf', u'Sample rst2pdf doc', u'Your Name'),] 27 | 28 | templates_path = ['_templates'] 29 | exclude_patterns = [] 30 | 31 | autoclass_content = 'both' 32 | 33 | # -- Options for HTML output ------------------------------------------------- 34 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 35 | 36 | html_theme = 'sphinx_rtd_theme' 37 | #html_static_path = ['_static'] 38 | #html_theme = "sphinx_rtd_theme" 39 | 40 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 41 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyMOE documentation master file, created by 2 | sphinx-quickstart on Wed Aug 23 11:35:11 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | 11 | Welcome to pyMOE's documentation! 12 | ================================= 13 | 14 | pyMOE is a Python library for designing and producing masks for Micro Optical Elements. 15 | 16 | Features 17 | ******** 18 | 19 | Mask design features: 20 | 21 | * Designing multi-layer (grayscale) masks from analytical and/or numerical (e.g. Gerchberg–Saxton) methods 22 | 23 | * Designing single layer (binary) dithered masks from grayscale masks 24 | 25 | * Designing rudimentary metasurfaces masks 26 | 27 | * Numerical scalar propagation methods (Rayleigh-Sommerfeld, Fresnel, Fraunhofer) of electric fields from (grayscale) masks 28 | 29 | Mask production features: 30 | 31 | * Image file (binary/grayscale) <-> CAD files input-output conversion 32 | 33 | * Operations with/within mask files (e.g. change layer numbers, make intances, CAD file import, merge shapes, ...) 34 | 35 | 36 | Dependencies 37 | ************ 38 | 39 | A list of dependencies (with versions) is at https://github.com/INLnano/pyMOE/blob/main/requirements.txt . To use the package straighforwardly please make sure you have these dependencies (and versions) installed. 40 | 41 | 42 | Modular architecture 43 | ********************* 44 | 45 | pyMOE is based on a modular architecture, where each module provides specific functionalities. The use of each module and its inter-modular connections is exemplified at https://www.sciencedirect.com/science/article/pii/S0010465524002546#fg0060 and practically in the example notebooks (there is one notebook for each module). 46 | 47 | 48 | Literature 49 | ********** 50 | 51 | For background, theory, package structure, practical results and discussion of package's performance, please refer to the publication: 52 | 53 | J. Cunha, J. Queiroz, C. Silva, F. Gentile and D. E. Aguiam, pyMOE: Mask Design and Modelling for Micro Optical Elements and Flat Optics, Computer Physics Communications 109331 (2024). Link: https://doi.org/10.1016/j.cpc.2024.109331 54 | 55 | 56 | Example Notebooks 57 | ***************** 58 | 59 | Check the notebooks in the sidebar menu. 60 | 61 | .. toctree:: 62 | notebooks 63 | 64 | Full function/class listings 65 | **************************** 66 | 67 | Please check the function/class Indices and Tables in the sidebar menu or use Search functionality. 68 | 69 | .. toctree:: 70 | listings -------------------------------------------------------------------------------- /docs/listings.rst: -------------------------------------------------------------------------------- 1 | Full function/class listings 2 | ============================ 3 | 4 | Please check the Indices and tables links below: 5 | 6 | * :ref:`genindex` 7 | * :ref:`modindex` 8 | * :ref:`search` 9 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | pyMOE-v1.0 2 | ========== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | pyMOE 8 | tests 9 | -------------------------------------------------------------------------------- /docs/notebooks.rst: -------------------------------------------------------------------------------- 1 | Example Notebooks 2 | ================= 3 | 4 | Check the notebooks in the menu. 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | :glob: 9 | 10 | notebooks/Generate_masks.ipynb 11 | notebooks/Metasurfaces_masks.ipynb 12 | notebooks/Import_export.ipynb 13 | notekoobs/GDS_operations.ipynb 14 | notebooks/Hologram_masks.ipynb 15 | notebooks/Propagate_masks.ipynb -------------------------------------------------------------------------------- /docs/pyMOE.rst: -------------------------------------------------------------------------------- 1 | pyMOE package 2 | ============= 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyMOE.aperture module 8 | --------------------- 9 | 10 | .. automodule:: pyMOE.aperture 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyMOE.dither module 16 | ------------------- 17 | 18 | .. automodule:: pyMOE.dither 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pyMOE.export module 24 | ------------------- 25 | 26 | .. automodule:: pyMOE.export 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pyMOE.gds\_klops module 32 | ----------------------- 33 | 34 | .. automodule:: pyMOE.gds_klops 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pyMOE.gdsconverter module 40 | ------------------------- 41 | 42 | .. automodule:: pyMOE.gdsconverter 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | pyMOE.generate module 48 | --------------------- 49 | 50 | .. automodule:: pyMOE.generate 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | pyMOE.holograms module 56 | ---------------------- 57 | 58 | .. automodule:: pyMOE.holograms 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | pyMOE.importing module 64 | ---------------------- 65 | 66 | .. automodule:: pyMOE.importing 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | pyMOE.metas module 72 | ------------------ 73 | 74 | .. automodule:: pyMOE.metas 75 | :members: 76 | :undoc-members: 77 | :show-inheritance: 78 | 79 | pyMOE.plotting module 80 | --------------------- 81 | 82 | .. automodule:: pyMOE.plotting 83 | :members: 84 | :undoc-members: 85 | :show-inheritance: 86 | 87 | pyMOE.propagate module 88 | ---------------------- 89 | 90 | .. automodule:: pyMOE.propagate 91 | :members: 92 | :undoc-members: 93 | :show-inheritance: 94 | 95 | pyMOE.sag\_functions module 96 | --------------------------- 97 | 98 | .. automodule:: pyMOE.sag_functions 99 | :members: 100 | :undoc-members: 101 | :show-inheritance: 102 | 103 | pyMOE.utils module 104 | ------------------ 105 | 106 | .. automodule:: pyMOE.utils 107 | :members: 108 | :undoc-members: 109 | :show-inheritance: 110 | 111 | Module contents 112 | --------------- 113 | 114 | .. automodule:: pyMOE 115 | :members: 116 | :undoc-members: 117 | :show-inheritance: 118 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx_rtd_theme==1.3.0 2 | sphinx>=1.4 3 | ipykernel 4 | nbsphinx 5 | sphinx-gallery 6 | opencv-python 7 | gdspy==1.6.3 8 | klayout==0.26.12 9 | Pillow==8.1.1 10 | scipy 11 | matplotlib 12 | numpy 13 | dask 14 | zernike 15 | shapely==1.7.1 -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = 'pyMOE' 10 | copyright = '2023 - now, J. Cunha, D. E. Aguiam' 11 | author = 'J. Cunha, D. E. Aguiam' 12 | release = 'v1.4.2' 13 | import os # line 13 14 | import sys # line 14 15 | import sphinx_rtd_theme 16 | 17 | 18 | sys.path.insert(0, os.path.abspath('../../pyMOE/')) 19 | sys.path.insert(0, os.path.abspath('..')) 20 | sys.path.insert(0, os.path.abspath('../..')) 21 | # -- General configuration --------------------------------------------------- 22 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 23 | 24 | extensions = ['sphinx.ext.autodoc', 'sphinx_rtd_theme'] 25 | 26 | #pdf_documents = [('index', u'rst2pdf', u'Sample rst2pdf doc', u'Your Name'),] 27 | 28 | templates_path = ['_templates'] 29 | exclude_patterns = [] 30 | 31 | autoclass_content = 'both' 32 | 33 | # -- Options for HTML output ------------------------------------------------- 34 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 35 | 36 | html_theme = 'sphinx_rtd_theme' 37 | #html_static_path = ['_static'] 38 | #html_theme = "sphinx_rtd_theme" 39 | 40 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. pyMOE documentation master file, created by 2 | sphinx-quickstart on Wed Aug 23 11:35:11 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to pyMOE's documentation! 7 | ================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 4 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | pyMOE 2 | ===== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | pyMOE 8 | -------------------------------------------------------------------------------- /docs/source/pyMOE.rst: -------------------------------------------------------------------------------- 1 | pyMOE package 2 | ============= 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyMOE.aperture module 8 | --------------------- 9 | 10 | .. automodule:: pyMOE.aperture 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyMOE.dither module 16 | ------------------- 17 | 18 | .. automodule:: pyMOE.dither 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pyMOE.export module 24 | ------------------- 25 | 26 | .. automodule:: pyMOE.export 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pyMOE.gds\_klops module 32 | ----------------------- 33 | 34 | .. automodule:: pyMOE.gds_klops 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pyMOE.gdsconverter module 40 | ------------------------- 41 | 42 | .. automodule:: pyMOE.gdsconverter 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | pyMOE.generate module 48 | --------------------- 49 | 50 | .. automodule:: pyMOE.generate 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | pyMOE.holograms module 56 | ---------------------- 57 | 58 | .. automodule:: pyMOE.holograms 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | pyMOE.importing module 64 | ---------------------- 65 | 66 | .. automodule:: pyMOE.importing 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | pyMOE.metas module 72 | ------------------ 73 | 74 | .. automodule:: pyMOE.metas 75 | :members: 76 | :undoc-members: 77 | :show-inheritance: 78 | 79 | pyMOE.plotting module 80 | --------------------- 81 | 82 | .. automodule:: pyMOE.plotting 83 | :members: 84 | :undoc-members: 85 | :show-inheritance: 86 | 87 | pyMOE.propagate module 88 | ---------------------- 89 | 90 | .. automodule:: pyMOE.propagate 91 | :members: 92 | :undoc-members: 93 | :show-inheritance: 94 | 95 | pyMOE.sag\_functions module 96 | --------------------------- 97 | 98 | .. automodule:: pyMOE.sag_functions 99 | :members: 100 | :undoc-members: 101 | :show-inheritance: 102 | 103 | pyMOE.utils module 104 | ------------------ 105 | 106 | .. automodule:: pyMOE.utils 107 | :members: 108 | :undoc-members: 109 | :show-inheritance: 110 | 111 | 112 | Module contents 113 | --------------- 114 | 115 | .. automodule:: pyMOE 116 | :members: 117 | :undoc-members: 118 | :show-inheritance: 119 | -------------------------------------------------------------------------------- /docs/source/requirements.txt: -------------------------------------------------------------------------------- 1 | opencv-python 2 | gdspy==1.6.3 3 | klayout==0.26.12 4 | Pillow==8.1.1 5 | scipy==1.6.1 6 | matplotlib==3.5.3 7 | numpy==1.20 -------------------------------------------------------------------------------- /docs/test.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INLnano/pyMOE/41f8c47e9bbc6f9036e5dcf13a63bc4be544fa8c/docs/test.txt -------------------------------------------------------------------------------- /docs/tests.rst: -------------------------------------------------------------------------------- 1 | tests package 2 | ============= 3 | 4 | Submodules 5 | ---------- 6 | 7 | tests.test\_aperture module 8 | --------------------------- 9 | 10 | .. automodule:: tests.test_aperture 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | tests.test\_basic module 16 | ------------------------ 17 | 18 | .. automodule:: tests.test_basic 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | tests.test\_gdsconverter module 24 | ------------------------------- 25 | 26 | .. automodule:: tests.test_gdsconverter 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | tests.test\_generate module 32 | --------------------------- 33 | 34 | .. automodule:: tests.test_generate 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | tests.test\_holograms module 40 | ---------------------------- 41 | 42 | .. automodule:: tests.test_holograms 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | tests.test\_imports module 48 | -------------------------- 49 | 50 | .. automodule:: tests.test_imports 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | Module contents 56 | --------------- 57 | 58 | .. automodule:: tests 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | -------------------------------------------------------------------------------- /notebooks/2 - Metasurfaces/circle.gds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INLnano/pyMOE/41f8c47e9bbc6f9036e5dcf13a63bc4be544fa8c/notebooks/2 - Metasurfaces/circle.gds -------------------------------------------------------------------------------- /notebooks/2 - Metasurfaces/dvar_638nm_p445nm.txt: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00 2 | 5.562499999999999953e-04 3 | 1.112499999999999991e-03 4 | 1.668750000000000094e-03 5 | 2.224999999999999981e-03 6 | 2.781249999999999868e-03 7 | 3.337500000000000189e-03 8 | 3.893750000000000076e-03 9 | 4.449999999999999963e-03 10 | 5.006250000000000283e-03 11 | 5.562499999999999736e-03 12 | 6.118750000000000057e-03 13 | 6.675000000000000377e-03 14 | 7.231249999999999831e-03 15 | 7.787500000000000151e-03 16 | 8.343750000000000472e-03 17 | 8.899999999999999925e-03 18 | 9.456249999999999378e-03 19 | 1.001250000000000057e-02 20 | 1.056875000000000002e-02 21 | 1.112499999999999947e-02 22 | 1.168125000000000066e-02 23 | 1.223750000000000011e-02 24 | 1.279374999999999957e-02 25 | 1.335000000000000075e-02 26 | 1.390625000000000021e-02 27 | 1.446249999999999966e-02 28 | 1.501874999999999911e-02 29 | 1.557500000000000030e-02 30 | 1.613124999999999976e-02 31 | 1.668750000000000094e-02 32 | 1.724374999999999866e-02 33 | 1.779999999999999985e-02 34 | 1.835625000000000104e-02 35 | 1.891249999999999876e-02 36 | 1.946874999999999994e-02 37 | 2.002500000000000113e-02 38 | 2.058124999999999885e-02 39 | 2.113750000000000004e-02 40 | 2.169375000000000123e-02 41 | 2.224999999999999895e-02 42 | 2.280625000000000013e-02 43 | 2.336250000000000132e-02 44 | 2.391874999999999904e-02 45 | 2.447500000000000023e-02 46 | 2.503125000000000142e-02 47 | 2.558749999999999913e-02 48 | 2.614375000000000032e-02 49 | 2.670000000000000151e-02 50 | 2.725624999999999923e-02 51 | 2.781250000000000042e-02 52 | 2.836874999999999813e-02 53 | 2.892499999999999932e-02 54 | 2.948125000000000051e-02 55 | 3.003749999999999823e-02 56 | 3.059374999999999942e-02 57 | 3.115000000000000061e-02 58 | 3.170624999999999832e-02 59 | 3.226249999999999951e-02 60 | 3.281875000000000070e-02 61 | 3.337500000000000189e-02 62 | 3.393125000000000308e-02 63 | 3.448749999999999732e-02 64 | 3.504374999999999851e-02 65 | 3.559999999999999970e-02 66 | 3.615625000000000089e-02 67 | 3.671250000000000208e-02 68 | 3.726874999999999633e-02 69 | 3.782499999999999751e-02 70 | 3.838124999999999870e-02 71 | 3.893749999999999989e-02 72 | 3.949375000000000108e-02 73 | 4.005000000000000226e-02 74 | 4.060624999999999651e-02 75 | 4.116249999999999770e-02 76 | 4.171874999999999889e-02 77 | 4.227500000000000008e-02 78 | 4.283125000000000127e-02 79 | 4.338750000000000245e-02 80 | 4.394374999999999670e-02 81 | 4.449999999999999789e-02 82 | 4.505624999999999908e-02 83 | 4.561250000000000027e-02 84 | 4.616875000000000145e-02 85 | 4.672500000000000264e-02 86 | 4.728124999999999689e-02 87 | 4.783749999999999808e-02 88 | 4.839374999999999927e-02 89 | 4.895000000000000046e-02 90 | 4.950625000000000164e-02 91 | 5.006250000000000283e-02 92 | 5.061874999999999708e-02 93 | 5.117499999999999827e-02 94 | 5.173124999999999946e-02 95 | 5.228750000000000064e-02 96 | 5.284375000000000183e-02 97 | 5.340000000000000302e-02 98 | 5.395624999999999727e-02 99 | 5.451249999999999846e-02 100 | 5.506874999999999964e-02 101 | 5.562500000000000083e-02 102 | 5.618125000000000202e-02 103 | 5.673749999999999627e-02 104 | 5.729374999999999746e-02 105 | 5.784999999999999865e-02 106 | 5.840624999999999983e-02 107 | 5.896250000000000102e-02 108 | 5.951875000000000221e-02 109 | 6.007499999999999646e-02 110 | 6.063124999999999765e-02 111 | 6.118749999999999883e-02 112 | 6.174375000000000002e-02 113 | 6.230000000000000121e-02 114 | 6.285625000000000240e-02 115 | 6.341249999999999665e-02 116 | 6.396875000000000477e-02 117 | 6.452499999999999902e-02 118 | 6.508124999999999327e-02 119 | 6.563750000000000140e-02 120 | 6.619374999999999565e-02 121 | 6.675000000000000377e-02 122 | 6.730624999999999802e-02 123 | 6.786250000000000615e-02 124 | 6.841875000000000040e-02 125 | 6.897499999999999465e-02 126 | 6.953125000000000278e-02 127 | 7.008749999999999702e-02 128 | 7.064375000000000515e-02 129 | 7.119999999999999940e-02 130 | 7.175624999999999365e-02 131 | 7.231250000000000178e-02 132 | 7.286874999999999603e-02 133 | 7.342500000000000415e-02 134 | 7.398124999999999840e-02 135 | 7.453749999999999265e-02 136 | 7.509375000000000078e-02 137 | 7.564999999999999503e-02 138 | 7.620625000000000315e-02 139 | 7.676249999999999740e-02 140 | 7.731875000000000553e-02 141 | 7.787499999999999978e-02 142 | 7.843124999999999403e-02 143 | 7.898750000000000215e-02 144 | 7.954374999999999640e-02 145 | 8.010000000000000453e-02 146 | 8.065624999999999878e-02 147 | 8.121249999999999303e-02 148 | 8.176875000000000115e-02 149 | 8.232499999999999540e-02 150 | 8.288125000000000353e-02 151 | 8.343749999999999778e-02 152 | 8.399375000000000591e-02 153 | 8.455000000000000016e-02 154 | 8.510624999999999440e-02 155 | 8.566250000000000253e-02 156 | 8.621874999999999678e-02 157 | 8.677500000000000491e-02 158 | 8.733124999999999916e-02 159 | 8.788749999999999341e-02 160 | 8.844375000000000153e-02 161 | 8.899999999999999578e-02 162 | 8.955625000000000391e-02 163 | 9.011249999999999816e-02 164 | 9.066874999999999241e-02 165 | 9.122500000000000053e-02 166 | 9.178124999999999478e-02 167 | 9.233750000000000291e-02 168 | 9.289374999999999716e-02 169 | 9.345000000000000528e-02 170 | 9.400624999999999953e-02 171 | 9.456249999999999378e-02 172 | 9.511875000000000191e-02 173 | 9.567499999999999616e-02 174 | 9.623125000000000429e-02 175 | 9.678749999999999853e-02 176 | 9.734374999999999278e-02 177 | 9.790000000000000091e-02 178 | 9.845624999999999516e-02 179 | 9.901250000000000329e-02 180 | 9.956874999999999754e-02 181 | 1.001250000000000057e-01 182 | 1.006812499999999999e-01 183 | 1.012374999999999942e-01 184 | 1.017937500000000023e-01 185 | 1.023499999999999965e-01 186 | 1.029062500000000047e-01 187 | 1.034624999999999989e-01 188 | 1.040187499999999932e-01 189 | 1.045750000000000013e-01 190 | 1.051312499999999955e-01 191 | 1.056875000000000037e-01 192 | 1.062437499999999979e-01 193 | 1.068000000000000060e-01 194 | 1.073562500000000003e-01 195 | 1.079124999999999945e-01 196 | 1.084687500000000027e-01 197 | 1.090249999999999969e-01 198 | 1.095812500000000050e-01 199 | 1.101374999999999993e-01 200 | 1.106937499999999935e-01 201 | 1.112500000000000017e-01 202 | 1.118062499999999959e-01 203 | 1.123625000000000040e-01 204 | 1.129187499999999983e-01 205 | 1.134749999999999925e-01 206 | 1.140312500000000007e-01 207 | 1.145874999999999949e-01 208 | 1.151437500000000030e-01 209 | 1.156999999999999973e-01 210 | 1.162562500000000054e-01 211 | 1.168124999999999997e-01 212 | 1.173687499999999939e-01 213 | 1.179250000000000020e-01 214 | 1.184812499999999963e-01 215 | 1.190375000000000044e-01 216 | 1.195937499999999987e-01 217 | 1.201499999999999929e-01 218 | 1.207062500000000010e-01 219 | 1.212624999999999953e-01 220 | 1.218187500000000034e-01 221 | 1.223749999999999977e-01 222 | 1.229312500000000058e-01 223 | 1.234875000000000000e-01 224 | 1.240437499999999943e-01 225 | 1.246000000000000024e-01 226 | 1.251562499999999967e-01 227 | 1.257125000000000048e-01 228 | 1.262687499999999852e-01 229 | 1.268249999999999933e-01 230 | 1.273812500000000014e-01 231 | 1.279375000000000095e-01 232 | 1.284937499999999899e-01 233 | 1.290499999999999980e-01 234 | 1.296062500000000062e-01 235 | 1.301624999999999865e-01 236 | 1.307187499999999947e-01 237 | 1.312750000000000028e-01 238 | 1.318312500000000109e-01 239 | 1.323874999999999913e-01 240 | 1.329437499999999994e-01 241 | 1.335000000000000075e-01 242 | 1.340562499999999879e-01 243 | 1.346124999999999960e-01 244 | 1.351687500000000042e-01 245 | 1.357250000000000123e-01 246 | 1.362812499999999927e-01 247 | 1.368375000000000008e-01 248 | 1.373937500000000089e-01 249 | 1.379499999999999893e-01 250 | 1.385062499999999974e-01 251 | 1.390625000000000056e-01 252 | 1.396187499999999859e-01 253 | 1.401749999999999940e-01 254 | 1.407312500000000022e-01 255 | 1.412875000000000103e-01 256 | 1.418437499999999907e-01 257 | 1.423999999999999988e-01 258 | 1.429562500000000069e-01 259 | 1.435124999999999873e-01 260 | 1.440687499999999954e-01 261 | 1.446250000000000036e-01 262 | 1.451812500000000117e-01 263 | 1.457374999999999921e-01 264 | 1.462937500000000002e-01 265 | 1.468500000000000083e-01 266 | 1.474062499999999887e-01 267 | 1.479624999999999968e-01 268 | 1.485187500000000049e-01 269 | 1.490749999999999853e-01 270 | 1.496312499999999934e-01 271 | 1.501875000000000016e-01 272 | 1.507437500000000097e-01 273 | 1.512999999999999901e-01 274 | 1.518562499999999982e-01 275 | 1.524125000000000063e-01 276 | 1.529687499999999867e-01 277 | 1.535249999999999948e-01 278 | 1.540812500000000029e-01 279 | 1.546375000000000111e-01 280 | 1.551937499999999914e-01 281 | 1.557499999999999996e-01 282 | 1.563062500000000077e-01 283 | 1.568624999999999881e-01 284 | 1.574187499999999962e-01 285 | 1.579750000000000043e-01 286 | 1.585312500000000124e-01 287 | 1.590874999999999928e-01 288 | 1.596437500000000009e-01 289 | 1.602000000000000091e-01 290 | 1.607562499999999894e-01 291 | 1.613124999999999976e-01 292 | 1.618687500000000057e-01 293 | 1.624249999999999861e-01 294 | 1.629812499999999942e-01 295 | 1.635375000000000023e-01 296 | 1.640937500000000104e-01 297 | 1.646499999999999908e-01 298 | 1.652062499999999989e-01 299 | 1.657625000000000071e-01 300 | 1.663187499999999874e-01 301 | 1.668749999999999956e-01 302 | 1.674312500000000037e-01 303 | 1.679875000000000118e-01 304 | 1.685437499999999922e-01 305 | 1.691000000000000003e-01 306 | 1.696562500000000084e-01 307 | 1.702124999999999888e-01 308 | 1.707687499999999969e-01 309 | 1.713250000000000051e-01 310 | 1.718812499999999854e-01 311 | 1.724374999999999936e-01 312 | 1.729937500000000017e-01 313 | 1.735500000000000098e-01 314 | 1.741062499999999902e-01 315 | 1.746624999999999983e-01 316 | 1.752187500000000064e-01 317 | 1.757749999999999868e-01 318 | 1.763312499999999949e-01 319 | 1.768875000000000031e-01 320 | 1.774437500000000112e-01 321 | 1.779999999999999916e-01 322 | 1.785562499999999997e-01 323 | 1.791125000000000078e-01 324 | 1.796687499999999882e-01 325 | 1.802249999999999963e-01 326 | 1.807812500000000044e-01 327 | 1.813374999999999848e-01 328 | 1.818937499999999929e-01 329 | 1.824500000000000011e-01 330 | 1.830062500000000092e-01 331 | 1.835624999999999896e-01 332 | 1.841187499999999977e-01 333 | 1.846750000000000058e-01 334 | 1.852312499999999862e-01 335 | 1.857874999999999943e-01 336 | 1.863437500000000024e-01 337 | 1.869000000000000106e-01 338 | 1.874562499999999909e-01 339 | 1.880124999999999991e-01 340 | 1.885687500000000072e-01 341 | 1.891249999999999876e-01 342 | 1.896812499999999957e-01 343 | 1.902375000000000038e-01 344 | 1.907937500000000119e-01 345 | 1.913499999999999923e-01 346 | 1.919062500000000004e-01 347 | 1.924625000000000086e-01 348 | 1.930187499999999889e-01 349 | 1.935749999999999971e-01 350 | 1.941312500000000052e-01 351 | 1.946874999999999856e-01 352 | 1.952437499999999937e-01 353 | 1.958000000000000018e-01 354 | 1.963562500000000099e-01 355 | 1.969124999999999903e-01 356 | 1.974687499999999984e-01 357 | 1.980250000000000066e-01 358 | 1.985812499999999869e-01 359 | 1.991374999999999951e-01 360 | 1.996937500000000032e-01 361 | 2.002500000000000113e-01 362 | 2.008062499999999917e-01 363 | 2.013624999999999998e-01 364 | 2.019187500000000079e-01 365 | 2.024749999999999883e-01 366 | 2.030312499999999964e-01 367 | 2.035875000000000046e-01 368 | 2.041437499999999849e-01 369 | 2.046999999999999931e-01 370 | 2.052562500000000012e-01 371 | 2.058125000000000093e-01 372 | 2.063687499999999897e-01 373 | 2.069249999999999978e-01 374 | 2.074812500000000060e-01 375 | 2.080374999999999863e-01 376 | 2.085937499999999944e-01 377 | 2.091500000000000026e-01 378 | 2.097062500000000107e-01 379 | 2.102624999999999911e-01 380 | 2.108187499999999992e-01 381 | 2.113750000000000073e-01 382 | 2.119312499999999877e-01 383 | 2.124874999999999958e-01 384 | 2.130437500000000040e-01 385 | 2.136000000000000121e-01 386 | 2.141562499999999925e-01 387 | 2.147125000000000006e-01 388 | 2.152687500000000087e-01 389 | 2.158249999999999891e-01 390 | 2.163812499999999972e-01 391 | 2.169375000000000053e-01 392 | 2.174937499999999857e-01 393 | 2.180499999999999938e-01 394 | 2.186062500000000020e-01 395 | 2.191625000000000101e-01 396 | 2.197187499999999905e-01 397 | 2.202749999999999986e-01 398 | 2.208312500000000067e-01 399 | 2.213874999999999871e-01 400 | 2.219437499999999952e-01 401 | 2.225000000000000033e-01 402 | 2.230562500000000115e-01 403 | 2.236124999999999918e-01 404 | 2.241687500000000000e-01 405 | 2.247250000000000081e-01 406 | 2.252812499999999885e-01 407 | 2.258374999999999966e-01 408 | 2.263937500000000047e-01 409 | 2.269499999999999851e-01 410 | 2.275062499999999932e-01 411 | 2.280625000000000013e-01 412 | 2.286187500000000095e-01 413 | 2.291749999999999898e-01 414 | 2.297312499999999980e-01 415 | 2.302875000000000061e-01 416 | 2.308437499999999865e-01 417 | 2.313999999999999946e-01 418 | 2.319562500000000027e-01 419 | 2.325125000000000108e-01 420 | 2.330687499999999912e-01 421 | 2.336249999999999993e-01 422 | 2.341812500000000075e-01 423 | 2.347374999999999878e-01 424 | 2.352937499999999960e-01 425 | 2.358500000000000041e-01 426 | 2.364062499999999845e-01 427 | 2.369624999999999926e-01 428 | 2.375187500000000007e-01 429 | 2.380750000000000088e-01 430 | 2.386312499999999892e-01 431 | 2.391874999999999973e-01 432 | 2.397437500000000055e-01 433 | 2.402999999999999858e-01 434 | 2.408562499999999940e-01 435 | 2.414125000000000021e-01 436 | 2.419687500000000102e-01 437 | 2.425249999999999906e-01 438 | 2.430812499999999987e-01 439 | 2.436375000000000068e-01 440 | 2.441937499999999872e-01 441 | 2.447499999999999953e-01 442 | 2.453062500000000035e-01 443 | 2.458625000000000116e-01 444 | 2.464187499999999920e-01 445 | 2.469750000000000001e-01 446 | 2.475312500000000082e-01 447 | 2.480874999999999886e-01 448 | 2.486437499999999967e-01 449 | 2.492000000000000048e-01 450 | 2.497562499999999852e-01 451 | 2.503124999999999933e-01 452 | 2.508687499999999737e-01 453 | 2.514250000000000096e-01 454 | 2.519812499999999900e-01 455 | 2.525374999999999703e-01 456 | 2.530937500000000062e-01 457 | 2.536499999999999866e-01 458 | 2.542062500000000225e-01 459 | 2.547625000000000028e-01 460 | 2.553187499999999832e-01 461 | 2.558750000000000191e-01 462 | 2.564312499999999995e-01 463 | 2.569874999999999798e-01 464 | 2.575437500000000157e-01 465 | 2.580999999999999961e-01 466 | 2.586562499999999765e-01 467 | 2.592125000000000123e-01 468 | 2.597687499999999927e-01 469 | 2.603249999999999731e-01 470 | 2.608812500000000090e-01 471 | 2.614374999999999893e-01 472 | 2.619937500000000252e-01 473 | 2.625500000000000056e-01 474 | 2.631062499999999860e-01 475 | 2.636625000000000218e-01 476 | 2.642187500000000022e-01 477 | 2.647749999999999826e-01 478 | 2.653312500000000185e-01 479 | 2.658874999999999988e-01 480 | 2.664437499999999792e-01 481 | 2.670000000000000151e-01 482 | 2.675562499999999955e-01 483 | 2.681124999999999758e-01 484 | 2.686687500000000117e-01 485 | 2.692249999999999921e-01 486 | 2.697812499999999725e-01 487 | 2.703375000000000083e-01 488 | 2.708937499999999887e-01 489 | 2.714500000000000246e-01 490 | 2.720062500000000050e-01 491 | 2.725624999999999853e-01 492 | 2.731187500000000212e-01 493 | 2.736750000000000016e-01 494 | 2.742312499999999820e-01 495 | 2.747875000000000179e-01 496 | 2.753437499999999982e-01 497 | 2.758999999999999786e-01 498 | 2.764562500000000145e-01 499 | 2.770124999999999948e-01 500 | 2.775687499999999752e-01 501 | 2.781250000000000111e-01 502 | 2.786812499999999915e-01 503 | 2.792374999999999718e-01 504 | 2.797937500000000077e-01 505 | 2.803499999999999881e-01 506 | 2.809062500000000240e-01 507 | 2.814625000000000044e-01 508 | 2.820187499999999847e-01 509 | 2.825750000000000206e-01 510 | 2.831312500000000010e-01 511 | 2.836874999999999813e-01 512 | 2.842437500000000172e-01 513 | 2.847999999999999976e-01 514 | 2.853562499999999780e-01 515 | 2.859125000000000139e-01 516 | 2.864687499999999942e-01 517 | 2.870249999999999746e-01 518 | 2.875812500000000105e-01 519 | 2.881374999999999909e-01 520 | 2.886937499999999712e-01 521 | 2.892500000000000071e-01 522 | 2.898062499999999875e-01 523 | 2.903625000000000234e-01 524 | 2.909187500000000037e-01 525 | 2.914749999999999841e-01 526 | 2.920312500000000200e-01 527 | 2.925875000000000004e-01 528 | 2.931437499999999807e-01 529 | 2.937000000000000166e-01 530 | 2.942562499999999970e-01 531 | 2.948124999999999774e-01 532 | 2.953687500000000132e-01 533 | 2.959249999999999936e-01 534 | 2.964812499999999740e-01 535 | 2.970375000000000099e-01 536 | 2.975937499999999902e-01 537 | 2.981499999999999706e-01 538 | 2.987062500000000065e-01 539 | 2.992624999999999869e-01 540 | 2.998187500000000227e-01 541 | 3.003750000000000031e-01 542 | 3.009312499999999835e-01 543 | 3.014875000000000194e-01 544 | 3.020437499999999997e-01 545 | 3.025999999999999801e-01 546 | 3.031562500000000160e-01 547 | 3.037124999999999964e-01 548 | 3.042687499999999767e-01 549 | 3.048250000000000126e-01 550 | 3.053812499999999930e-01 551 | 3.059374999999999734e-01 552 | 3.064937500000000092e-01 553 | 3.070499999999999896e-01 554 | 3.076062499999999700e-01 555 | 3.081625000000000059e-01 556 | 3.087187499999999862e-01 557 | 3.092750000000000221e-01 558 | 3.098312500000000025e-01 559 | 3.103874999999999829e-01 560 | 3.109437500000000187e-01 561 | 3.114999999999999991e-01 562 | 3.120562499999999795e-01 563 | 3.126125000000000154e-01 564 | 3.131687499999999957e-01 565 | 3.137249999999999761e-01 566 | 3.142812500000000120e-01 567 | 3.148374999999999924e-01 568 | 3.153937499999999727e-01 569 | 3.159500000000000086e-01 570 | 3.165062499999999890e-01 571 | 3.170625000000000249e-01 572 | 3.176187500000000052e-01 573 | 3.181749999999999856e-01 574 | 3.187312500000000215e-01 575 | 3.192875000000000019e-01 576 | 3.198437499999999822e-01 577 | 3.204000000000000181e-01 578 | 3.209562499999999985e-01 579 | 3.215124999999999789e-01 580 | 3.220687500000000147e-01 581 | 3.226249999999999951e-01 582 | 3.231812499999999755e-01 583 | 3.237375000000000114e-01 584 | 3.242937499999999917e-01 585 | 3.248499999999999721e-01 586 | 3.254062500000000080e-01 587 | 3.259624999999999884e-01 588 | 3.265187500000000242e-01 589 | 3.270750000000000046e-01 590 | 3.276312499999999850e-01 591 | 3.281875000000000209e-01 592 | 3.287437500000000012e-01 593 | 3.292999999999999816e-01 594 | 3.298562500000000175e-01 595 | 3.304124999999999979e-01 596 | 3.309687499999999782e-01 597 | 3.315250000000000141e-01 598 | 3.320812499999999945e-01 599 | 3.326374999999999749e-01 600 | 3.331937500000000107e-01 601 | 3.337499999999999911e-01 602 | 3.343062499999999715e-01 603 | 3.348625000000000074e-01 604 | 3.354187499999999877e-01 605 | 3.359750000000000236e-01 606 | 3.365312500000000040e-01 607 | 3.370874999999999844e-01 608 | 3.376437500000000203e-01 609 | 3.382000000000000006e-01 610 | 3.387562499999999810e-01 611 | 3.393125000000000169e-01 612 | 3.398687499999999972e-01 613 | 3.404249999999999776e-01 614 | 3.409812500000000135e-01 615 | 3.415374999999999939e-01 616 | 3.420937499999999742e-01 617 | 3.426500000000000101e-01 618 | 3.432062499999999905e-01 619 | 3.437624999999999709e-01 620 | 3.443187500000000068e-01 621 | 3.448749999999999871e-01 622 | 3.454312500000000230e-01 623 | 3.459875000000000034e-01 624 | 3.465437499999999837e-01 625 | 3.471000000000000196e-01 626 | 3.476562500000000000e-01 627 | 3.482124999999999804e-01 628 | 3.487687500000000163e-01 629 | 3.493249999999999966e-01 630 | 3.498812499999999770e-01 631 | 3.504375000000000129e-01 632 | 3.509937499999999932e-01 633 | 3.515499999999999736e-01 634 | 3.521062500000000095e-01 635 | 3.526624999999999899e-01 636 | 3.532187499999999702e-01 637 | 3.537750000000000061e-01 638 | 3.543312499999999865e-01 639 | 3.548875000000000224e-01 640 | 3.554437500000000028e-01 641 | 3.559999999999999831e-01 642 | 3.565562500000000190e-01 643 | 3.571124999999999994e-01 644 | 3.576687499999999797e-01 645 | 3.582250000000000156e-01 646 | 3.587812499999999960e-01 647 | 3.593374999999999764e-01 648 | 3.598937500000000123e-01 649 | 3.604499999999999926e-01 650 | 3.610062499999999730e-01 651 | 3.615625000000000089e-01 652 | 3.621187499999999893e-01 653 | 3.626749999999999696e-01 654 | 3.632312500000000055e-01 655 | 3.637874999999999859e-01 656 | 3.643437500000000218e-01 657 | 3.649000000000000021e-01 658 | 3.654562499999999825e-01 659 | 3.660125000000000184e-01 660 | 3.665687499999999988e-01 661 | 3.671249999999999791e-01 662 | 3.676812500000000150e-01 663 | 3.682374999999999954e-01 664 | 3.687937499999999758e-01 665 | 3.693500000000000116e-01 666 | 3.699062499999999920e-01 667 | 3.704624999999999724e-01 668 | 3.710187500000000083e-01 669 | 3.715749999999999886e-01 670 | 3.721312500000000245e-01 671 | 3.726875000000000049e-01 672 | 3.732437499999999853e-01 673 | 3.738000000000000211e-01 674 | 3.743562500000000015e-01 675 | 3.749124999999999819e-01 676 | 3.754687500000000178e-01 677 | 3.760249999999999981e-01 678 | 3.765812499999999785e-01 679 | 3.771375000000000144e-01 680 | 3.776937499999999948e-01 681 | 3.782499999999999751e-01 682 | 3.788062500000000110e-01 683 | 3.793624999999999914e-01 684 | 3.799187499999999718e-01 685 | 3.804750000000000076e-01 686 | 3.810312499999999880e-01 687 | 3.815875000000000239e-01 688 | 3.821437500000000043e-01 689 | 3.826999999999999846e-01 690 | 3.832562500000000205e-01 691 | 3.838125000000000009e-01 692 | 3.843687499999999813e-01 693 | 3.849250000000000171e-01 694 | 3.854812499999999975e-01 695 | 3.860374999999999779e-01 696 | 3.865937500000000138e-01 697 | 3.871499999999999941e-01 698 | 3.877062499999999745e-01 699 | 3.882625000000000104e-01 700 | 3.888187499999999908e-01 701 | 3.893749999999999711e-01 702 | 3.899312500000000070e-01 703 | 3.904874999999999874e-01 704 | 3.910437500000000233e-01 705 | 3.916000000000000036e-01 706 | 3.921562499999999840e-01 707 | 3.927125000000000199e-01 708 | 3.932687500000000003e-01 709 | 3.938249999999999806e-01 710 | 3.943812500000000165e-01 711 | 3.949374999999999969e-01 712 | 3.954937499999999773e-01 713 | 3.960500000000000131e-01 714 | 3.966062499999999935e-01 715 | 3.971624999999999739e-01 716 | 3.977187500000000098e-01 717 | 3.982749999999999901e-01 718 | 3.988312499999999705e-01 719 | 3.993875000000000064e-01 720 | 3.999437499999999868e-01 721 | 4.005000000000000226e-01 722 | 4.010562500000000030e-01 723 | 4.016124999999999834e-01 724 | 4.021687500000000193e-01 725 | 4.027249999999999996e-01 726 | 4.032812499999999800e-01 727 | 4.038375000000000159e-01 728 | 4.043937499999999963e-01 729 | 4.049499999999999766e-01 730 | 4.055062500000000125e-01 731 | 4.060624999999999929e-01 732 | 4.066187499999999733e-01 733 | 4.071750000000000091e-01 734 | 4.077312499999999895e-01 735 | 4.082874999999999699e-01 736 | 4.088437500000000058e-01 737 | 4.093999999999999861e-01 738 | 4.099562500000000220e-01 739 | 4.105125000000000024e-01 740 | 4.110687499999999828e-01 741 | 4.116250000000000187e-01 742 | 4.121812499999999990e-01 743 | 4.127374999999999794e-01 744 | 4.132937500000000153e-01 745 | 4.138499999999999956e-01 746 | 4.144062499999999760e-01 747 | 4.149625000000000119e-01 748 | 4.155187499999999923e-01 749 | 4.160749999999999726e-01 750 | 4.166312500000000085e-01 751 | 4.171874999999999889e-01 752 | 4.177437499999999693e-01 753 | 4.183000000000000052e-01 754 | 4.188562499999999855e-01 755 | 4.194125000000000214e-01 756 | 4.199687500000000018e-01 757 | 4.205249999999999821e-01 758 | 4.210812500000000180e-01 759 | 4.216374999999999984e-01 760 | 4.221937499999999788e-01 761 | 4.227500000000000147e-01 762 | 4.233062499999999950e-01 763 | 4.238624999999999754e-01 764 | 4.244187500000000113e-01 765 | 4.249749999999999917e-01 766 | 4.255312499999999720e-01 767 | 4.260875000000000079e-01 768 | 4.266437499999999883e-01 769 | 4.272000000000000242e-01 770 | 4.277562500000000045e-01 771 | 4.283124999999999849e-01 772 | 4.288687500000000208e-01 773 | 4.294250000000000012e-01 774 | 4.299812499999999815e-01 775 | 4.305375000000000174e-01 776 | 4.310937499999999978e-01 777 | 4.316499999999999782e-01 778 | 4.322062500000000140e-01 779 | 4.327624999999999944e-01 780 | 4.333187499999999748e-01 781 | 4.338750000000000107e-01 782 | 4.344312499999999910e-01 783 | 4.349874999999999714e-01 784 | 4.355437500000000073e-01 785 | 4.360999999999999877e-01 786 | 4.366562500000000235e-01 787 | 4.372125000000000039e-01 788 | 4.377687499999999843e-01 789 | 4.383250000000000202e-01 790 | 4.388812500000000005e-01 791 | 4.394374999999999809e-01 792 | 4.399937500000000168e-01 793 | 4.405499999999999972e-01 794 | 4.411062499999999775e-01 795 | 4.416625000000000134e-01 796 | 4.422187499999999938e-01 797 | 4.427749999999999742e-01 798 | 4.433312500000000100e-01 799 | 4.438874999999999904e-01 800 | 4.444437499999999708e-01 801 | -------------------------------------------------------------------------------- /notebooks/2 - Metasurfaces/rectangle.gds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INLnano/pyMOE/41f8c47e9bbc6f9036e5dcf13a63bc4be544fa8c/notebooks/2 - Metasurfaces/rectangle.gds -------------------------------------------------------------------------------- /notebooks/2 - Metasurfaces/triangle.gds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INLnano/pyMOE/41f8c47e9bbc6f9036e5dcf13a63bc4be544fa8c/notebooks/2 - Metasurfaces/triangle.gds -------------------------------------------------------------------------------- /notebooks/3 - Import_export/calibration.csv: -------------------------------------------------------------------------------- 1 | #greylevel,thickness 2 | 0,-0.0597 3 | 1,-0.0642 4 | 2,-0.1024 5 | 3,-0.1186 6 | 4,-0.1402 7 | 5,-0.1672 8 | 6,-0.2021 9 | 7,-0.2330 10 | 8,-0.2556 11 | 9,-0.2819 12 | 10,-0.3111 13 | 11,-0.3306 14 | 12,-0.3636 15 | 13,-0.3856 16 | 14,-0.4082 17 | 15,-0.4368 18 | 16,-0.4799 19 | 17,-0.5022 20 | 18,-0.5305 21 | 19,-0.5606 22 | 20,-0.5881 23 | 21,-0.6142 24 | 22,-0.6406 25 | 23,-0.6724 26 | 24,-0.7062 27 | 25,-0.7285 28 | 26,-0.7455 29 | 27,-0.7666 30 | 28,-0.7933 31 | 29,-0.8207 32 | 30,-0.8464 33 | 31,-0.8684 34 | 32,-0.8904 35 | 33,-0.9151 36 | 34,-0.9382 37 | 35,-0.9608 38 | 36,-0.9884 39 | 37,-1.0098 40 | 38,-1.0364 41 | 39,-1.0596 42 | 40,-1.0779 43 | 41,-1.0925 44 | 42,-1.1039 45 | 43,-1.1339 46 | 44,-1.1567 47 | 45,-1.1736 48 | 46,-1.1877 49 | 47,-1.2007 50 | 48,-1.2071 51 | 49,-1.2259 52 | 50,-1.2444 53 | 51,-1.2658 54 | 52,-1.2793 55 | 53,-1.2994 56 | 54,-1.3138 57 | 55,-1.3257 58 | 56,-1.3342 59 | 57,-1.3443 60 | 58,-1.3563 61 | 59,-1.3697 62 | 60,-1.3767 63 | 61,-1.3924 64 | 62,-1.4131 65 | 63,-1.4199 66 | 64,-1.4266 67 | 65,-1.4406 68 | 66,-1.4577 69 | 67,-1.4685 70 | 68,-1.4763 71 | 69,-1.4853 72 | 70,-1.4905 73 | 71,-1.5016 74 | 72,-1.5128 75 | 73,-1.5200 76 | 74,-1.5326 77 | 75,-1.5445 78 | 76,-1.5526 79 | 77,-1.5623 80 | 78,-1.5687 81 | 79,-1.5736 82 | 80,-1.5766 83 | 81,-1.5816 84 | 82,-1.5879 85 | 83,-1.6001 86 | 84,-1.6121 87 | 85,-1.6246 88 | 86,-1.6331 89 | 87,-1.6380 90 | 88,-1.6441 91 | 89,-1.6528 92 | 90,-1.6579 93 | 91,-1.6648 94 | 92,-1.6691 95 | 93,-1.6809 96 | 94,-1.6875 97 | 95,-1.6935 98 | 96,-1.6989 99 | 97,-1.7052 100 | 98,-1.7082 101 | 99,-1.7148 102 | 100,-1.7227 103 | 101,-1.7274 104 | 102,-1.7292 105 | 103,-1.7337 106 | 104,-1.7358 107 | 105,-1.7450 108 | 106,-1.7469 109 | 107,-1.7473 110 | 108,-1.7554 111 | 109,-1.7646 112 | 110,-1.7725 113 | 111,-1.7766 114 | 112,-1.7819 115 | 113,-1.7880 116 | 114,-1.7929 117 | 115,-1.7997 118 | 116,-1.8075 119 | 117,-1.8141 120 | 118,-1.8177 121 | 119,-1.8187 122 | 120,-1.8238 123 | 121,-1.8236 124 | 122,-1.8308 125 | 123,-1.8345 126 | 124,-1.8373 127 | 125,-1.8427 128 | 126,-1.8393 129 | 127,-1.8484 130 | -------------------------------------------------------------------------------- /notebooks/5 - Holograms/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/INLnano/pyMOE/41f8c47e9bbc6f9036e5dcf13a63bc4be544fa8c/notebooks/5 - Holograms/target.png -------------------------------------------------------------------------------- /pyMOE/__init__.py: -------------------------------------------------------------------------------- 1 | ###first way of importing all 2 | #from pyMOE.export import * 3 | #from pyMOE.gds_klops import * 4 | #from pyMOE.generate import * 5 | #from pyMOE.impor import * 6 | #from pyMOE.metas import * 7 | #from pyMOE.propagate import* 8 | 9 | ###second way of import each 10 | import pyMOE.dither as dither 11 | import pyMOE.export as export 12 | import pyMOE.gds_klops as gdsops 13 | import pyMOE.importing as importing 14 | import pyMOE.metas as metas 15 | import pyMOE.propagate as propagate 16 | import pyMOE.field as field 17 | 18 | from pyMOE.aperture import Aperture 19 | from pyMOE.field import Field 20 | from pyMOE.field import Screen 21 | from pyMOE.aperture import ApertureField 22 | 23 | from pyMOE.gdsconverter import GDSMask 24 | from pyMOE.gdsconverter import GrayscaleCalibration 25 | 26 | import pyMOE.plotting as plotting 27 | import pyMOE.utils as utils 28 | import pyMOE.holograms as holograms 29 | import pyMOE.generate as generate 30 | import pyMOE.sag_functions as sag 31 | 32 | 33 | __version__ = '1.5' -------------------------------------------------------------------------------- /pyMOE/aperture.py: -------------------------------------------------------------------------------- 1 | """ 2 | aperture.py 3 | 4 | 5 | Definition of Class Aperture 6 | 7 | 8 | """ 9 | 10 | import numpy as np 11 | from pyMOE.utils import digitize_array_to_bins 12 | from pyMOE.utils import discretize_array 13 | from pyMOE.sag_functions import phase2height, height2phase 14 | from pyMOE.utils import progress_bar, Timer 15 | 16 | 17 | 18 | class Aperture: 19 | """ 20 | Class Aperture: 21 | Creates an Aperture object that is an homogenous array of values corresponding 22 | to the transfer function matrix across the aperture 23 | 24 | Args: 25 | :x: Vector for the x axis 26 | :y: Vector for the y axis 27 | 28 | Methods: 29 | :aperture: returns the aperture 30 | :shape: returns the shape of the aperture 31 | 32 | """ 33 | def __init__(self, x, y): 34 | self.x = x 35 | self.y = y 36 | self.XX, self.YY = np.meshgrid(x, y) 37 | self.pixel_x = self.x[1]-self.x[0] 38 | self.pixel_y = self.y[1]-self.y[0] 39 | 40 | # XX.shape has the same shape as YY.shape 41 | self.aperture = np.zeros(self.XX.shape) 42 | self.aperture_original = None 43 | self.levels = None 44 | self.aperture_discretized = None 45 | self.discretized_flag = False 46 | 47 | 48 | @property 49 | def shape(self): 50 | return self.aperture.shape 51 | 52 | def discretize(self, levels): 53 | """Discretizes the aperture to the number of levels""" 54 | if self.aperture_original is None: 55 | self.aperture_original = np.copy(self.aperture) 56 | levels, digitized = digitize_array_to_bins(self.aperture, levels) 57 | 58 | self.levels = levels 59 | self.aperture_discretized = digitized 60 | self.aperture = levels[digitized] 61 | self.discretized_flag=True 62 | 63 | def modulos(self, mod, normalize_to_max=True,mod_tolerance=1e-64): 64 | """Discretizes the aperture to the number of levels""" 65 | if self.aperture_original is None: 66 | self.aperture_original = np.copy(self.aperture) 67 | 68 | aux = self.aperture 69 | self.aperture = (aux-np.max(aux)-mod_tolerance) % (float(mod)) 70 | 71 | def pixelize(self, pixelize_x, pixelize_y, verbose=True): 72 | """Pixelizes the aperture to the given pixelize_x in real space coordinates by averaging the data within the pixel, keeping same shape""" 73 | assert pixelize_x > 0, "Pixel size must be greater than 0" 74 | assert pixelize_y > 0, "Pixel size must be greater than 0" 75 | # assert self.aperture_original is not None, "Original aperture not saved" 76 | if self.aperture_original is None: 77 | self.aperture_original = np.copy(self.aperture) 78 | 79 | aux_aperture = np.copy(self.aperture) 80 | XX_copy = np.copy(self.XX) 81 | YY_copy = np.copy(self.YY) 82 | 83 | XX_copy = XX_copy//pixelize_x 84 | YY_copy = YY_copy//pixelize_y 85 | 86 | XX_copy = XX_copy*pixelize_x 87 | YY_copy = YY_copy*pixelize_y 88 | 89 | 90 | N_x = len(np.unique(XX_copy)) 91 | N_y = len(np.unique(YY_copy)) 92 | 93 | if verbose: 94 | progress_bar(0/(N_x*N_y)) 95 | for i,x_val in enumerate(np.unique(XX_copy)): 96 | 97 | 98 | for j, y_val in enumerate(np.unique(YY_copy)): 99 | 100 | self.aperture[(XX_copy==x_val) & (YY_copy==y_val)] = np.mean(aux_aperture[(XX_copy==x_val) & (YY_copy==y_val)]) 101 | if verbose: 102 | progress_bar((i*N_y+j)/(N_x*N_y)) 103 | # print((i*N_y+j)/(N_x*N_y)) 104 | if verbose: 105 | progress_bar(1) 106 | 107 | 108 | 109 | 110 | 111 | 112 | def phase_unwrap(self): 113 | """Unwraps the phase of the aperture""" 114 | #assert self.is_height is False, "Cannot unwrap height" 115 | 116 | self.aperture = np.unwrap(np.unwrap(self.aperture, axis=0), axis=1) 117 | # self.aperture = np.apply_over_axes(np.unwrap, self.aperture, np.arange(len(self.aperture.shape))) 118 | # self.aperture = np.apply_over_axes(np.unwrap, self.aperture, np.arange(len(self.aperture.shape))) 119 | 120 | 121 | def phase2height(self, wavelength, n1, n0=1): 122 | """Converts the phase to height 123 | Args: 124 | :wavelength: Wavelength of the light 125 | :n1: Refractive index of the medium where the light is propagating 126 | :n0: Refractive index of the medium background""" 127 | # assert self.is_height is False, "Cannot unwrap height" 128 | 129 | 130 | self.aperture = phase2height(self.aperture, wavelength, n1, n0) 131 | 132 | def height2phase(self, wavelength, n1, n0=1): 133 | """Converts the height to phase 134 | Args: 135 | :wavelength: Wavelength of the light 136 | :n1: Refractive index of the medium where the light is propagating 137 | :n0: Refractive index of the medium background""" 138 | 139 | self.aperture = height2phase(self.aperture, wavelength, n1, n0) 140 | 141 | 142 | 143 | class ApertureField: 144 | """ 145 | Class Aperture: 146 | Creates an Aperture object that is an homogenous array of complex values corresponding 147 | to the transfer function matrix across the aperture 148 | 149 | Args: 150 | :x: Vector for the x axis 151 | :y: Vector for the y axis 152 | 153 | Methods: 154 | :aperture: returns the complex aperture array 155 | :amplitude: sets or returns the amplitude of the aperture array 156 | :phase: sets or returns the phase of the aperture array 157 | :unwrap: retursn the unwrapped phase 158 | :shape: returns the shape of the aperture 159 | 160 | """ 161 | def __init__(self, x, y): 162 | self.x = x 163 | self.y = y 164 | self.XX, self.YY = np.meshgrid(x, y) 165 | self.pixel_x = self.x[1]-self.x[0] 166 | self.pixel_y = self.y[1]-self.y[0] 167 | 168 | 169 | self.aperture = np.ones(self.XX.shape)*np.exp(1j*np.zeros(self.XX.shape)) 170 | 171 | @property 172 | def shape(self): 173 | return self.aperture.shape 174 | @property 175 | def amplitude(self): 176 | return np.abs(self.aperture) 177 | 178 | @amplitude.setter 179 | def amplitude(self, amplitude): 180 | assert amplitude.shape == self.shape, "Provided array shape does not match Aperture shape" 181 | self.aperture = amplitude*np.exp(1j*self.phase) 182 | 183 | @property 184 | def phase(self): 185 | return np.angle(self.aperture) 186 | 187 | @phase.setter 188 | def phase(self, phase): 189 | assert phase.shape == self.shape, "Provided array shape does not match Aperture shape" 190 | self.aperture = self.amplitude*np.exp(1j*phase) 191 | 192 | @property 193 | def unwrap(self): 194 | return np.unwrap(self.phase) 195 | 196 | 197 | -------------------------------------------------------------------------------- /pyMOE/dither.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | dither.py 4 | Module for dithering of masks/images 5 | """ 6 | 7 | import cv2 8 | import numpy as np 9 | 10 | def floyd_steinberg(input_img , plot = False ): 11 | """ 12 | Applies Floyd-Steinberg dithering algorithm to input image. 13 | 14 | Args: 15 | :input_img: input image as a 2D grid (use cv2 img) 16 | :plot: binary value, if True shows the plot, defaults to False 17 | 18 | """ 19 | ###NOTE: considers the same pixel as in the image, possible improvement, change of pixel size 20 | 21 | #input image as provided 22 | img_gray_eq = input_img 23 | 24 | h,w = img_gray_eq.shape 25 | 26 | img_dither = np.zeros((h+1, w+1), dtype=np.float64) 27 | 28 | threshold = 128 29 | 30 | for i in np.arange(0,h): 31 | for j in np.arange(0,w): 32 | img_dither[i, j] = img_gray_eq[i, j] 33 | 34 | for i in np.arange(0,h): 35 | for j in np.arange(0,w): 36 | opix = img_dither[i, j] 37 | if (img_dither[i, j] > threshold): 38 | vpix = 255 39 | else: 40 | vpix = 0 41 | 42 | img_dither[i, j] = vpix 43 | 44 | err = opix - vpix 45 | 46 | if j > 0: 47 | img_dither[i+1, j-1] = img_dither[i+1, j-1] + err * 3 / 16 48 | img_dither[i+1, j] = img_dither[i+1, j] + err * 5 / 16 49 | img_dither[i, j+1] = img_dither[i, j+1] + err * 7 / 16 50 | img_dither[i+1, j+1] = img_dither[i+1, j+1] + err * 1 / 16 51 | 52 | img_dither = img_dither.astype(np.uint8) 53 | img_dither = img_dither[0:h, 0:w] 54 | 55 | if plot == True: 56 | import matplotlib.pyplot as plt 57 | fig = plt.figure() 58 | plt.imshow(255-img_dither, vmin=0, vmax=255, cmap=plt.get_cmap("Greys")) 59 | 60 | #cv2.imwrite(output_filename, img_dither_inv) 61 | 62 | return img_gray_eq, img_dither 63 | 64 | 65 | def dither_img(input_img, output_filename, plotting = False): 66 | """ 67 | Returns a make dithered image from input_img, save to output_img. 68 | 69 | Args: 70 | :inputcv2: input image as a 2D grid (use cv2 img) 71 | :output_filename: filename of image to be written 72 | :plotting: binary value, if True shows the plot, defaults to False 73 | """ 74 | 75 | img_gray0 = cv2.imread(input_img, cv2.IMREAD_GRAYSCALE) 76 | #img_gray0 = 255 - img_gray0 77 | 78 | #possible IMPROVEMENT considering passing any dithering algorithm as argument to to dither_img 79 | img_gray_eq, img_dither= floyd_steinberg(img_gray0, plot = plotting) 80 | 81 | cv2.imwrite(output_filename, img_dither) 82 | 83 | del img_gray_eq, img_dither 84 | 85 | 86 | 87 | ####missing other possible algorithms 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /pyMOE/export.py: -------------------------------------------------------------------------------- 1 | """ 2 | export.py 3 | Module containing several functions to export masks to gds. 4 | 5 | """ 6 | import cv2 7 | # import gdspy 8 | import numpy as np 9 | import gdspy 10 | 11 | 12 | 13 | ###Function exports an image file (converted to gray) into a gds file 14 | def grayim2gds(infile, outfile, pixelx, pixely, cellname, level, layer=0, datatype=0, verbose=False): 15 | """ 16 | (void) Transforms one image (converted to grayscale) into a gds, using cv2 17 | 18 | Args: 19 | :infile: input IMAGE file (on extension that cv2 can read ), e.g. "image.png" 20 | :outfile: output GDS file, e.g. "image.gds" 21 | :pixelx: pixel size in x, in um 22 | :pixely: pixel size in y, in um 23 | :cellname: string with cellname, e.g. "TOP" 24 | :level: int from 0 to 255, pixels gray value to be passed to GDS 25 | :layer: (optional) gray level, defaults to 0 26 | :datatype: (optional) gds datatype, defaults to 0 27 | :verbose: (optional) defaults to False, if True prints 28 | 29 | ---- 30 | Usage example: 31 | 32 | infilxe = "image.png" 33 | outfilxe = "image.gds" 34 | pixelx = 1 #um 35 | pixely = 1 #um 36 | grayim2gds(infilxe, outfilxe, pixelx, pixely,"TOP", 0) 37 | """ 38 | img = cv2.imread(infile, cv2.IMREAD_GRAYSCALE) 39 | 40 | h,w = img.shape 41 | #print(h) 42 | #print(w) 43 | 44 | pols=[] 45 | for i in np.arange(0,h): 46 | if verbose == True: 47 | print(i/h) 48 | for j in np.arange(0, w): 49 | #print(j/w) 50 | #here we can also think of selectin pixels at a certain level only 51 | #and creating a GDS from a grayscale image 52 | if img[i][j] == int(level): 53 | #print(i) 54 | #rectangle takes the two opposite corners 55 | pols.append(gdspy.Rectangle((pixelx*j,-pixely*i),(pixelx*(j+1), -pixely*(i+1)), layer, datatype)) 56 | 57 | if len(pols) !=0: 58 | polygons=gdspy.boolean(pols[0], pols[1:], "or") #max_points could be used 59 | 60 | #define current as Gdslib with default properties unit=1e-06, precision=1e-09 61 | gdspy.current_library = gdspy.GdsLibrary() 62 | cell = gdspy.Cell(cellname) 63 | cell.add(polygons) 64 | gdspy.write_gds(outfile) 65 | print("Exported the image file "+str(infile) + " into " + str(outfile)) 66 | 67 | else: 68 | print("There are no pixels in this gray level! Please try another gray level.") 69 | 70 | 71 | def grayim2gds_writer_frac(infile, outfile, pixelx, pixely, cellname, level, nm=None, layer=0, datatype=0 , verbose=False): 72 | """ 73 | (void) Transforms one image (converted to grayscale) into a gds, using cv2 74 | 75 | By default adds the image to (layer, datatype) = (0,0) 76 | 77 | Args: 78 | :infile: input IMAGE file (on extension that cv2 can read ), e.g. "image.png" 79 | :outfile: output GDS file, e.g. "image.gds" 80 | :pixelx: pixel size in x, in um 81 | :pixely: pixel size in y, in um 82 | :cellname: string with cellname, e.g. "TOP" 83 | :level: int from 0 to 255 (0 = black, 255=white) , pixels gray value to be passed to GDS 84 | :nm: (optional) If nm is given fractionates the image, nr of pixels for each fractioned part (should be multiple of pixel sizes), defaults to None 85 | :layer: (optional) gray level, defaults to 0 86 | :datatype: (optional) gds datatype, defaults to 0 87 | :verbose: (optional) defaults to False, if True prints 88 | 89 | ---- 90 | Usage example: 91 | 92 | infilxe = "image.png" 93 | outfilxe = "image.gds" 94 | pixelx = 1 #um 95 | pixely = 1 #um 96 | cellname = "TOP" #name of the gds cell 97 | graycolor = 0 #black pixels 98 | frac = 250 #size of frac pixels in the image 99 | 100 | grayim2gds_writer_frac(infilxe, outfilxe, pixelx, pixely, cellname, graycolor, frac, verbose=True) 101 | """ 102 | 103 | img = cv2.imread(infile, cv2.IMREAD_GRAYSCALE) 104 | 105 | if img is not None: 106 | print("Sucessfully imported img!") 107 | 108 | h,w = img.shape 109 | print(h) 110 | print(w) 111 | 112 | if nm == None: 113 | nmx = w 114 | nmy = h 115 | else: 116 | nmx = nm 117 | nmy = nm 118 | 119 | harray = np.arange(0,h+1,nmy) 120 | warray = np.arange(0,w+1,nmx) 121 | cn = 1 122 | #print(harray) 123 | 124 | for hn, hi in enumerate(harray): 125 | if hn == (len(harray)-1): 126 | 127 | break 128 | #print(hi) 129 | for hw, wi in enumerate(warray): 130 | if hw == (len(warray)-1): 131 | break 132 | 133 | #print(wi) 134 | 135 | pols=[] 136 | 137 | gdspy.current_library = gdspy.GdsLibrary() 138 | lib = gdspy.GdsLibrary() 139 | outfilen = str(cn)+outfile 140 | writer = gdspy.GdsWriter(outfilen,unit=1.0e-6,precision=1.0e-9) 141 | cell = lib.new_cell(cellname) 142 | 143 | for i in np.arange(hi,hi+nmy): 144 | cell.remove_polygons(lambda pts, layer, datatype: layer == 0) 145 | if verbose == True: 146 | print(i/h) 147 | for j in np.arange(wi, wi+nmx): 148 | #print(j/w) 149 | #here we can also think of selectin pixels at a certain level only 150 | #and creating a GDS from a grayscale image 151 | if img[i][j] == int(level): 152 | #rectangle takes the two opposite corners 153 | pols.append(gdspy.Rectangle((pixelx*j,-pixely*i),(pixelx*(j+1), -pixely*(i+1)), layer, datatype)) 154 | 155 | cell.add(pols) 156 | writer.write_cell(cell) 157 | del cell 158 | writer.close() 159 | cn = cn+1 160 | 161 | #print(cn) 162 | print("Exported the image file "+str(infile) + " into " + str(outfile)) 163 | 164 | 165 | def grayim2gds_writer(infile, outfile, pixelx, pixely, cellname, level, layer=0, datatype=0 , verbose=False): 166 | """ 167 | (void) Transforms one image (converted to grayscale) into a gds, using cv2 168 | by default adds the image to (layer, datatype) = (0,0) 169 | 170 | Args: 171 | :infile: input IMAGE file (on extension that cv2 can read ), e.g. "image.png" 172 | :outfile: output GDS file, e.g. "image.gds" 173 | :pixelx: pixel size in x, in um 174 | :pixely: pixel size in y, in um 175 | :cellname: string with cellname, e.g. "TOP" 176 | :level: int from 0 to 255 (0 = black, 255=white) , pixels gray value to be passed to GDS 177 | :layer: (optional) gray level, defaults to 0 178 | :datatype: (optional) gds datatype, defaults to 0 179 | :verbose: (optional) defaults to False, if True prints 180 | 181 | ---- 182 | Usage example: 183 | 184 | infilxe = "image.png" 185 | outfilxe = "image.gds" 186 | cellname = "TOP" #name of the gds cell 187 | graycolor = 0 #black pixels 188 | pixelx = 1 #um 189 | pixely = 1 #um 190 | 191 | grayim2gds_writer(infilxe, outfilxe, pixelx, pixely,cellname, graycolor, verbose=True)""" 192 | 193 | 194 | img = cv2.imread(infile, cv2.IMREAD_GRAYSCALE) 195 | 196 | if img is not None: 197 | print("Sucessfully imported img!") 198 | 199 | h,w = img.shape 200 | print(h) 201 | print(w) 202 | 203 | nmx = w 204 | nmy = h 205 | 206 | harray = np.arange(0,h+1,nmy) 207 | warray = np.arange(0,w+1,nmx) 208 | #print(harray) 209 | 210 | lib = gdspy.GdsLibrary() 211 | gdspy.current_library = gdspy.GdsLibrary() 212 | 213 | outfilen = outfile 214 | writer = gdspy.GdsWriter(outfilen,unit=1.0e-6,precision=1.0e-9) 215 | cell = lib.new_cell(cellname) 216 | 217 | pols = [] 218 | 219 | for hn, hi in enumerate(harray): 220 | if hn == (len(harray)-1): 221 | #writer.close() 222 | break 223 | #print(hi) 224 | for hw, wi in enumerate(warray): 225 | if hw == (len(warray)-1): 226 | break 227 | #print(wi) 228 | 229 | for i in np.arange(hi,hi+nmy): 230 | cell.remove_polygons(lambda pts, layer, datatype: layer == 0) 231 | if verbose == True: 232 | print(i/h) 233 | 234 | for j in np.arange(wi, wi+nmx): 235 | #print(j/w) 236 | #here we can also think of selectin pixels at a certain level only 237 | #and creating a GDS from a grayscale image 238 | if img[i][j] == int(level): 239 | #rectangle takes the two opposite corners 240 | pols.append(gdspy.Rectangle((pixelx*j,-pixely*i),(pixelx*(j+1), -pixely*(i+1)), layer, datatype)) 241 | 242 | cell.add(pols) 243 | writer.write_cell(cell) 244 | del cell 245 | 246 | writer.close() 247 | 248 | print("Exported the image file "+str(infile) + " into " + str(outfile)) 249 | 250 | def grayim2gds_writer_klops(infile, output_filename , pixelx, pixely, cellname, level, layer=0, datatype=0 , verbose=False): 251 | """ 252 | (void) Transforms one image (converted to grayscale) into a gds, using cv2 for reading the image 253 | by default adds the image to (layer, datatype) = (0,0) 254 | 255 | Args: 256 | :infile: input IMAGE file (on extension that cv2 can read ), e.g. "image.png" 257 | :output_filename: intermediate GDS file 258 | :pixelx: pixel size in x, in um 259 | :pixely: pixel size in y, in um 260 | :cellname: string with cellname, e.g. "TOP" 261 | :level: int from 0 to 255 (0 = black, 255=white) , pixels gray value to be passed to GDS 262 | :layer: (optional) gray level, defaults to 0 263 | :datatype: (optional) gds datatype, defaults to 0 264 | :verbose: (optional) defaults to False, if True prints 265 | 266 | """ 267 | 268 | img = cv2.imread(infile, cv2.IMREAD_GRAYSCALE) 269 | 270 | if img is not None: 271 | print("Sucessfully imported img!") 272 | 273 | h,w = img.shape 274 | print(h) 275 | print(w) 276 | 277 | nmx = w 278 | nmy = h 279 | 280 | harray = np.arange(0,h+1,nmy) 281 | warray = np.arange(0,w+1,nmx) 282 | #print(harray) 283 | 284 | lib = gdspy.GdsLibrary() 285 | gdspy.current_library = gdspy.GdsLibrary() 286 | 287 | #this is a gds file with single square that will be instantiated 288 | outfile="image.gds" 289 | writer = gdspy.GdsWriter(outfile,unit=1.0e-6,precision=1.0e-9) 290 | cell = lib.new_cell(cellname) 291 | 292 | i=0 293 | j=0 294 | 295 | rect = gdspy.Rectangle((pixelx*j,-pixely*i),(pixelx*(j+1), -pixely*(i+1)), layer, datatype) 296 | cell.add(rect) 297 | writer.write_cell(cell) 298 | writer.close() 299 | 300 | print("Exported the image file "+str(infile) + " into " + str(outfile)) 301 | 302 | #####-------------------------------- 303 | print("Starting making instances") 304 | 305 | import pya 306 | 307 | layout = pya.Layout() 308 | 309 | #input_filename = "fresnel_phase_mask_newlayers.gds" 310 | cell_name = "TOP" #name of the cell for instance array cannot be the same as the input gds filename 311 | 312 | #create cell at top 313 | top = layout.create_cell(cell_name) 314 | 315 | #gds files to read (could also be a list) 316 | gds_files = [outfile] 317 | 318 | for file in gds_files: 319 | layout.read(file) #read the files 320 | 321 | for top_cll in layout.top_cells(): 322 | if (top_cll.name != cell_name): #Don't insert in the top_cell 323 | cell_index = top_cll.cell_index() 324 | 325 | for hn, hi in enumerate(harray): 326 | if hn == (len(harray)-1): 327 | break 328 | #print(hi) 329 | for hw, wi in enumerate(warray): 330 | if hw == (len(warray)-1): 331 | break 332 | #print(wi) 333 | 334 | for i in np.arange(hi,hi+nmy): 335 | if verbose == True: 336 | print(i/h) 337 | 338 | for j in np.arange(wi, wi+nmx): 339 | #print(j/w) 340 | #here we can also think of selectin pixels at a certain level only 341 | #and creating a GDS from a grayscale image 342 | if img[i][j] == int(level): 343 | new_instance = pya.CellInstArray( cell_index, pya.Trans(pya.Vector(int(j*pixelx*1000),int(-i*pixely*1000))), pya.Vector(0, 0), pya.Vector(0, 0), 0, 0) 344 | top.insert( new_instance ) #insert the cell in the array 345 | else: 346 | if cellname==cell_name: 347 | print("Cell names seem to be the same. Proceed with caution, the final file might not be complete.") 348 | 349 | #write to gds 350 | layout.write(output_filename) 351 | print("Done") 352 | -------------------------------------------------------------------------------- /pyMOE/field.py: -------------------------------------------------------------------------------- 1 | """ 2 | field.py 3 | 4 | 5 | Definition of Class Field and related functions 6 | 7 | 8 | """ 9 | 10 | import numpy as np 11 | 12 | 13 | from pyMOE.aperture import Aperture 14 | 15 | 16 | 17 | class Field: 18 | """ 19 | Class Field: 20 | Creates an E Field object is an of values corresponding 21 | to an input or modulated electric field 22 | 23 | 24 | Args: 25 | :x: Vector for the x axis 26 | :y: Vector for the y axis 27 | 28 | Methods: 29 | :field: returns the field 30 | :shape: returns the shape of the field 31 | 32 | """ 33 | def __init__(self, x, y): 34 | self.x = x 35 | self.y = y 36 | self.XX, self.YY = np.meshgrid(x, y) # indexing='ij') 37 | self.pixel_x = self.x[1]-self.x[0] 38 | self.pixel_y = self.y[1]-self.y[0] 39 | 40 | self.field = np.zeros(self.XX.shape, dtype=complex) 41 | 42 | @property 43 | def shape(self): 44 | return self.field.shape 45 | @property 46 | def amplitude(self): 47 | return np.abs(self.field) 48 | @property 49 | def phase(self): 50 | return np.angle(self.field) 51 | @property 52 | def intensity(self): 53 | return np.abs(self.field)**2 54 | 55 | 56 | 57 | def create_empty_field(xmin, xmax, N_x, ymin, ymax, N_y): 58 | """ 59 | Creates an empty field max of the mesh dimensions provided 60 | 61 | Args: 62 | :xmin, xmax: range for x 63 | :N_x: number of x points 64 | :ymin, ymax: range for y 65 | :N_y: number of y points 66 | 67 | Returns: 68 | :field: empty Field 69 | """ 70 | x = np.linspace(xmin, xmax, N_x) 71 | y = np.linspace(ymin, ymax, N_y) 72 | 73 | return Field(x,y) 74 | 75 | def create_empty_field_from_field(field): 76 | """ 77 | Creates an empty field with the same spatial dimensions of the given field 78 | 79 | Args: 80 | :field: field 81 | Returns: 82 | :field: empty Field of same spatial dimensions 83 | """ 84 | assert type(field) is Field, "aperture must be of type Field" 85 | 86 | 87 | return Field(field.x, field.y) 88 | 89 | def create_empty_field_from_aperture(aperture): 90 | """ 91 | Creates an empty field with the same spatial dimensions of the given aperture 92 | but does not modulate the field. 93 | 94 | Args: 95 | :aperture: aperture 96 | Returns: 97 | :field: empty Field of same spatial dimensions 98 | """ 99 | assert type(aperture) is Aperture, "aperture must be of type Aperture" 100 | 101 | 102 | return Field(aperture.x, aperture.y) 103 | 104 | 105 | def modulate_field(field, amplitude_mask=None, phase_mask=None): 106 | """ 107 | Modulates the input field with the given amplitude or phase mask. 108 | If amplitude_mask is not given, it assumes an amplitude of 1 for the amplitude modulatiom. 109 | If phase is not given, it assumes a phase of 0 for the phase modulation 110 | The field dimensions and the provided masks must be the same otherwise it raises an error. 111 | 112 | Args: 113 | :field: Input field to be modulated 114 | :amplitude_mask: Mask of amplitude aperture 115 | :phase_mask: Mask of phase aperture 116 | Returns: 117 | :field: returns the modulated field. 118 | """ 119 | assert type(field) is Field, "field must be of type Field" 120 | 121 | 122 | modulation_amplitude = np.ones(field.XX.shape) 123 | modulation_phase = np.zeros(field.XX.shape) 124 | 125 | if amplitude_mask is not None: 126 | assert type(amplitude_mask) is Aperture, "amplitude_mask must be of type Aperture" 127 | assert np.all(amplitude_mask.XX == field.XX) and np.all(amplitude_mask.YY == field.YY), "Spatial dimensions of field and amplitude_mask must be the same" 128 | modulation_amplitude = amplitude_mask.aperture 129 | if phase_mask is not None: 130 | assert type(phase_mask) is Aperture, "phase_mask must be of type Aperture" 131 | assert np.all(phase_mask.XX == field.XX) and np.all(phase_mask.YY == field.YY), "Spatial dimensions of field and phase_mask must be the same" 132 | modulation_phase = phase_mask.aperture 133 | 134 | # Creates a new empty field to store the modulated field 135 | modulated_field = create_empty_field_from_field(field) 136 | 137 | # Calculates the modulation field from the provided masks 138 | modulation = modulation_amplitude *np.exp(1.0j*modulation_phase) 139 | 140 | # Modulates the input field 141 | modulated_field.field = field.field*modulation 142 | 143 | 144 | return modulated_field 145 | 146 | 147 | def generate_uniform_field(field, E0=1 ): 148 | """ 149 | Generates a unifor, wavefront field with amplitude E 150 | 151 | Args: 152 | :field: Input field object to add the flat field 153 | :E0: Amplitude of the electric field 154 | Returns: 155 | :field: returns the field. 156 | """ 157 | assert type(field) is Field, "field must be of type Field" 158 | 159 | field.field = np.ones(field.XX.shape)*E0 160 | 161 | 162 | return field 163 | 164 | def generate_gaussian_field(field, E0, w0, center=(0,0) ): 165 | """ 166 | Generates a Gaussian beam amplitude (no imaginary part) 167 | **to be deprecated in favor of generate_gaussian_beam 168 | 169 | E = E0*exp(-r^2/w0^2) 170 | E0 - amplitude on axis 171 | r - radius 172 | w0 - radius where amplitude is 1/e of it's on axis value 173 | 174 | Args: 175 | :field: Input field object to add the flat field 176 | :E: Amplitude of the electric field 177 | Returns: 178 | :field: returns the field. 179 | """ 180 | assert type(field) is Field, "field must be of type Field. Field is type %s"%(type(field)) 181 | 182 | 183 | x0,y0 = center 184 | 185 | field.field = E0*np.exp(-((field.XX-x0)**2+(field.YY-y0)**2)/(w0**2)) 186 | 187 | 188 | return field 189 | 190 | 191 | 192 | def generate_gaussian_beam(field, w0, z, wavelength, center=(0,0), E0=1 ): 193 | """ 194 | Generates a Gaussian beam (with wimaginary part) https://spie.org/publications/spie-publication-resources/optipedia-free-optics-information/fg12_p18-19_gaussian_beams#_=_ 195 | 196 | 197 | Args: 198 | :field: Input field object to add the flat field 199 | :w0: Beam waist at z=0 200 | :z: Z-coordinate, with origin in minimum waist 201 | :wavelength: Wavelength in meters 202 | :center: center in (x,y) plane, default (0,0) 203 | :E0: Amplitude of the electric field 204 | Returns: 205 | :field: returns the field. 206 | """ 207 | assert type(field) is Field, "field must be of type Field. Field is type %s"%(type(field)) 208 | 209 | x0,y0 = center 210 | 211 | k = 2*np.pi/wavelength 212 | r2 = ((field.XX-x0)**2+(field.YY-y0)**2) 213 | 214 | zR = np.pi*w0**2/wavelength 215 | wz = w0*np.sqrt(1+(z/zR)**2) 216 | Rz = z*(1+(zR/z)**2) 217 | phi = k*z - np.arctan(z/zR)+ k* r2/(2*Rz) 218 | 219 | Exyz = E0 * np.exp(-r2/wz**2) * np.exp(1.0j * phi) 220 | 221 | field.field = Exyz 222 | 223 | return field 224 | 225 | 226 | 227 | 228 | 229 | class Screen: 230 | """ 231 | Class Screen: 232 | Creates a Screen object that is 1D or 2D and has internal xyz coordinates 233 | to facilitate the propagation of a field onto a target screen 234 | 235 | 236 | 237 | Args: 238 | :x: Vector for the x axis 239 | :y: Vector for the y axis 240 | :z: Vector for the z axis 241 | 242 | Methods: 243 | :screen: returns the field on the screen 244 | :shape: returns the shape of the field 245 | 246 | """ 247 | def __init__(self, x, y, z): 248 | self.x = x 249 | self.y = y 250 | self.z = z 251 | self.XX, self.YY, self.ZZ = np.meshgrid(x, y, z)#, indexing='ij') 252 | # self.pixel_x = self.x[1]-self.x[0] 253 | # self.pixel_y = self.y[1]-self.y[0] 254 | # self.pixel_z = self.z[1]-self.z[0] 255 | 256 | self.n = np.ones(self.XX.shape) 257 | 258 | self.screen = np.zeros(self.XX.shape, dtype=complex) 259 | @property 260 | def shape(self): 261 | return self.screen.shape 262 | @property 263 | def amplitude(self): 264 | return np.abs(self.screen) 265 | @property 266 | def phase(self): 267 | return np.angle(self.screen) 268 | @property 269 | def intensity(self): 270 | return np.abs(self.screen)**2 271 | @property 272 | def nindex(self): 273 | return self.n 274 | 275 | 276 | 277 | 278 | def create_screen_XY(xmin, xmax, N_x, ymin, ymax, N_y, z): 279 | """ 280 | Creates an empty screen of the mesh dimensions provided 281 | 282 | Args: 283 | :xmin, xmax: range for x 284 | :N_x: number of x points 285 | :ymin, ymax: range for y 286 | :N_y: number of y points 287 | :z: z position of the screen plane 288 | 289 | Returns: 290 | :screen: empty Screen 291 | """ 292 | x = np.linspace(xmin, xmax, N_x) 293 | y = np.linspace(ymin, ymax, N_y) 294 | z=z 295 | 296 | return Screen(x,y,z) 297 | 298 | 299 | 300 | 301 | def create_screen_YZ(ymin, ymax, N_y, zmin, zmax, N_z, x=0): 302 | """ 303 | Creates an empty screen of the mesh dimensions provided 304 | 305 | Args: 306 | :ymin, ymax: range for y 307 | :N_y: number of y points 308 | :zmin, zmax: range for z 309 | :N_z: number of z points 310 | :x: x position of the screen plane 311 | 312 | Returns: 313 | :screen: empty Screen 314 | """ 315 | x=x 316 | y = np.linspace(ymin, ymax, N_y) 317 | z = np.linspace(zmin, zmax, N_z) 318 | 319 | return Screen(x,y,z) 320 | 321 | 322 | 323 | def create_screen_ZZ(zmin, zmax, N_z, x=0, y=0, log=False): 324 | """ 325 | Creates an empty screen of the mesh dimensions provided 326 | 327 | Args: 328 | :zmin, zmax: range for z 329 | :N_z: number of z points 330 | :x: x position of the screen line 331 | :y: y position of the screen line 332 | 333 | Returns: 334 | :screen: empty Screen 335 | """ 336 | z = np.linspace(zmin, zmax, N_z) 337 | 338 | if log==True: 339 | z = np.logspace(zmin, zmax, N_z) 340 | y=y 341 | x=x 342 | 343 | return Screen(x,y,z) -------------------------------------------------------------------------------- /pyMOE/holograms.py: -------------------------------------------------------------------------------- 1 | """ 2 | holograms.py 3 | Library module for hologram generation and visualiation 4 | """ 5 | 6 | 7 | from PIL import Image 8 | import numpy as np 9 | 10 | from pyMOE.utils import progress_bar, Timer, mean_squared_error, discretize_array 11 | 12 | from pyMOE.propagate import * 13 | 14 | class Field(): 15 | """ 16 | Class to define complex fields with methods for amplitude and phase""" 17 | def __init__(self, a,b): 18 | self.signal = a+1j*b 19 | 20 | @property 21 | def phase(self): 22 | return np.angle(self.signal) 23 | 24 | @phase.setter 25 | def phase(self, phase): 26 | assert phase.shape == self.signal.shape, "Provided array shape must match existing signal" 27 | self.signal = self.amplitude*np.exp(1j*phase) 28 | 29 | @property 30 | def amplitude(self): 31 | return np.abs(self.signal) 32 | 33 | @amplitude.setter 34 | def amplitude(self, amplitude): 35 | assert amplitude.shape == self.signal.shape, "Provided array shape must match existing signal" 36 | self.signal = amplitude*np.exp(1j*self.phase) 37 | 38 | @property 39 | def intensity(self): 40 | return np.power(self.amplitude,2) 41 | 42 | 43 | def algorithm_Gerchberg_Saxton(target_intensity, iterations=3, levels=None, input_phase=None, source_beam=None, verbose=True): 44 | """ 45 | Creates hologram phase mask to generate target intensity using Gerchberg Saxton Algorithm. 46 | 47 | U0 - input field 48 | U1 - calculated far-field 49 | 50 | Args: 51 | :target_intensity: 2D array of intensity values 52 | :levels: Scalar or array: levels to consider in the phase mask as physical constraint. If None, it does not discretize. 53 | :input_phase: If given, uses this input_phase as starting phase instead of random. 54 | 55 | Returns: 56 | :phase_mask: 2D phase mask 57 | :error_list: list of errors measured in each iteration 58 | """ 59 | shape = target_intensity.shape 60 | 61 | if levels is not None: 62 | if isinstance(levels, int): 63 | levels = np.linspace(-np.pi, np.pi, levels, endpoint=False) 64 | 65 | # Due to the way numpy fft works, we must first fftshift all fields 66 | target_intensity = np.fft.fftshift(target_intensity) 67 | 68 | if input_phase is not None: 69 | input_phase = np.fft.fftshift(input_phase) 70 | field_0.phase = input_phase 71 | else: 72 | input_phase = np.random.random(shape)*2*np.pi 73 | if source_beam is not None: 74 | source_beam = np.fft.fftshift(source_beam) 75 | else: 76 | source_beam = np.ones(shape) 77 | 78 | field_0 = Field(source_beam, input_phase) 79 | 80 | field_1 = Field(np.ones(shape), input_phase) 81 | 82 | list_iteration_errors = [] 83 | 84 | with Timer("Gerchberg Saxton Algorithm"): 85 | for i in range(iterations): 86 | 87 | # Add input beam amplitude 88 | field_0.amplitude = source_beam 89 | 90 | # Apply physical constraints 91 | 92 | # Discretize phase array to the levels 93 | if levels is not None: 94 | physical_phase = discretize_array(field_0.phase, levels) 95 | field_0.phase = physical_phase 96 | 97 | # Output_phase is here 98 | phase_mask = field_0.phase 99 | 100 | # Calculate forward Fourier Transform 101 | field_1.signal = np.fft.fft2(field_0.signal) 102 | 103 | # Calculate error metrics 104 | # TODO 105 | norm_amplitude = field_1.amplitude/field_1.amplitude.max() 106 | error = mean_squared_error(norm_amplitude, target_intensity) 107 | 108 | list_iteration_errors.append(error) 109 | field_1.amplitude = target_intensity 110 | 111 | # Calculate inverse Fourier Transform 112 | field_0.signal = np.fft.ifft2(field_1.signal) 113 | 114 | if verbose: 115 | progress_bar(i/iterations) 116 | 117 | if verbose: 118 | progress_bar(1) 119 | phase_mask = np.fft.fftshift(phase_mask) 120 | return phase_mask, list_iteration_errors 121 | 122 | def calculate_phase_farfield(phase, source_beam=None): 123 | """ 124 | Calculates a proportional far field of a given phase aperture and source_beam 125 | 126 | TODO Fraunhofer propagation 127 | 128 | Args: 129 | :phase: phase mask 130 | :source_beam: if not given, assumes constant intensity=1 131 | 132 | Returns 133 | :far_field: far field amplitude 134 | """ 135 | #fraunhofer(z, mask, npixmask, pixsizemask, npixscreen, dxscreen, dyscreen, wavelength) 136 | 137 | shape = phase.shape 138 | if source_beam is not None: 139 | source_beam = np.fft.fftshift(source_beam) 140 | else: 141 | source_beam = np.ones(shape) 142 | phase_mask = np.fft.fftshift(phase) 143 | field_0 = Field(np.ones(shape), np.zeros(shape)) 144 | field_0.amplitude = source_beam 145 | field_0.phase = phase_mask 146 | field_1 = Field(np.ones(shape), np.zeros(shape)) 147 | 148 | field_1.signal = np.fft.fft2(field_0.signal) 149 | field_1.signal = np.fft.fftshift(field_1.signal) 150 | 151 | 152 | 153 | return field_1.amplitude 154 | 155 | 156 | 157 | def correct_mask_shift(mask): 158 | return np.fft.fftshift(mask) -------------------------------------------------------------------------------- /pyMOE/importing.py: -------------------------------------------------------------------------------- 1 | """ 2 | importing.py 3 | Module containing functions to import gds files, inspect them in matplotlib and save gds files as image files 4 | 5 | 6 | """ 7 | 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | import gdspy as gdspy 11 | from gdspy import FlexPath 12 | from shapely.geometry import MultiPolygon, Polygon 13 | import pickle 14 | import cv2 15 | 16 | 17 | def makesubplot(x,y, *argv, **kwargs): 18 | """ 19 | Makes a subplot object for plotting 20 | """ 21 | #plot in subplot 22 | axes = kwargs.pop("axes", None) 23 | if not axes: 24 | fig, axes = plt.subplot() 25 | 26 | return axes.plot(x,y, *argv, **kwargs) 27 | 28 | 29 | def inspect_gds2(filename, colors, rescale=0, **kwargs): 30 | """ 31 | Plots the gds for inspection in python matplotlib, only with the borders of the features 32 | Note: if plotting different gds file together, please make sure they are aligned (e.g. centered at origin) 33 | 34 | Args: 35 | :filename: string gds filename (e.g. 'yolo.gds') 36 | :color: string color name for plotting 37 | :rescale: int rescaling factor for all points in mask, by default is 0 38 | 39 | """ 40 | 41 | lib = gdspy.GdsLibrary(infile=filename) 42 | main_cell = lib.top_level()[0] 43 | 44 | axess = kwargs.pop("axes", None) 45 | 46 | #control vars 47 | n=0 48 | ct=1 49 | 50 | #while it is empty read again 51 | while main_cell.polygons ==[]: 52 | lib = gdspy.GdsLibrary(infile=filename) 53 | main_cell = lib.top_level()[0] 54 | n=n+1 #just a control to avoid infinite loop 55 | if n==10: #if we got to 10 trials 56 | print("Cannot read polygons in this GDS file after"+str(n)+" trials.") 57 | break 58 | else: 59 | if main_cell.polygons !=[]: 60 | print(str(np.size(main_cell.polygons))+" polygons found...") 61 | pol_dict = main_cell.get_polygons(by_spec=False) 62 | #we first get all the polygons 63 | #print(np.size(pol_dict[:])) 64 | for po, pod in enumerate(pol_dict[:]): 65 | polygon1 = Polygon(pod) 66 | x1,y1 = polygon1.exterior.xy 67 | if rescale!=0: 68 | x1= np.array(x1)*rescale #get coords in um 69 | y1= np.array(y1)*rescale #get coords in um 70 | axess.fill(x1, y1, alpha=0.5, fc=colors, ec='none') 71 | makesubplot(x1,y1, color=colors, axes=axess) 72 | 73 | #now we get all the paths 74 | main_cell = lib.top_level()[0] 75 | paths = main_cell.get_paths() 76 | 77 | layers = list(main_cell.get_layers()) 78 | print(layers) 79 | datatps = list(main_cell.get_datatypes()) 80 | 81 | if main_cell.get_paths() !=[]: 82 | print(str(np.size(main_cell.get_paths()))+" paths found...") 83 | for po, pod in enumerate(paths): 84 | #print(po) 85 | #to be sure we get all the polygons lets make the paths thicker 86 | polypath = FlexPath(paths[po].points, width =0.1, offset=0, corners='natural', ends='flush', bend_radius=None, tolerance=0.01, precision=0.001, max_points=1000000, gdsii_path=False, width_transform=True, layer=2, datatype=0) 87 | polygon = polypath.get_polygons(by_spec=False) 88 | 89 | #print(polygon) 90 | if polygon!=[]: 91 | if ct==1: 92 | print("And paths are being converted to polygons...") 93 | ct=0 94 | polygon1 = Polygon(polygon[0]) 95 | #x1,y1 = polygon1.interior.xy 96 | x1,y1 = polygon1.exterior.xy 97 | if rescale!=0: 98 | x1= np.array(x1)*rescale #get coords in um 99 | y1= np.array(y1)*rescale #get coords in um 100 | 101 | #axess.fill(x1, y1, alpha=0.5, fc=colors, ec='none') 102 | makesubplot(x1,y1, color=colors, axes=axess) 103 | 104 | axess.set_aspect('equal') 105 | print("Done.") 106 | 107 | 108 | def inspect_gds2layers(filename, norm, rescale=0,verbose = False, **kwargs ): 109 | """ 110 | Returns cell, polygon dictionary, (xmin,xmax) and (ymin,ymax) for representation of the gds layers into a grayscale image 111 | 112 | Args: 113 | :filename: string gds filename (e.g. 'yolo.gds') 114 | :norm: maximum level of gray (e.g. 128) 115 | :verbose: if True show some information while doing the operations. defaults false 116 | 117 | for **kwargs, add 'axes = subplot', where subplot has been previously defined as subplot = fig.add_subplot(111) 118 | with 'import matplotlib.pyplot as plt' and 'fig = plt.figure()' 119 | """ 120 | 121 | 122 | #create cell to store the layers 123 | cell_multipol = gdspy.Cell('top') 124 | 125 | lib = gdspy.GdsLibrary(infile=filename) 126 | main_cell = lib.top_level()[0] 127 | 128 | axess = kwargs.pop("axes", None) 129 | 130 | #control var 131 | n=0 132 | 133 | #while it is empty read again 134 | while main_cell.polygons ==[]: 135 | try: 136 | lib = gdspy.read_gds(infile=filename) 137 | except: 138 | continue 139 | 140 | main_cell = lib.top_level()[0] 141 | n=n+1 #just a control to avoid infinite loop 142 | if n==10: #if we got to 10 trials 143 | print("Cannot read polygons in this GDS file after "+str(n)+" trials.") 144 | break 145 | else: 146 | if main_cell.polygons !=[]: 147 | print(str(np.size(main_cell.polygons))+" polygons found...") 148 | pol_dict = main_cell.get_polygons(by_spec=True) 149 | 150 | layers = list(main_cell.get_layers()) 151 | datatps = list(main_cell.get_datatypes()) 152 | 153 | if verbose == True: 154 | print("Layers: "+ str(layers)) 155 | print("Datatypes: "+ str(datatps)) 156 | 157 | xmaxs =[] 158 | ymaxs =[] 159 | xmins =[] 160 | ymins =[] 161 | 162 | for i in layers: 163 | for j in datatps: #here is fine because datatps is single element 164 | if np.max(layers)>=norm: 165 | gss_norm = i/np.max(layers) 166 | #print(gss_norm) 167 | else: #the levels are within 0-127 range, the gray level is norm to the available levels 168 | gss_norm = i/(norm-1) 169 | #print(gss_norm) 170 | 171 | ### lower levels show more black 172 | #(0,0,0) is black 173 | #(1,1,1) is white 174 | #we want lower levels to show more black 175 | 176 | colorx=(gss_norm,gss_norm,gss_norm) 177 | 178 | #print(i) 179 | for k, pols in enumerate(pol_dict[(i,j)][:]): 180 | pol_dict[(i,j)][k] = Polygon(pol_dict[(i,j)][k]) 181 | x1,y1 = pol_dict[(i,j)][k].exterior.xy 182 | if rescale!=0: 183 | x1= np.array(x1)*rescale #get coords in um 184 | y1= np.array(y1)*rescale #get coords in um 185 | #print(x1) 186 | axess.fill(x1, y1, fc=colorx) 187 | 188 | xmaxs = np.append(np.max(x1),xmaxs) 189 | ymaxs = np.append(np.max(y1),ymaxs) 190 | xmins = np.append(np.min(x1),xmins) 191 | ymins = np.append(np.min(y1),ymins) 192 | 193 | 194 | pol_dict[(i,j)] = MultiPolygon(pol_dict[(i,j)]) 195 | polslayer = gdspy.PolygonSet(pol_dict[(i,j)], layer=i) 196 | #if polslayer.is_valid ==False: 197 | # if verbose == True: 198 | # print(i/(norm-1)) 199 | # print("There is an invalid polygon at "+str(i)+", but I will proceed, please check the result at the end") 200 | 201 | 202 | cell_multipol.add(polslayer) 203 | 204 | xmx = np.max(xmaxs) 205 | ymx = np.max(ymaxs) 206 | xmn = np.min(xmins) 207 | ymn = np.min(ymins) 208 | 209 | print("xmax is "+ str(xmx)) 210 | print("ymax is "+ str(ymx)) 211 | print("xmin is "+ str(xmn)) 212 | print("ymin is "+ str(ymn)) 213 | 214 | axess.set_xlim([xmn, xmx]) 215 | axess.set_ylim([ymn, ymx]) 216 | #axess.margins('tight') 217 | 218 | 219 | return cell_multipol, pol_dict, xmn, xmx, ymn, ymx 220 | 221 | 222 | def inspect_gds2layersplt(filename, norm, rescale=0, verbose = False, **kwargs ): 223 | """ 224 | Returns cell, polygon dictionary, (xmin,xmax) and (ymin,ymax) for representation of ALL gds layers into a grayscale image 225 | 226 | Args: 227 | :filename: string gds filename (e.g. 'yolo.gds') 228 | :norm: maximum level of gray (e.g. 128) 229 | :verbose: if True show some information while doing the operations. defaults false 230 | 231 | for **kwargs, add 'axes = subplot', where subplot has been previously defined as subplot = fig.add_subplot(111) 232 | with 'import matplotlib.pyplot as plt' and 'fig = plt.figure()' 233 | """ 234 | 235 | #create cell to store the layers 236 | gdspy.current_library = gdspy.GdsLibrary() 237 | cell_multipol = gdspy.Cell('top') 238 | 239 | lib = gdspy.GdsLibrary(infile=filename) 240 | main_cell = lib.top_level()[0] 241 | 242 | axess = kwargs.pop("axes", None) 243 | #print(axess) 244 | #print(*kwargs) 245 | 246 | #control var 247 | n=0 248 | 249 | #while it is empty read again 250 | if main_cell.polygons ==[]: 251 | lib = gdspy.GdsLibrary(infile=filename) 252 | #try: 253 | # lib = gdspy.read_gds(infile=filename) 254 | #except: 255 | # continue 256 | 257 | main_cell = lib.top_level()[0] 258 | 259 | try: 260 | pol_dict = main_cell.get_polygons(by_spec=True) 261 | except: 262 | print("Cannot read polygons in this GDS file after "+str(n)+" trials.") 263 | 264 | else: 265 | if main_cell.polygons !=[]: 266 | print(str(np.size(main_cell.polygons))+" polygons found...") 267 | pol_dict = main_cell.get_polygons(by_spec=True) 268 | 269 | layers = list(main_cell.get_layers()) 270 | datatps = list(main_cell.get_datatypes()) 271 | 272 | if verbose == True: 273 | print("Layers: "+ str(layers)) 274 | print("Datatypes: "+ str(datatps)) 275 | 276 | xmaxs =[] 277 | ymaxs =[] 278 | xmins =[] 279 | ymins =[] 280 | 281 | for i in layers: 282 | for j in datatps: #here is fine because datatps is single element 283 | if np.max(layers)>=norm: 284 | gss_norm = i/np.max(layers) 285 | #print(gss_norm) 286 | else: #the levels are within 0-127 range, the gray level is norm to the available levels 287 | gss_norm = i/(norm-1) 288 | #print(gss_norm) 289 | 290 | ### lower levels show more black 291 | #(0,0,0) is black 292 | #(1,1,1) is white 293 | #we want lower levels to show more black 294 | 295 | colorx=(gss_norm,gss_norm,gss_norm) 296 | 297 | #print(i) 298 | for k, pols in enumerate(pol_dict[(i,j)][:]): 299 | pol_dict[(i,j)][k] = Polygon(pol_dict[(i,j)][k]) 300 | x1,y1 = pol_dict[(i,j)][k].exterior.xy 301 | if rescale!=0: 302 | x1= np.array(x1)*rescale #get coords in um 303 | y1= np.array(y1)*rescale #get coords in um 304 | #print(x1) 305 | fplot = axess.fill(x1, y1, fc=colorx) 306 | axess.axis('off') 307 | #fplot.axes.get_xaxis().set_visible(False) 308 | #fplot.axes.get_yaxis().set_visible(False) 309 | 310 | xmaxs = np.append(np.max(x1),xmaxs) 311 | ymaxs = np.append(np.max(y1),ymaxs) 312 | xmins = np.append(np.min(x1),xmins) 313 | ymins = np.append(np.min(y1),ymins) 314 | 315 | 316 | pol_dict[(i,j)] = MultiPolygon(pol_dict[(i,j)]) 317 | polslayer = gdspy.PolygonSet(pol_dict[(i,j)], layer=i) 318 | #if polslayer.is_valid ==False: 319 | # if verbose == True: 320 | # print(i/(norm-1)) 321 | # print("There is an invalid polygon at "+str(i)+", but I will proceed, please check the result at the end") 322 | 323 | cell_multipol.add(polslayer) 324 | 325 | 326 | xmx = np.max(xmaxs) 327 | ymx = np.max(ymaxs) 328 | xmn = np.min(xmins) 329 | ymn = np.min(ymins) 330 | axess.xlim([xmn,xmx]) 331 | axess.ylim([ymn,ymx]) 332 | #axess.savefig("special.tiff", bbox_inches=0, pad_inches = 0) 333 | 334 | print("xmax is "+ str(xmx)) 335 | print("ymax is "+ str(ymx)) 336 | print("xmin is "+ str(xmn)) 337 | print("ymin is "+ str(ymn)) 338 | 339 | return cell_multipol, pol_dict, xmn, xmx, ymn, ymx 340 | 341 | 342 | 343 | def gds2img(infile,outfile,norm, rescaled=0, verbose=False): 344 | """ 345 | (void) plots the gds for inspection in python matplotlib and saves the figure to image file 346 | Note: if plotting different gds file together, please make sure they are aligned (e.g. centered at origin) 347 | 348 | Args: 349 | :infile: string gds filename (e.g. 'yolo.gds') 350 | :outfile: string img filename (e.g. 'yolo.tiff') 351 | :norm: maximum level of gray (e.g. 128) 352 | :rescaled: if !=0 will rescale the layout 353 | :verb: if True, shows verbose, defaults to False 354 | """ 355 | 356 | fig = plt.figure() 357 | 358 | cell_multipol, pol_dict, xmn, xmx, ymn, ymx = inspect_gds2layersplt(infile,norm,rescale=rescaled, verbose = verbose, axes=plt) 359 | 360 | ax = fig.add_subplot(1,1,1) 361 | 362 | fig.set_dpi(1) 363 | 364 | plt.axis('equal') 365 | plt.axis('off') 366 | 367 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 368 | fig.tight_layout(w_pad=0, h_pad=0, pad =0) 369 | 370 | plt.gca().set_axis_off() 371 | plt.subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, hspace = 0, wspace = 0) 372 | plt.margins(0,0) 373 | 374 | fig.set_size_inches(((xmx-xmn)), ((ymx-ymn))) 375 | 376 | fig.savefig("temp.png", bbox_inches=0,pad_inches = 0, dpi=1) 377 | 378 | #remove any white padding 379 | im = cv2.imread("temp.png") 380 | 381 | cv2.imwrite(outfile, im) 382 | 383 | print("Imported file "+infile+" and exported into "+outfile+ " with size "+ str(int(xmx)) + " x " + str(int(ymx)) + " pixels.") 384 | 385 | -------------------------------------------------------------------------------- /pyMOE/metas.py: -------------------------------------------------------------------------------- 1 | """ 2 | metas.py 3 | Module containing functions to create metasurfaces from phase masks 4 | 5 | 6 | """ 7 | 8 | import cv2 9 | import gdspy 10 | import numpy as np 11 | from pyMOE.utils import progress_bar, Timer 12 | from pyMOE.gds_klops import rescale_layout, rotate_layout 13 | 14 | import pya 15 | 16 | 17 | def metasurface_from_phase(xsiz, ysiz, pixelx, pixely, p, aperture_vals, topcellname, outfilen, gdspyelements='pillar', \ 18 | verbose=False, rotation=None, scaling=None, grid='square', mindim = 0.05, smallerdim =0, \ 19 | largest_phase=None): 20 | """ 21 | Transform a 2D array (aperture_vals) representing the phase into a 2D metasurface and saves it to gds 22 | 23 | Args: 24 | :xsiz: x size of aperture in x in um 25 | :ysiz: y size of aperture size in y in um 26 | :pixelx: pixel size in x in um 27 | :pixely: pixel size in y in um 28 | :p: periodicity in um 29 | :aperture_vals: 2D array with the phase 30 | :topcellname: string with name of top cell, e.g. 'TOP' 31 | :oufilen: string filename of output gds 32 | :gdspyelements: gdspy element to be used as individual meta-element (also accepts array of such elements for iteration, with same dimension as unique values in aperture_vals). If == 'pillar' (default) -> gdspy circle with 1 um diameter 33 | :verbose: if True, prints during execution 34 | :rotation: array with the rotation angles of unique meta-elements (1:1 correspondence with unique aperture_vals values!), Rotation angle is anti-clockwise in radians. If None (default), sets the rotation angle = 0 for all elements. 35 | :scaling: array with the scaling factor of unique meta-element (1:1 correspondence with unique aperture_vals values!), Scaling factor is with respect to the dimension of the individual meta-element. If None (default), sets scaling factor to 1.0 for all elements. 36 | :grid: Type of grid, options are 'square' or 'hex'. Default is 'square'. Please make sure the aperture_vals have been evaluated in an hexagonal grid, to make sure the values match. 37 | :mindim: clipping scaling factor (cannot scale below a certain value, to avoid very small elements) 38 | :smallerdim: lowest scaling factor 39 | :largest_phase: largest phase in the phase mask. If None, takes the maximum of aperture_vals 40 | 41 | Returns: 42 | None 43 | """ 44 | from gdspy import Polygon, PolygonSet 45 | from pyMOE.utils import Timer, progress_bar 46 | 47 | #total number of elements count 48 | tot_meta = 0 49 | 50 | #some global variables 51 | tolerance, nr_points, mindim, smallerdim = 0.001, 15, 0.05, 0 52 | 53 | #Start the metasurface library 54 | lib = gdspy.GdsLibrary() 55 | 56 | if largest_phase is None: 57 | largest_phase = np.max(aperture_vals) 58 | 59 | #Extract unique values of phase from the aperture 2D array 60 | phase_array = np.unique(aperture_vals[aperture_vals <=largest_phase]) 61 | 62 | ############################################################## 63 | ###Various options for the metasurface currently given as args 64 | if rotation is not None: 65 | flag=1 66 | if isinstance(rotation, (int, float)): 67 | rotation_array, flag = np.ones(len(phase_array)) * rotation, 0 68 | else: 69 | assert len(rotation)==len(phase_array), "The length of unique phase values and rotation array is different." 70 | rotation_array, flag = rotation , 0 71 | if flag: 72 | print("Unsuported rotation argument!") 73 | else: 74 | rotation_array = np.zeros(len(phase_array)) #default rotate by 0 degs 75 | 76 | ################################# 77 | if scaling is not None: 78 | scaling_flag=0 79 | flag=1 80 | if isinstance(scaling, (int, float)): 81 | scaling_array, flag = np.ones(len(phase_array)) * scaling, 0 82 | else: 83 | assert len(scaling)==len(phase_array), "The length of unique phase values and rscaling array is different." 84 | scaling_array, flag = scaling, 0 85 | if flag: 86 | print("Unsuported scaling argument!") 87 | else: 88 | scaling_array = np.ones(len(phase_array)) #default scale by 1 89 | scaling_flag = 1 90 | 91 | ################################# 92 | if grid == 'square': 93 | xv, yv = np.meshgrid(np.arange(0, xsiz, pixelx, dtype=float), np.arange(0, ysiz, pixely, dtype=float)) 94 | positions = np.array([xv.ravel(), yv.ravel()]) 95 | positions_xv = positions[0] 96 | positions_yv = positions[1] 97 | 98 | elif grid == 'hex': 99 | x= np.arange(0, xsiz, pixelx, dtype=float) # arange is preferred over linspace because it keeps the pixel size! 100 | y= np.arange(0, ysiz, pixely, dtype=float) # arange is preferred over linspace because it keeps the pixel size! 101 | xv, yv = np.meshgrid(x,y) 102 | xv[::2, :] += pixelx/2 103 | positions = np.array([xv.ravel(), yv.ravel()]) 104 | positions_xv = positions[0] 105 | positions_yv = positions[1] 106 | else: 107 | print("Unsuported grid argument!") 108 | 109 | ################################# 110 | ###elements options: 111 | pflag = 3 112 | if type(gdspyelements) is not str: 113 | print("Custom metasurface") 114 | 115 | #print(np.asarray(gdspyelements).size) 116 | 117 | if np.asarray(gdspyelements).size>1: 118 | assert len(gdspyelements)==len(phase_array), "The length of unique phase values and gdspyelements argument array is different." 119 | pflag = 2 120 | elif np.asarray(gdspyelements).size==1: 121 | print("Single gdspyelement element") 122 | pflag =0 123 | 124 | elif type(gdspyelements) is str: ###This corresponds to the default 125 | if gdspyelements=='pillar': 126 | print("Pillar metasurface") 127 | diameter = 1 #standard 1 um 128 | if scaling_flag: 129 | print("By default all pillars have "+str(diameter)+" um diameter without scaling. Not sure this is was what is wanted.") 130 | 131 | gdspyelements = gdspy.Round((0, 0), diameter/2, tolerance = tolerance, number_of_points = nr_points, max_points=100) 132 | pflag = 0 133 | 134 | else: 135 | pflag = 1 136 | else: 137 | if gdspyelements is None: 138 | pflag = 4 139 | else: 140 | pflag = 1 141 | 142 | if pflag==1: 143 | print("Unsuported gdspyelements argument!") 144 | ###################################################################################################### 145 | #####-------------------------------- 146 | print("Building the metasurface...") 147 | print("Total of "+str(len(phase_array))+" layers.") 148 | 149 | with Timer(): 150 | gdspy.current_library = gdspy.GdsLibrary() 151 | writer = gdspy.GdsWriter(outfilen,unit=1.0e-6,precision=1.0e-9) 152 | cell = lib.new_cell(topcellname) 153 | 154 | for ids, phase in enumerate(phase_array): 155 | angle = rotation_array[ids] 156 | scaling_factor = scaling_array[ids] 157 | 158 | #tempcellname = "p"+str(np.round(phase,3))+"_s"+str(np.round(scaling_factor[ids],3))+"_r"+str(np.round(angle,3)) 159 | 160 | print("Building meta-elements in layer "+str(ids)+":") 161 | 162 | if grid == 'square': 163 | selection_ids = np.where(aperture_vals == phase) 164 | harray = selection_ids[1]*pixelx 165 | warray = selection_ids[0]*pixely 166 | 167 | if grid == 'hex': 168 | ###select the positions of each phase value in the aperture 169 | selection_ids = np.where(aperture_vals.ravel()==phase) 170 | harray = positions_xv[selection_ids] 171 | warray = positions_yv[selection_ids] 172 | 173 | with Timer(): 174 | if (scaling_array[ids] >0) and (phase<=largest_phase):# & (scaling_array[ids] < p): 175 | for hn, (hi, wi) in enumerate(zip(harray,warray)): 176 | if verbose == True: 177 | progress_bar(hn/len(harray)) 178 | 179 | #avoid features with scaling smaller than mindim, setting them to smallerdim(=0) 180 | if scaling_array[ids] < mindim: 181 | scaling_array[ids] = smallerdim 182 | 183 | newpolygon, newpolygon2 = [], [] 184 | cell.remove_polygons(lambda pts, layer, datatype: layer == 0) 185 | 186 | if pflag==2: 187 | newpolygon = gdspyelements[ids] 188 | elif pflag==0: 189 | newpolygon = gdspyelements 190 | 191 | newpolygon2 = gdspy.copy(newpolygon) 192 | newpolygon2 = newpolygon2.scale(scaling_factor) 193 | newpolygon2 = newpolygon2.rotate(angle) 194 | newpolygon2 = newpolygon2.translate(hi, wi) 195 | cell.add(newpolygon2) 196 | 197 | tot_meta = tot_meta + 1 198 | writer.write_cell(cell) 199 | 200 | progress_bar(1) 201 | if verbose == True: 202 | print("So far "+str(tot_meta)+" elements and counting.") 203 | 204 | writer.close() 205 | 206 | print("\n Saved the metasurface mask with "+str(tot_meta)+" meta-elements in the file "+str(outfilen)) 207 | 208 | 209 | 210 | def metasurface_from_phase_instances (xsiz, ysiz, pixelx, pixely, p, aperture_vals, topcellname, outfilen, gdspyelements='pillar', \ 211 | infile=None, verbose=False, rotation=None, scaling=None, grid='square',\ 212 | mindim = 0.05, smallerdim =0, tempfile="temp.gds", largest_phase=None): 213 | """ 214 | Transform a 2D array (aperture_vals) representing the phase into a 2D metasurface using instances (from pya) package and saves it to gds 215 | 216 | Args: 217 | :xsiz: x size of aperture in x in um 218 | :ysiz: y size of aperture size in y in um 219 | :pixelx: pixel size in x in um 220 | :pixely: pixel size in y in um 221 | :p: periodicity in um 222 | :aperture_vals: 2D array with the phase 223 | :topcellname: string with name of top cell, e.g. 'TOP' 224 | :oufilen: string filename of output gds 225 | :gdspyelements: gdspy element to be used as individual meta-element (also accepts array of such elements for iteration, with same dimension as unique values in aperture_vals). If == 'pillar' (default) -> gdspy circle with 1 um diameter. If 'infile' is provided, ignores this design. 226 | :infile: string with filename to be used as meta-element 227 | :verbose: if True, prints during execution 228 | :rotation: array with the rotation angles of unique meta-elements (1:1 correspondence with unique aperture_vals values!), Rotation angle is anti-clockwise in radians. If None (default), sets the rotation angle = 0 for all elements. 229 | :scaling: array with the scaling factor of unique meta-element (1:1 correspondence with unique aperture_vals values!), Scaling factor is with respect to the dimension of the individual meta-element. If None (default), sets scaling factor to 1.0 for all elements. 230 | :grid: Type of grid, options are 'square' or 'hex'. Default is 'square'. Please make sure the aperture_vals have been evaluated in an hexagonal grid, to make sure the values match. 231 | :mindim: clipping scaling factor (cannot scale below a certain value, to avoid very small elements) 232 | :smallerdim: lowest scaling factor 233 | :tempfile: string with name of a temporary file that will be used to have the individual elements and make the instances 234 | :largest_phase: largest phase in the phase mask. If None, takes the maximum of aperture_vals 235 | 236 | Returns: 237 | None 238 | """ 239 | from gdspy import Polygon, PolygonSet 240 | from pyMOE.utils import Timer, progress_bar 241 | 242 | #total number of elements count 243 | tot_meta = 0 244 | 245 | #some global variables 246 | tolerance, nr_points, mindim, smallerdim = 0.001, 15, 0.05, 0 247 | 248 | #Start the metasurface library 249 | lib = gdspy.GdsLibrary() 250 | 251 | if largest_phase is None: 252 | largest_phase = np.max(aperture_vals) 253 | 254 | #Extract unique values of phase from the aperturea 2D array 255 | phase_array = np.unique(aperture_vals[aperture_vals <=largest_phase]) 256 | 257 | ############################################################## 258 | ###Various options for the metasurface currently given as args 259 | if rotation is not None: 260 | flag=1 261 | if isinstance(rotation, (int, float)): 262 | rotation_array, flag = np.ones(len(phase_array)) * rotation, 0 263 | else: 264 | assert len(rotation)==len(phase_array), "The length of unique phase values and rotation array is different." 265 | rotation_array, flag = rotation , 0 266 | if flag: 267 | print("Unsuported rotation argument!") 268 | else: 269 | rotation_array = np.zeros(len(phase_array)) #default rotate by 0 degs 270 | 271 | ################################# 272 | if scaling is not None: 273 | scaling_flag=0 274 | flag=1 275 | if isinstance(scaling, (int, float)): 276 | scaling_array, flag = np.ones(len(phase_array)) * scaling, 0 277 | else: 278 | assert len(scaling)==len(phase_array), "The length of unique phase values and rscaling array is different." 279 | scaling_array, flag = scaling, 0 280 | if flag: 281 | print("Unsuported scaling argument!") 282 | else: 283 | scaling_array = np.ones(len(phase_array)) #default scale by 1 284 | scaling_flag = 1 285 | 286 | ################################# 287 | if grid == 'square': 288 | xv, yv = np.meshgrid(np.arange(0, xsiz, pixelx, dtype=float), np.arange(0, ysiz, pixely, dtype=float)) 289 | positions = np.array([xv.ravel(), yv.ravel()]) 290 | positions_xv = positions[0] 291 | positions_yv = positions[1] 292 | 293 | elif grid == 'hex': 294 | x= np.arange(0, xsiz, pixelx, dtype=float) # arange is preferred over linspace because it keeps the pixel size! 295 | y= np.arange(0, ysiz, pixely, dtype=float) # arange is preferred over linspace because it keeps the pixel size! 296 | xv, yv = np.meshgrid(x,y) 297 | xv[::2, :] += pixelx/2 298 | positions = np.array([xv.ravel(), yv.ravel()]) 299 | positions_xv = positions[0] 300 | positions_yv = positions[1] 301 | else: 302 | print("Unsuported grid argument!") 303 | 304 | ################################# 305 | ###elements options: 306 | pflag = 3 307 | if infile is None: 308 | if type(gdspyelements) is not str: 309 | print("Custom metasurface") 310 | 311 | #print(np.asarray(gdspyelements).size) 312 | 313 | if np.asarray(gdspyelements).size>1: 314 | assert len(gdspyelements)==len(phase_array), "The length of unique phase values and gdspyelements argument array is different." 315 | pflag = 2 316 | elif np.asarray(gdspyelements).size==1: 317 | print("Single gdspyelement element") 318 | pflag =0 319 | 320 | elif type(gdspyelements) is str: ###This corresponds to the default 321 | if gdspyelements=='pillar': 322 | print("Pillar metasurface") 323 | diameter = 1 #standard 1 um 324 | if scaling_flag: 325 | print("By default all pillars have "+str(diameter)+" um diameter without scaling. Not sure this is was what is wanted.") 326 | 327 | gdspyelements = gdspy.Round((0, 0), diameter/2, tolerance = tolerance, number_of_points = nr_points, max_points=100) 328 | pflag = 0 329 | 330 | else: 331 | pflag = 1 332 | else: 333 | if gdspyelements is None: 334 | pflag = 4 335 | else: 336 | pflag = 1 337 | 338 | if pflag==1: 339 | print("Unsuported gdspyelements argument!") 340 | 341 | ######################################################################################################## 342 | #####-------------------------------- 343 | print("Building the metasurface...") 344 | print("Total of "+str(len(phase_array))+" layers.") 345 | 346 | with Timer(): 347 | layout = pya.Layout() 348 | 349 | #create cell at top 350 | top = layout.create_cell(topcellname) 351 | 352 | for ids, phase in enumerate(phase_array): 353 | first = 1 354 | cell_index2 = None 355 | 356 | angle = np.degrees(rotation_array[ids]) 357 | scaling_factor = scaling_array[ids] 358 | fvalue = phase 359 | 360 | tempcellname = "layer_"+str(ids)+"p"+str(np.round(fvalue,3))+"_s"+str(np.round(scaling_factor,3))+"_r"+str(np.round(angle,3)) 361 | 362 | print("Building meta-elements in layer "+str(ids)+":") 363 | 364 | if grid == 'square': 365 | selection_ids = np.where(aperture_vals == phase) 366 | harray = selection_ids[1]*pixelx 367 | warray = selection_ids[0]*pixely 368 | 369 | if grid == 'hex': 370 | ###select the positions of each phase value in the aperture 371 | selection_ids = np.where(aperture_vals.ravel()==phase) 372 | harray = positions_xv[selection_ids] 373 | warray = positions_yv[selection_ids] 374 | 375 | with Timer(): 376 | if (scaling_array[ids] >0) and (phase<=largest_phase):# & (scaling_array[ids] < p): 377 | for hn, (hi, wi) in enumerate(zip(harray,warray)): 378 | if verbose == True: 379 | progress_bar(hn/len(harray)) 380 | 381 | #avoid features with scaling smaller than mindim, setting them to smallerdim(=0) 382 | if scaling_array[ids] < mindim: 383 | scaling_array[ids] = smallerdim 384 | 385 | if infile is None: 386 | lib = gdspy.GdsLibrary() 387 | gdspy.current_library = gdspy.GdsLibrary() 388 | 389 | if first==1: 390 | writer = gdspy.GdsWriter(tempfile, unit=1.0e-6, precision=1.0e-9) #the precision could be passed as argument if needed 391 | 392 | cell = lib.new_cell(tempcellname) 393 | newpolygon, newpolygon2 = [], [] 394 | cell.remove_polygons(lambda pts, layer, datatype: layer == 0) 395 | 396 | if pflag==2: 397 | newpolygon = gdspyelements[ids] 398 | else: 399 | newpolygon = gdspyelements 400 | 401 | newpolygon2 = gdspy.copy(newpolygon) 402 | cell.add(newpolygon2) 403 | 404 | writer.write_cell(cell) 405 | writer.close() 406 | first = 0 407 | 408 | layout.read(tempfile) 409 | cell_index1 = layout.cell(tempcellname).cell_index() 410 | 411 | 412 | tot_meta = tot_meta + 1 413 | 414 | new_instance1 = pya.DCellInstArray(cell_index1 , pya.DCplxTrans(scaling_factor, angle, False, pya.DVector(float(hi),float(wi)))) 415 | top.insert( new_instance1 ) 416 | 417 | else: 418 | if first==1: 419 | layout.read(infile) 420 | 421 | rotate_layout(infile, tempcellname, angle, tempfile, transx =0, transy=0) 422 | first = 0 423 | 424 | layout.read(tempfile) 425 | cell_index2 = layout.cell(tempcellname).cell_index() 426 | 427 | if cell_index2 is not None: 428 | new_instance1 = pya.DCellInstArray(cell_index2 , pya.DCplxTrans(scaling_factor, angle, False, pya.DVector(float(hi),float(wi)))) 429 | top.insert( new_instance1 ) 430 | 431 | tot_meta = tot_meta + 1 432 | 433 | layout.write(outfilen) 434 | progress_bar(1) 435 | print("So far "+str(tot_meta)+" elements and counting.") 436 | 437 | 438 | print("\n Saved the metasurface mask with "+str(tot_meta)+" meta-elements in the file "+str(outfilen)) 439 | -------------------------------------------------------------------------------- /pyMOE/plotting.py: -------------------------------------------------------------------------------- 1 | """ 2 | plotting.py 3 | Module for plotting apertures ans save them as image files 4 | 5 | 6 | """ 7 | 8 | import matplotlib.pyplot as plt 9 | import cv2 10 | import numpy as np 11 | 12 | from pyMOE import Aperture 13 | from pyMOE import Field 14 | from pyMOE import Screen 15 | from pyMOE import ApertureField 16 | 17 | 18 | def save_mask_plot(maskcir, xsize, ysize, filename): 19 | plt.ioff() 20 | 21 | fig = plt.figure() 22 | ax = fig.add_subplot(1,1,1) 23 | 24 | fig.set_dpi(1) 25 | 26 | plt.axis('equal') 27 | plt.axis('off') 28 | 29 | maskcir = np.flip(maskcir,0) 30 | plt.imshow(maskcir, vmin=0, vmax=1,extent =[0,xsize,0,ysize], cmap=plt.get_cmap("Greys")) 31 | 32 | plt.tight_layout(pad=0, w_pad=0, h_pad=0) 33 | fig.tight_layout(w_pad=0, h_pad=0, pad =0) 34 | 35 | plt.gca().set_axis_off() 36 | plt.subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, hspace = 0, wspace = 0) 37 | plt.margins(0,0) 38 | 39 | fig.set_size_inches((xsize), (ysize)) 40 | fig.savefig("temp.png", bbox_inches=0,pad_inches = 0, dpi=1) 41 | plt.close() 42 | 43 | #remove any white padding 44 | im = cv2.imread("temp.png") 45 | cv2.imwrite(filename, im) 46 | 47 | plt.ion() 48 | 49 | 50 | def plot_aperture(aperture, scale=None, colorbar=True, only_plot=False, filename=None, **kwargs): 51 | """ 52 | Plots the given aperture 53 | 54 | Args: 55 | :aperture: Aperture object of the mask 56 | :colorbar: True/False flag to plot colorbars 57 | :only_plot: if True, only shows image without labels and axes 58 | :filename: if provided, saves figure to filename 59 | ###available output file extensions are same as opencv https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html 60 | """ 61 | assert type(aperture) is Aperture, "aperture given is not an Aperture object" 62 | 63 | 64 | if scale is not None: 65 | scale_factor = scale 66 | else: 67 | scale_factor = 1 68 | scale = "" 69 | 70 | fig = plt.figure() 71 | ax = plt.gca() 72 | 73 | plt.sca(ax) 74 | ax.set_aspect(1) 75 | pcm = plt.pcolormesh(aperture.x/scale_factor, aperture.y/scale_factor, aperture.aperture,) 76 | 77 | if not only_plot: 78 | plt.xlabel("x [%sm]"%scale) 79 | plt.ylabel("y [%sm]"%scale) 80 | if colorbar: 81 | fig.colorbar(pcm, ax=ax, shrink=0.6) 82 | 83 | else: 84 | plt.axis('off') 85 | ax.get_xaxis().set_visible(False) 86 | ax.get_yaxis().set_visible(False) 87 | 88 | plt.subplots_adjust(wspace=0.3) 89 | if filename is not None: 90 | plt.savefig("temp.png", bbox_inches='tight', pad_inches = 0) 91 | plt.close(fig1) 92 | img = cv2.imread("temp.png") 93 | cv2.imwrite(filename, img) 94 | plt.show() 95 | 96 | 97 | 98 | 99 | 100 | def plot_field(field, which='both', scale=None, colorbar=True, only_plot=False, filename=None, **kwargs): 101 | """ 102 | Plots the given field 103 | 104 | Args: 105 | :field: Field object of the mask 106 | :which: default "both", "amplitude" or "phase" 107 | :colorbar: True/False flag to plot colorbars 108 | :only_plot: if True, only shows image without labels and axes 109 | :filename: if provided, saves figure to filename 110 | ###available output file extensions are same as opencv https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html 111 | """ 112 | assert type(field) is Field, "field given is not an Field object" 113 | assert which in ["both", "amplitude", "phase"] 114 | 115 | if scale is not None: 116 | scale_factor = scale 117 | else: 118 | scale_factor = 1 119 | scale = "" 120 | 121 | if which == "both": 122 | fig, axes = plt.subplots(1,2, sharey=True, sharex=True, ) 123 | ax1 = axes[0] 124 | ax2 = axes[1] 125 | elif which == "amplitude": 126 | fig = plt.figure() 127 | ax1 = plt.gca() 128 | elif which == "phase": 129 | fig = plt.figure() 130 | ax2 = plt.gca() 131 | 132 | if which in ["both", "amplitude"]: 133 | plt.sca(ax1) 134 | ax1.set_aspect(1) 135 | pcm = plt.pcolormesh(field.x/scale_factor, field.y/scale_factor, field.amplitude,) 136 | 137 | if not only_plot: 138 | plt.xlabel("x [%sm]"%scale) 139 | plt.ylabel("y [%sm]"%scale) 140 | if colorbar: 141 | fig.colorbar(pcm, ax=ax1, label='Amplitude [a.u.]', shrink=0.6) 142 | else: 143 | plt.title("Amplitude") 144 | else: 145 | plt.axis('off') 146 | ax1.get_xaxis().set_visible(False) 147 | ax1.get_yaxis().set_visible(False) 148 | 149 | if which in ["both", "phase"]: 150 | plt.sca(ax2) 151 | ax2.set_aspect(1) 152 | pcm = plt.pcolormesh(field.x/scale_factor, field.y/scale_factor, field.phase) 153 | 154 | if not only_plot: 155 | plt.xlabel("x [%sm]"%scale) 156 | plt.ylabel("y [%sm]"%scale) 157 | if colorbar: 158 | fig.colorbar(pcm, ax=ax2, label='Phase [rad]', shrink=0.6) 159 | else: 160 | plt.title("Phase") 161 | else: 162 | plt.axis('off') 163 | ax2.get_xaxis().set_visible(False) 164 | ax2.get_yaxis().set_visible(False) 165 | 166 | plt.subplots_adjust(wspace=0.3) 167 | if filename is not None: 168 | plt.savefig("temp.png", bbox_inches='tight', pad_inches = 0) 169 | plt.close(fig1) 170 | # img = cv2.imread("temp.png") 171 | # cv2.imwrite(filename, img) 172 | 173 | 174 | 175 | 176 | 177 | def plot_screen_XY(screen, which='both', scale=None, colorbar=True, only_plot=False, filename=None, **kwargs): 178 | """ 179 | Plots the given screen 180 | 181 | Args: 182 | :screen: Screen object of the mask 183 | :which: default "both", "amplitude" or "phase" 184 | :colorbar: True/False flag to plot colorbars 185 | :only_plot: if True, only shows image without labels and axes 186 | :filename: if provided, saves figure to filename 187 | ###available output file extensions are same as opencv https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html 188 | """ 189 | assert type(screen) is Screen, "screen given is not an Screen object" 190 | assert which in ["both", "amplitude", "phase"] 191 | 192 | if scale is not None: 193 | scale_factor = scale 194 | else: 195 | scale_factor = 1 196 | scale = "" 197 | 198 | if which == "both": 199 | fig, axes = plt.subplots(1,2, sharey=True, sharex=True, ) 200 | ax1 = axes[0] 201 | ax2 = axes[1] 202 | elif which == "amplitude": 203 | fig = plt.figure() 204 | ax1 = plt.gca() 205 | elif which == "phase": 206 | fig = plt.figure() 207 | ax2 = plt.gca() 208 | 209 | if which in ["both", "amplitude"]: 210 | plt.sca(ax1) 211 | ax1.set_aspect(1) 212 | pcm = plt.pcolormesh(screen.x/scale_factor, screen.y/scale_factor, screen.amplitude.reshape((len(screen.x),len(screen.y)))) 213 | 214 | if not only_plot: 215 | plt.xlabel("x [%sm]"%scale) 216 | plt.ylabel("y [%sm]"%scale) 217 | if colorbar: 218 | fig.colorbar(pcm, ax=ax1, label='Amplitude [a.u.]', shrink=0.6) 219 | else: 220 | plt.title("Amplitude") 221 | else: 222 | plt.axis('off') 223 | ax1.get_xaxis().set_visible(False) 224 | ax1.get_yaxis().set_visible(False) 225 | 226 | if which in ["both", "phase"]: 227 | plt.sca(ax2) 228 | ax2.set_aspect(1) 229 | pcm = plt.pcolormesh(screen.x/scale_factor, screen.y/scale_factor, screen.phase.reshape((len(screen.x),len(screen.y)))) 230 | 231 | if not only_plot: 232 | plt.xlabel("x [%sm]"%scale) 233 | plt.ylabel("y [%sm]"%scale) 234 | if colorbar: 235 | fig.colorbar(pcm, ax=ax2, label='Phase [rad]', shrink=0.6) 236 | else: 237 | plt.title("Phase") 238 | else: 239 | plt.axis('off') 240 | ax2.get_xaxis().set_visible(False) 241 | ax2.get_yaxis().set_visible(False) 242 | 243 | plt.subplots_adjust(wspace=0.3) 244 | if filename is not None: 245 | plt.savefig("temp.png", bbox_inches='tight', pad_inches = 0) 246 | plt.close(fig1) 247 | # img = cv2.imread("temp.png") 248 | # cv2.imwrite(filename, img) 249 | 250 | 251 | def plot_screen_YZ(screen, which='both', scale=None, colorbar=True, only_plot=False, filename=None, **kwargs): 252 | """ 253 | Plots the given screen 254 | 255 | Args: 256 | :screen: Screen object of the mask 257 | :which: default "both", "amplitude" or "phase" 258 | :colorbar: True/False flag to plot colorbars 259 | :only_plot: if True, only shows image without labels and axes 260 | :filename: if provided, saves figure to filename 261 | ###available output file extensions are same as opencv https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html 262 | """ 263 | assert type(screen) is Screen, "screen given is not an Screen object" 264 | assert which in ["both", "amplitude", "phase"] 265 | 266 | if scale is not None: 267 | scale_factor = scale 268 | else: 269 | scale_factor = 1 270 | scale = "" 271 | 272 | if which == "both": 273 | fig, axes = plt.subplots(1,2, sharey=True, sharex=True, ) 274 | ax1 = axes[0] 275 | ax2 = axes[1] 276 | elif which == "amplitude": 277 | fig = plt.figure() 278 | ax1 = plt.gca() 279 | elif which == "phase": 280 | fig = plt.figure() 281 | ax2 = plt.gca() 282 | 283 | if which in ["both", "amplitude"]: 284 | plt.sca(ax1) 285 | # ax1.set_aspect(1) 286 | pcm = plt.pcolormesh(screen.z/scale_factor, screen.y/scale_factor, screen.amplitude[:,0]) 287 | 288 | if not only_plot: 289 | plt.xlabel("z [%sm]"%scale) 290 | plt.ylabel("y [%sm]"%scale) 291 | if colorbar: 292 | fig.colorbar(pcm, ax=ax1, label='Amplitude [a.u.]', shrink=0.6) 293 | else: 294 | plt.title("Amplitude") 295 | else: 296 | plt.axis('off') 297 | ax1.get_xaxis().set_visible(False) 298 | ax1.get_yaxis().set_visible(False) 299 | 300 | if which in ["both", "phase"]: 301 | plt.sca(ax2) 302 | # ax2.set_aspect(1) 303 | pcm = plt.pcolormesh(screen.z/scale_factor, screen.y/scale_factor, screen.phase[:,0]) 304 | 305 | if not only_plot: 306 | plt.xlabel("z [%sm]"%scale) 307 | plt.ylabel("y [%sm]"%scale) 308 | if colorbar: 309 | fig.colorbar(pcm, ax=ax2, label='Phase [rad]', shrink=0.6) 310 | else: 311 | plt.title("Phase") 312 | else: 313 | plt.axis('off') 314 | ax2.get_xaxis().set_visible(False) 315 | ax2.get_yaxis().set_visible(False) 316 | 317 | plt.subplots_adjust(wspace=0.3) 318 | if filename is not None: 319 | plt.savefig("temp.png", bbox_inches='tight', pad_inches = 0) 320 | plt.close(fig1) 321 | # img = cv2.imread("temp.png") 322 | # cv2.imwrite(filename, img) 323 | 324 | 325 | 326 | def plot_screen_ZZ(screen, which='both', scale=None, only_plot=False, filename=None, **kwargs): 327 | """ 328 | Plots the given screen 329 | 330 | Args: 331 | :screen: Screen object of the mask 332 | :which: default "both", "amplitude" or "phase" 333 | :colorbar: True/False flag to plot colorbars 334 | :only_plot: if True, only shows image without labels and axes 335 | :filename: if provided, saves figure to filename 336 | ###available output file extensions are same as opencv https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html 337 | """ 338 | assert type(screen) is Screen, "screen given is not an Screen object" 339 | assert which in ["both", "amplitude", "phase"] 340 | 341 | if scale is not None: 342 | scale_factor = scale 343 | else: 344 | scale_factor = 1 345 | scale = "" 346 | 347 | if which == "both": 348 | fig, axes = plt.subplots(1,2, sharey=False, sharex=True, ) 349 | ax1 = axes[0] 350 | ax2 = axes[1] 351 | elif which == "amplitude": 352 | fig = plt.figure() 353 | ax1 = plt.gca() 354 | elif which == "phase": 355 | fig = plt.figure() 356 | ax2 = plt.gca() 357 | 358 | if which in ["both", "amplitude"]: 359 | plt.sca(ax1) 360 | # ax1.set_aspect(1) 361 | pcm = plt.plot(screen.z/scale_factor, screen.amplitude[0,0,:],) 362 | 363 | if not only_plot: 364 | plt.xlabel("z [%sm]"%scale) 365 | plt.ylabel("Amplitude [a.u.]") 366 | 367 | else: 368 | plt.axis('off') 369 | ax1.get_xaxis().set_visible(False) 370 | ax1.get_yaxis().set_visible(False) 371 | 372 | if which in ["both", "phase"]: 373 | plt.sca(ax2) 374 | # ax2.set_aspect(1) 375 | pcm = plt.plot(screen.z/scale_factor, screen.phase[0,0,:]) 376 | 377 | if not only_plot: 378 | plt.xlabel("z [%sm]"%scale) 379 | plt.ylabel("Phase [rad]") 380 | # plt.title("Phase") 381 | else: 382 | plt.axis('off') 383 | ax2.get_xaxis().set_visible(False) 384 | ax2.get_yaxis().set_visible(False) 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | ################## consider removing below 394 | def plot_field_legacy(aperture, which='both', scale=None, colorbar=True, only_plot=False, filename=None, **kwargs): 395 | """ 396 | Plots the given aperture 397 | 398 | Args: 399 | :aperture: Aperture object of the mask 400 | :which: default "both", "amplitude" or "phase" 401 | :colorbar: True/False flag to plot colorbars 402 | :only_plot: if True, only shows image without labels and axes 403 | :filename: if provided, saves figure to filename 404 | ###available output file extensions are same as opencv https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html 405 | """ 406 | assert type(aperture) is ApertureField, "aperture given is not an Aperture object" 407 | assert which in ["both", "amplitude", "phase"] 408 | 409 | if scale is not None: 410 | scale_factor = scale 411 | else: 412 | scale_factor = 1 413 | scale = "" 414 | 415 | if which == "both": 416 | fig, axes = plt.subplots(1,2, sharey=True, sharex=True, ) 417 | ax1 = axes[0] 418 | ax2 = axes[1] 419 | elif which == "amplitude": 420 | fig = plt.figure() 421 | ax1 = plt.gca() 422 | elif which == "phase": 423 | fig = plt.figure() 424 | ax2 = plt.gca() 425 | 426 | if which in ["both", "amplitude"]: 427 | plt.sca(ax1) 428 | ax1.set_aspect(1) 429 | pcm = plt.pcolormesh(aperture.x/scale_factor, aperture.y/scale_factor, aperture.amplitude,) 430 | 431 | if not only_plot: 432 | plt.xlabel("x [%sm]"%scale) 433 | plt.ylabel("y [%sm]"%scale) 434 | if colorbar: 435 | fig.colorbar(pcm, ax=ax1, label='Amplitude [a.u.]', shrink=0.6) 436 | else: 437 | plt.title("Amplitude") 438 | else: 439 | plt.axis('off') 440 | ax1.get_xaxis().set_visible(False) 441 | ax1.get_yaxis().set_visible(False) 442 | 443 | if which in ["both", "phase"]: 444 | plt.sca(ax2) 445 | ax2.set_aspect(1) 446 | pcm = plt.pcolormesh(aperture.x/scale_factor, aperture.y/scale_factor, aperture.phase) 447 | 448 | if not only_plot: 449 | plt.xlabel("x [%sm]"%scale) 450 | plt.ylabel("y [%sm]"%scale) 451 | if colorbar: 452 | fig.colorbar(pcm, ax=ax2, label='Phase [rad]', shrink=0.6) 453 | else: 454 | plt.title("Phase") 455 | else: 456 | plt.axis('off') 457 | ax2.get_xaxis().set_visible(False) 458 | ax2.get_yaxis().set_visible(False) 459 | 460 | plt.subplots_adjust(wspace=0.3) 461 | if filename is not None: 462 | plt.savefig("temp.png", bbox_inches='tight', pad_inches = 0) 463 | plt.close(fig1) 464 | img = cv2.imread("temp.png") 465 | cv2.imwrite(filename, img) 466 | 467 | -------------------------------------------------------------------------------- /pyMOE/propagate.py: -------------------------------------------------------------------------------- 1 | """ 2 | propagate.py 3 | 4 | This module had origin in the "propopt" repository module with same name 5 | -> For more information, examples of use and validation tests, 6 | please see link for repository : https://github.com/cunhaJ/propopt 7 | 8 | Here, "propopt" code has been modified and extended for use with apertures and masks of "pyMOE" 9 | """ 10 | 11 | import numpy as np 12 | import scipy.fftpack as sfft 13 | 14 | import decimal 15 | 16 | from pyMOE.utils import simpson2d 17 | 18 | from scipy import integrate 19 | 20 | import dask 21 | import dask.array as da 22 | from dask import delayed 23 | import dask.bag as db 24 | from dask.diagnostics import ProgressBar 25 | 26 | from pyMOE.utils import progress_bar, Timer 27 | 28 | 29 | 30 | def circ_zz24(aperture_rad, zdist, wavelength): 31 | """ 32 | This function is the axial intensity for a circular aperture following 1992 JOSA 9(2) paper "Diffraction by a circular aperture: a generalization of Fresnel diffraction theory" , exp (24) 33 | 34 | Args: 35 | :aperture_rad: Radius of the circular aperture in m 36 | :zdist: Distance of propagation 37 | :wavelength: Wavelength of incoming illumination in m 38 | 39 | Returns: 40 | Propagated intensity to zdist 41 | 42 | """ 43 | import numpy as np 44 | 45 | k = 2*np.pi /wavelength 46 | 47 | izz1 = 1/(1+aperture_rad**2/zdist**2) 48 | izz2 = 2/np.sqrt(1+aperture_rad**2/zdist**2) 49 | izz3 = (k * aperture_rad**2/(zdist)) /(np.sqrt(1+aperture_rad**2/zdist**2)+1) 50 | itot = 0.25*(1+izz1-izz2* np.cos(izz3)) 51 | 52 | return itot 53 | 54 | 55 | 56 | def fresnel(z, mask, npixmask, pixsizemask, npixscreen, dxscreen, dyscreen, wavelength): 57 | """ 58 | Calculate Fresnel approximation, following Goodman exp 4-17 59 | 60 | Args: 61 | :z: distance to the observation plane in m 62 | :mask: 2D map (x-y plane) npixel vs npixel mask 63 | :npixmask: number of pixels of the mask 64 | :pixsizemask: size of the pixel at the mask in m 65 | :npixscreen: size of the pixel at the screen 66 | :dxscreen: x-size of the screen in m 67 | :dyscreen: y-size of the screen in m 68 | :wavelength: wavelength in m 69 | 70 | Returns: 71 | 2D map (x-y plane) with abs of Electric field at distance z 72 | 73 | """ 74 | k = 2* np.pi/wavelength 75 | 76 | #number of pixels 77 | nps = npixscreen 78 | npm = npixmask 79 | 80 | ##calculate the resolution - 81 | res = wavelength *z/ (pixsizemask *npm) 82 | 83 | dmask = res * npm 84 | 85 | if z < Fresnel_criterion(wavelength, dmask/2): 86 | print("The propagation distance is too short for Fresnel propagation! Propagation results might be incorrect.") 87 | 88 | xm1 = np.linspace(-dmask/2, dmask/2, npm) 89 | ym1 = np.linspace(-dmask/2, dmask/2, npm) 90 | (xm, ym) = np.meshgrid(xm1, ym1) 91 | 92 | xs1 = np.linspace(-dxscreen, dxscreen, nps) 93 | ys1 = np.linspace(-dyscreen, dyscreen, nps) 94 | (xs, ys) = np.meshgrid(xs1, ys1) 95 | 96 | Ef = fresnel_kernel(k, xm, ym, z, mask) 97 | 98 | return Ef 99 | 100 | def fresnel_kernel(k, xm, ym, z, mask): 101 | #Goodman, exp 4-17 102 | v1 = np.exp(1.0j*k* (xm*xm + ym*ym)/ (2*z)) 103 | intarg = v1 * mask 104 | Ef = sfft.fftshift(sfft.fft2(sfft.ifftshift(intarg))) 105 | 106 | return Ef 107 | 108 | def fraunhofer(z, mask, npixmask, pixsizemask, npixscreen, dxscreen, dyscreen, wavelength): 109 | """ 110 | Calculate Fraunhofer approximation, following Goodman exp 4-25 111 | 112 | Args: 113 | :z: distance to the observation plane in m 114 | :mask: 2D map (x-y plane) npixel vs npixel mask 115 | :npixmask: number of pixels of the mask 116 | :pixsizemask: size of the pixel at the mask in m 117 | :npixscreen: size of the pixel at the screen 118 | :dxscreen: x-size of the screen in m 119 | :dyscreen: y-size of the screen in m 120 | :wavelength: wavelength in m 121 | 122 | Returns: 123 | 2D map (x-y plane) with abs of Electric field 124 | 125 | """ 126 | 127 | k = 2* np.pi/wavelength 128 | #number of pixels 129 | nps = npixscreen 130 | npm = npixmask 131 | 132 | dmask = npixmask * npm 133 | 134 | if z < Fraunhofer_criterion(wavelength, dmask/2): 135 | print("The propagation distance is too short for Fraunhofer propagation! Propagation results might be incorrect.") 136 | 137 | xm1 = np.linspace(-dmask/2, dmask/2, npm) 138 | ym1 = np.linspace(-dmask/2, dmask/2, npm) 139 | (xm, ym) = np.meshgrid(xm1, ym1) 140 | 141 | xs1 = np.linspace(-dxscreen, dxscreen, nps) 142 | ys1 = np.linspace(-dyscreen, dyscreen, nps) 143 | (xs, ys) = np.meshgrid(xs1, ys1) 144 | 145 | delta1=pixsizemask 146 | 147 | Ef = fraunhofer_kernel(k, xm, ym, mask, z, wavelength) 148 | 149 | return Ef 150 | 151 | 152 | def fraunhofer_kernel(k, xm, ym, mask, z, wavelength): 153 | #Goodman, exp 4-25 154 | v2 = np.exp(1.0j*k* (xs*xs + ys*ys)/ (2*z)) 155 | v3 = np.exp(1.0j*k*z)/ (1.0j*wavelength*z) 156 | Ef = v2 * v3* sfft.fftshift(sfft.fft2(resized)) 157 | return Ef 158 | 159 | 160 | 161 | 162 | def Fresnel_num(width, wavelength, zdist): 163 | """ 164 | Calculation of Fresnel number, Goodman pag 85 165 | 166 | Args: 167 | :width: size of the aperture in m 168 | :wavelength: wavelength of the aperture in m 169 | :zdist: distance to the screen in m 170 | 171 | Returns: 172 | Fresnel number 173 | """ 174 | NF = width**2 / (wavelength * zdist) 175 | return NF 176 | 177 | 178 | def Fraunhofer_criterion(wavelength, radius): 179 | """ 180 | Calculation of "Fraunhofer distance" , Goodman 4-27 181 | 182 | Args: 183 | :wavelength: wavelength of the illumination m 184 | :radius: (max) radius of the aperture in m 185 | 186 | Returns: 187 | Fraunhofer distance 188 | """ 189 | z = (2*np.pi/wavelength)/2*radius**2 190 | return z 191 | 192 | def Fresnel_criterion(wavelength, radius): 193 | """ 194 | Calculation of "Fresnel distance" , Goodman 195 | 196 | Args: 197 | :wavelength: wavelength of the illumination m 198 | :radius: (max) radius of the aperture in m 199 | 200 | Returns: 201 | Fresnel distance 202 | """ 203 | z = ((np.pi/(4*wavelength))*radius**4)**(1/3) 204 | return z 205 | 206 | 207 | 208 | 209 | @dask.delayed 210 | def kernel_RS(field, k, x,y,z, simp2d=False): 211 | """ 212 | Calculates the RS kernel integral from a field input aperture, assumed to be at z=0 213 | and returns the calculated E field 214 | 215 | Implements the Kernel in Mahajan 2011 part II eq 1-20 216 | 217 | Args: 218 | :field: input field 219 | :k: Calculated wavenumber k=2pi/(wl*n) 220 | :x,y,z: x, y, z coordinates of the screen point being evaluated 221 | :simp2d: Defaults False, if True uses the simpson2d function 222 | Returns: 223 | :E: Calculated field 224 | """ 225 | 226 | z_field = 0 # the field source is assumed at z=0 227 | r = np.sqrt( (field.XX-x)**2 + (field.YY-y)**2 + (z_field-z)**2) 228 | r2 = r*r 229 | 230 | prop1 = np.exp(r*1.0j*k)/r2 231 | prop2 = z * k/(2*np.pi) *( 1/(r*k) - 1.0j) 232 | propE = field.field * prop1 * prop2 233 | 234 | # integrate over the input field and return field 235 | if simp2d==True: 236 | Exyz = simpson2d(propE,field.x[0], field.x[-1], field.y[0], field.y[-1]) /(2*np.pi) 237 | else: 238 | Exyz = integrate.simpson(integrate.simpson(propE, field.x),field.y)/(2*np.pi) 239 | 240 | return Exyz 241 | 242 | 243 | def RS_integral(field, screen, wavelength, n=None, parallel_computing=True, simp2d=False): 244 | """ 245 | Calculates the Raleyigh Sommerfeld integral in the of the first kind (Mahajan 2011 part II eq 1-20), receiving an input field and an observation screen plane on which to 246 | calculate the integral. 247 | 248 | Args: 249 | 250 | :field: input Field 251 | :screen: Observation Screen 252 | :wavelength: wavelength to consider 253 | :n: refractive index of the propagation medium (default=1 for vacuum/air) 254 | :parallel_computing: Flag to trigger the concurrent computation of the kernels using Python Dask library 255 | :simp2d: Defaults False, if True uses the simpson2d function 256 | Returns: 257 | :screen: Returns the screen populated with the result 258 | """ 259 | 260 | if (field.pixel_x > wavelength/2) or (field.pixel_y > wavelength/2): 261 | print("Warning: Sampling field pixel is larger than wavelength/2!") 262 | k = 2* np.pi/(wavelength) 263 | 264 | xlen,ylen,zlen = screen.XX.shape 265 | 266 | if parallel_computing: 267 | delayed_tasks = [] 268 | # For each cell on the screen, the RS integral will be calculated based on the input field 269 | # this loop sets up the delayed tasks to be executed 270 | for x_i in range(xlen): 271 | for y_i in range(ylen): 272 | for z_i in range(zlen): 273 | 274 | x = screen.XX[x_i, y_i, z_i] 275 | y = screen.YY[x_i, y_i, z_i] 276 | z = screen.ZZ[x_i, y_i, z_i] 277 | 278 | if n is not None: 279 | k = 2* np.pi*n[x_i,y_i,z_i]/(wavelength) 280 | # the kernel is configured as a dask delayed task 281 | result = kernel_RS(field, k ,x,y,z, simp2d) 282 | 283 | delayed_tasks.append(result) 284 | # screen.screen[x_i, y_i, z_i] = a 285 | 286 | # the dask.compute triggers the computation of the delayed tasks and stores the result 287 | # into the results list 288 | # print(delayed_tasks) 289 | with ProgressBar(): 290 | results = list(dask.compute(*delayed_tasks)) 291 | # print(results) 292 | # again we go through the for loop to pop the results and insert it into the screen position 293 | for x_i in range(xlen): 294 | for y_i in range(ylen): 295 | for z_i in range(zlen): 296 | screen.screen[x_i, y_i, z_i] = results.pop(0) 297 | else: 298 | with Timer(): 299 | for x_i in range(xlen): 300 | for y_i in range(ylen): 301 | for z_i in range(zlen): 302 | 303 | x = screen.XX[x_i, y_i, z_i] 304 | y = screen.YY[x_i, y_i, z_i] 305 | z = screen.ZZ[x_i, y_i, z_i] 306 | 307 | if n is not None: 308 | k = 2* np.pi*n[x_i,y_i,z_i]/(wavelength) 309 | 310 | result = kernel_RS(field, k ,x,y,z, simp2d).compute() 311 | 312 | screen.screen[x_i, y_i, z_i] = result 313 | progress_bar((x_i*zlen*ylen+y_i*zlen+z_i)/(xlen*ylen*zlen)) 314 | progress_bar(1) 315 | 316 | return screen 317 | 318 | 319 | 320 | 321 | ################################################## 322 | ##RS integral functions introduced in v1.3 323 | 324 | 325 | def RS_intXY(zs, mask, npixmask, pixsizemask, npixscreen, dxscreen, dyscreen, wavelength, verbose =False ): 326 | """ 327 | Calculates the RS int of the first kind 328 | returns Escreen (complex electric field at obs screen), Iscreen (intensity at obs screen), iplot (the actual intensity) 329 | 330 | Args: 331 | :zs: distance to screen [m] 332 | :mask: Electric field at the mask, complex valued 2D function 333 | :npixmask: number of pixels on the side of the mask 334 | :pixsizemask: size of pixel of the mask [m] 335 | :npixscreen: number of pixels on the side of the screen (observation) 336 | :dxscreen: max_x of the screen [m], the screen range is [-dxscreen, dxscreen] 337 | :dyscreen: max_y of the screen [m], the screen range is [-dyscreen, dyscreen] 338 | :wavelength: wavelength of the light [m] 339 | :verbose: defaults to False, if True prints 340 | """ 341 | # set the precision to double that of float64 342 | decimal.setcontext(decimal.Context(prec=34)) 343 | 344 | #number of pixels 345 | nps = npixscreen 346 | npm = npixmask 347 | 348 | #if nps <= 2*npm: #Increase sampling values 349 | # nps = npm*4 +1 350 | 351 | #size of mask 352 | #dmask = pixsizemask * npm 353 | dmask = 2*dxscreen 354 | 355 | #prop const 356 | k = 2* np.pi/wavelength 357 | 358 | #define the zpos of the mask at 0 359 | zm =0 360 | 361 | #definition of the mask 362 | xm1 = np.linspace(-dmask/2, dmask/2, npm) 363 | ym1 = np.linspace(-dmask/2, dmask/2, npm) 364 | (xm, ym) = np.meshgrid(xm1,ym1) 365 | 366 | xs1 = np.linspace(-dxscreen, dxscreen, nps) 367 | ys1 = np.linspace(-dyscreen, dyscreen, nps) 368 | (xs, ys) = np.meshgrid(xs1,ys1) 369 | 370 | #definition of the electric field at the mask 371 | E0m = mask 372 | 373 | ###### calculate the Rayleigh Sommerfeld integral 374 | Escreen = RS_intXY_kernel(dmask, nps, npm, xs, ys,zs,xm,ym,zm,k, E0m,verbose) 375 | 376 | Iscreen = np.abs(Escreen)**2 377 | iplot = 10*Iscreen**0.2 378 | 379 | return Escreen, Iscreen, iplot 380 | 381 | 382 | def RS_intXY_kernel(dmask, nps, npm, xs, ys,zs,xm,ym,zm,k, E0m,verbose): 383 | ## definitions 384 | unit = np.ones((npm,npm), dtype=complex) 385 | r = np.zeros((npm,npm)) 386 | prop1 = np.zeros((npm,npm), dtype=complex) 387 | prop2 = np.zeros((npm,npm), dtype=complex) 388 | propE = np.zeros((npm,npm), dtype=complex) 389 | 390 | #electric field real and imaginary and total 391 | rEs =np.zeros ((nps,nps)) 392 | iEs =np.zeros ((nps,nps)) 393 | Escreen =np.zeros ((nps,nps), dtype =complex) 394 | 395 | with Timer(): 396 | #e.g. Mahajan 2011 part II eq 1-20 397 | for isc in np.arange(0,nps-1): 398 | if verbose == True: 399 | progress_bar(isc/nps) 400 | 401 | for jsc in np.arange(0,nps-1): 402 | r = np.sqrt((xs[isc,jsc]-xm)**2 + (ys[isc,jsc]-ym)**2 + (zs-zm)**2) 403 | r2 = r*r 404 | prop1= np.exp(r*1.0j*k)/r2 405 | prop2 = zs * k/(2*np.pi) *(unit / (r*k) - 1.0j * unit) 406 | propE = E0m * prop1 * prop2 407 | 408 | #rEs[isc,jsc] = double_Integral(-dmask/2, dmask/2, -dmask/2, dmask/2, npm*100,npm*100,np.real(propE))/(2*np.pi) 409 | #iEs[isc,jsc] = double_Integral(-dmask/2, dmask/2, -dmask/2, dmask/2, npm*100,npm*100,np.imag(propE))/(2*np.pi) 410 | 411 | Escreen[isc,jsc] = simpson2d(propE,-dmask/2, dmask/2, -dmask/2, dmask/2) /(2*np.pi) 412 | progress_bar(1) 413 | 414 | return Escreen 415 | 416 | 417 | def RS_intZZ(zmin, zmax, nzs, xfixed, yfixed, mask, npixmask, pixsizemask, npixscreen, dxscreen, dyscreen, wavelength, nind, verbose =False ): 418 | """ 419 | Calculates the RS_int in the of the first kind, taking information about mask, distance to screen, and screen information 420 | returns Escreen (complex electric field at obs screen), Iscreen (intensity at obs screen), iplot (the actual intensity) 421 | 422 | Args: 423 | :[zmin, zmax]: distance range limits in z [m] 424 | :nzs: number of points along the optical axis 425 | :xfixed, yfixed: x and y fixed coordinates 426 | :mask: Electric field at the mask, complex valued 2D function 427 | :npixmask: number of pixels on the side of the mask 428 | :pixsizemask: size of pixel of the mask [m] 429 | :npixscreen: number of pixels on the side of the screen (observation) 430 | :dxscreen: max_x of the screen [m], the screen range is [-dxscreen, dxscreen] 431 | :dyscreen: max_y of the screen [m], the screen range is [-dyscreen, dyscreen] 432 | :nind: scaling refractive index 433 | :wavelength: wavelength of the light [m] 434 | :verbose: defaults to False, if True prints 435 | """ 436 | # set the precision to double that of float64 437 | decimal.setcontext(decimal.Context(prec=34)) 438 | 439 | #number of pixels 440 | nps = npixscreen 441 | npm = npixmask 442 | 443 | #size of mask 444 | #dmask = pixsizemask * npm 445 | dmask = 2*dxscreen 446 | 447 | 448 | #prop const 449 | k = 2* np.pi*nind/(wavelength) 450 | 451 | #define the zpos of the mask at 0 452 | zm =0 453 | 454 | #definition of the mask 455 | xm1 = np.linspace(-dmask/2, dmask/2, npm) 456 | ym1 = np.linspace(-dmask/2, dmask/2, npm) 457 | (xm, ym) = np.meshgrid(xm1,ym1) 458 | 459 | #definition of the electric field at the mask 460 | E0m = mask 461 | 462 | ##zdists 463 | zarray = np.linspace(zmin,zmax,nzs) 464 | 465 | ####calculation of the integral 466 | Escreen = RS_intZZ_kernel(dmask, npm, nzs, xfixed, yfixed,zarray,xm,ym,zm,k, E0m, verbose) 467 | 468 | Iscreen = np.abs(Escreen)**2 469 | iplot = 10*Iscreen**0.2 470 | 471 | return Escreen, Iscreen, iplot 472 | 473 | 474 | def RS_intZZ_kernel(dmask, npm, nzs, xs, ys,zs,xm,ym,zm,k, E0m, verbose): 475 | ## definitions 476 | unit = np.ones((npm,npm), dtype=complex) 477 | r = np.zeros((npm,npm)) 478 | prop1 = np.zeros((npm,npm), dtype=complex) 479 | prop2 = np.zeros((npm,npm), dtype=complex) 480 | propE = np.zeros((npm,npm), dtype=complex) 481 | 482 | #electric field real and imaginary and total 483 | rEs =np.zeros(nzs) 484 | iEs =np.zeros(nzs) 485 | Escreen =np.zeros(nzs, dtype=complex) 486 | 487 | 488 | ###### calculate the Rayleigh Sommerfeld integral 489 | #e.g. Mahajan 2011 part II eq 1-20 490 | with Timer(): 491 | for jsc in np.arange(0,nzs-1): 492 | if verbose == True: 493 | progress_bar(jsc/nzs) 494 | 495 | r = np.sqrt((xs-xm)**2 + (ys-ym)**2 + (zs[jsc]-zm)**2) 496 | r2 = r*r 497 | prop1= np.exp(r*1.0j*k)/r2 498 | prop2 = zs[jsc] * k/(2*np.pi) *(unit / (r*k) - 1.0j * unit) 499 | propE = E0m * prop1 * prop2 500 | 501 | #rEs[isc,jsc] = double_Integral(-dmask/2, dmask/2, -dmask/2, dmask/2, npm*100,npm*100,np.real(propE))/(2*np.pi) 502 | #iEs[isc,jsc] = double_Integral(-dmask/2, dmask/2, -dmask/2, dmask/2, npm*100,npm*100,np.imag(propE))/(2*np.pi) 503 | 504 | Escreen[jsc] = simpson2d(propE,-dmask/2, dmask/2, -dmask/2, dmask/2) /(2*np.pi) 505 | progress_bar(1) 506 | 507 | return Escreen 508 | 509 | 510 | def RS_intYZ(zmin, zmax, nzs, yfixed, mask, npixmask, pixsizemask, npixscreen, dxscreen, dyscreen, wavelength, nind, verbose =False ): 511 | """ 512 | Calculates the RS_int in the of the first kind, taking information about mask, distance to screen, and screen information 513 | returns Escreen (complex electric field at obs screen), Iscreen (intensity at obs screen), iplot (the actual intensity) 514 | 515 | Args: 516 | 517 | :[zmin, zmax]: distance range limits in z [m] 518 | :nzs: number of points along the optical axis 519 | :yfixed: y fixed coordinates 520 | :mask: Electric field at the mask, complex valued 2D function 521 | :npixmask: number of pixels on the side of the mask 522 | :pixsizemask: size of pixel of the mask [m] 523 | :npixscreen: number of pixels on the side of the screen (observation) 524 | :dxscreen: max_x of the screen [m], the screen range is [-dxscreen, dxscreen] 525 | :dyscreen: max_y of the screen [m], the screen range is [-dyscreen, dyscreen] 526 | :nind: scaling refractive index 527 | :wavelength: wavelength of the light [m] 528 | :verbose: defaults to False, if True prints 529 | 530 | """ 531 | # set the precision to double that of float64 532 | decimal.setcontext(decimal.Context(prec=34)) 533 | 534 | #number of pixels 535 | nps = npixscreen 536 | npm = npixmask 537 | 538 | #size of mask 539 | #dmask = pixsizemask * npm 540 | dmask = 2*dxscreen 541 | 542 | #prop const 543 | k = 2* np.pi*nind/(wavelength) 544 | 545 | #define the zpos of the mask at 0 546 | zm =0 547 | 548 | #definition of the mask 549 | xm1 = np.linspace(-dmask/2, dmask/2, npm) 550 | ym1 = np.linspace(-dmask/2, dmask/2, npm) 551 | (xm, ym) = np.meshgrid(xm1,ym1) 552 | 553 | #definition of the electric field at the mask 554 | E0m = mask 555 | 556 | xfixed = 0 557 | ys = np.linspace(-dmask/2, dmask/2, npixscreen) 558 | 559 | ##zdists 560 | zarray = np.linspace(zmin,zmax,nzs) 561 | 562 | ####calculation of the integral 563 | Escreen = RS_intYZ_kernel(dmask, npm, nzs, npixscreen, xfixed, ys,zarray,xm,ym,zm,k, E0m, verbose) 564 | 565 | #Escreen = rEs + 1.0j*iEs 566 | Iscreen = np.abs(Escreen)**2 567 | iplot = 10*Iscreen**0.2 568 | 569 | return Escreen, Iscreen, iplot 570 | 571 | 572 | def RS_intYZ_kernel(dmask, npm, nzs, npixscreen, xfixed, ys,zarray,xm,ym,zm,k, E0m, verbose): 573 | ## definitions 574 | unit = np.ones((npm,npm), dtype=complex) 575 | r = np.zeros((npm,npm)) 576 | prop1 = np.zeros((npm,npm), dtype=complex) 577 | prop2 = np.zeros((npm,npm), dtype=complex) 578 | propE = np.zeros((npm,npm), dtype=complex) 579 | 580 | #electric field real and imaginary and total 581 | rEs =np.zeros ((npixscreen,nzs)) 582 | iEs =np.zeros ((npixscreen,nzs)) 583 | Escreen =np.zeros((npixscreen,nzs), dtype=complex) 584 | 585 | ###### calculate the Rayleigh Sommerfeld integral 586 | #e.g. Mahajan 2011 part II eq 1-20 587 | with Timer(): 588 | for isc in np.arange(0,npixscreen): 589 | if verbose == True: 590 | progress_bar(isc/npixscreen) 591 | 592 | for jsc in np.arange(0,nzs-1): 593 | r = np.sqrt((xfixed-xm)**2 + (ys[isc]-ym)**2 + (zarray[jsc]-zm)**2) 594 | r2 = r*r 595 | prop1= np.exp(r*1.0j*k)/r2 596 | prop2 = zarray[jsc] * k/(2*np.pi) *(unit / (r*k) - 1.0j * unit) 597 | propE = E0m * prop1 * prop2 598 | 599 | 600 | #rEs[isc,jsc] = double_Integral(-dmask/2, dmask/2, -dmask/2, dmask/2, npm*100,npm*100,np.real(propE))/(2*np.pi) 601 | #iEs[isc,jsc] = double_Integral(-dmask/2, dmask/2, -dmask/2, dmask/2, npm*100,npm*100,np.imag(propE))/(2*np.pi) 602 | 603 | Escreen[isc,jsc] = simpson2d(propE,-dmask/2, dmask/2, -dmask/2, dmask/2) /(2*np.pi) 604 | progress_bar(1) 605 | 606 | return Escreen -------------------------------------------------------------------------------- /pyMOE/sag_functions.py: -------------------------------------------------------------------------------- 1 | """ 2 | sag_functions.py 3 | Module containing several sag or phase functions for various optical elements 4 | 5 | 6 | """ 7 | 8 | import numpy as np 9 | # from zernike import RZern 10 | from scipy import interpolate 11 | 12 | def phase2height(phase, wavelength, n1, n0=1): 13 | """Converts the phase to height 14 | Args: 15 | :wavelength: Wavelength of the light 16 | :n1: Refractive index of the medium where the light is propagating 17 | :n0: Refractive index of the medium background""" 18 | 19 | 20 | height = phase * wavelength/(2*np.pi*(n1-n0)) 21 | 22 | return height 23 | 24 | def height2phase(height, wavelength, n1, n0=1): 25 | """Converts the height to phase 26 | Args: 27 | :wavelength: Wavelength of the light 28 | :n1: Refractive index of the medium where the light is propagating 29 | :n0: Refractive index of the medium background""" 30 | 31 | phase = height * 2*np.pi*(n1-n0)/wavelength 32 | return phase 33 | 34 | def fresnel_lens_phase(XX,YY,focal_length,wavelength, phase_offset=np.pi): 35 | """ 36 | returns the COMPLEX PHASE of a fresnel lens with input meshgrid (x,y) with center at (x0,y0) 37 | 38 | Args: 39 | :XX: x array from meshgrid 40 | :YY: y array from meshgrid 41 | :focal_length: focal distance 42 | :wavelength: wavelength of design 43 | 44 | Note: for angle (in rad), call numpy.angle(...) 45 | """ 46 | 47 | rc = np.sqrt((XX)**2 + (YY)**2) 48 | fresn = np.exp(1.0j*((focal_length-np.sqrt(focal_length**2 + rc**2))*(2*np.pi)/(wavelength) + phase_offset)) 49 | fresn = np.angle(fresn) 50 | # fresn = fresn-np.min(fresn) 51 | 52 | return fresn 53 | 54 | 55 | def axicon(x,y,angle): 56 | radius = np.sqrt(np.power(x,2)+np.power(y,2)) 57 | 58 | height = -radius*np.tan(angle) 59 | 60 | 61 | return height 62 | 63 | 64 | def spiral(x,y,L): 65 | """ 66 | returns a spiral COMPLEX PHASE with input meshgrid (x,y) with center at (x0,y0) 67 | 68 | Args: 69 | :x: x array from meshgrid 70 | :y: y array from meshgrid 71 | :L: topological charge 72 | 73 | Returns 74 | spiral phase 75 | """ 76 | 77 | theta = np.arctan2(y, x) 78 | sp = np.exp(1.0j*L*theta) 79 | sp = np.angle(sp) 80 | return sp 81 | 82 | 83 | def saddle(x,y,a,b): 84 | """ 85 | returns a COMPLEX PHASE saddle function 86 | 87 | Args: 88 | :x: x array from meshgrid 89 | :y: y array from meshgrid 90 | :a: arbitrary parameter 91 | :b: arbitrary parameter 92 | """ 93 | 94 | sfunc = (a * ((x*x - y*y)) -b) 95 | func = np.exp(1.0j*sfunc) 96 | func = np.angle(func) 97 | 98 | return func 99 | 100 | 101 | def monkey_saddle(x,y,a,b): 102 | """ 103 | returns a COMPLEX PHASE monkey saddle function 104 | 105 | Args: 106 | :x: x array from meshgrid 107 | :y: y array from meshgrid 108 | :a: arbitrary parameter 109 | :b: arbitrary parameter 110 | """ 111 | 112 | sfunc = (a * ((x*x*x- 3*x*y*y)*1e6) -b) 113 | func = np.exp(1.0j*sfunc) 114 | func = np.angle(func) 115 | 116 | return func 117 | 118 | 119 | def Alvarez_phase(XX,YY, f1, f2, tuning_distance, wavelength): 120 | """ 121 | returns the phase of an Alvarez lens profile 122 | 123 | Args: 124 | :XX: x array from meshgrid 125 | :YY: y array from meshgrid 126 | :f1: shorter focal distance f1= transitions[i], x < transitions[i+1]) 150 | y[mask] = start_value * (-1) ** (i+1) 151 | mask = x >= transitions[-1] # Add this line to handle the last transition 152 | y[mask] = start_value * (-1) ** len(transitions) 153 | return y 154 | 155 | 156 | 157 | def alternate_transitions_symmetric(x, transitions, start_value=1): 158 | assert np.all(np.diff(transitions) > 0), "transitions should be monotonic increasing" 159 | y = np.full_like(x, start_value) 160 | for i in range(len(transitions)-1): 161 | mask = np.logical_and(abs(x) >= transitions[i], abs(x) < transitions[i+1]) 162 | y[mask] = start_value * (-1) ** (i+1) 163 | mask = abs(x) >= transitions[-1] # Add this line to handle the last transition 164 | y[mask] = start_value * (-1) ** len(transitions) 165 | return y 166 | 167 | 168 | 169 | 170 | 171 | def dammann_grating_element(x,transitions, start_value=1): 172 | assert np.all(np.array(transitions)<=0.5), "transitions should be <= 0.5" 173 | 174 | y = alternate_transitions_symmetric(x,transitions=transitions, start_value=start_value) 175 | return y 176 | 177 | 178 | 179 | def dammann_grating_periodic(x,transitions, period=1, start_value=1): 180 | # assert np.all(np.array(transitions)<=0.5), "transitions should be <= 0.5" 181 | 182 | transitions = np.array(transitions)*period 183 | # x = clip_remainder(x, period) 184 | half_period = period/2 185 | x = np.mod(np.abs(x+half_period), period)-half_period 186 | 187 | y = alternate_transitions_symmetric(x,transitions=transitions, start_value=start_value) 188 | return y 189 | 190 | 191 | 192 | def dammann_2d(XX,YY,transitions_x=None, period_x=1, transitions_y=None, period_y=1, start_value_x=1, start_value_y=1): 193 | x = XX[0,:] 194 | y = YY[:,0] 195 | 196 | if transitions_x is not None: 197 | grating_x = dammann_grating_periodic(x, transitions_x, period=period_x, start_value=start_value_x) 198 | else: 199 | grating_x = np.ones_like(x) 200 | 201 | if transitions_y is not None: 202 | grating_y = dammann_grating_periodic(y, transitions_y, period=period_y, start_value=start_value_y) 203 | else: 204 | grating_y = np.ones_like(y) 205 | 206 | grating = np.outer(grating_y, grating_x) 207 | return grating 208 | 209 | 210 | 211 | 212 | 213 | def theta_to_wavelength(theta, wavelengths, L_repetitions=1, ramp_up_down=True): 214 | """ 215 | Converts a theta map to the respetive wavelengths for axisimmetric fresnel phase 216 | 217 | """ 218 | wavelength_range = wavelengths 219 | 220 | if ramp_up_down: 221 | wavelength_range = np.concatenate((wavelengths, np.flip(wavelengths))) 222 | else: 223 | wavelength_range = wavelengths 224 | wavelength_range = np.concatenate((wavelengths, [])) 225 | 226 | 227 | # theta_range = np.linspace(-np.pi, np.pi, len(wavelength_range)) 228 | # theta_range = np.linspace(0, 1, len(wavelength_range), endpoint=True) 229 | theta_range = np.arange(0, 1, 1/len(wavelength_range)) 230 | 231 | 232 | theta_norm = np.mod(theta, 2*np.pi)/(2*np.pi) 233 | theta_norm *= L_repetitions 234 | theta_norm = np.mod(theta_norm, 1) 235 | 236 | wavelength_interp = interpolate.interp1d(theta_range, wavelength_range, kind='previous',fill_value="extrapolate") 237 | 238 | ramp_wavelength = wavelength_interp(theta_norm) 239 | 240 | unique, counts = np.unique(ramp_wavelength, return_counts=True) 241 | 242 | print("Unique counts: ", unique, counts) 243 | 244 | return ramp_wavelength 245 | 246 | def polychromatic_fresnel_phase_axisymmetric(XX,YY,focal_length,wavelengths, phase_offset=np.pi, L_repetitions=10, ramp_up_down=True): 247 | 248 | 249 | rc = np.sqrt((XX)**2 + (YY)**2) 250 | theta = np.arctan2(YY,XX) 251 | wavelength = theta_to_wavelength(theta, wavelengths, L_repetitions=L_repetitions, ramp_up_down=ramp_up_down) 252 | 253 | fresn = np.exp(1.0j*((focal_length-np.sqrt(focal_length**2 + rc**2))*(2*np.pi)/(wavelength) + phase_offset)) 254 | fresn = np.angle(fresn) 255 | # fresn = fresn-np.min(fresn) 256 | 257 | return fresn -------------------------------------------------------------------------------- /pyMOE/utils.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | utils.py 4 | Module containing utility functions 5 | 6 | """ 7 | 8 | 9 | import time 10 | from datetime import timedelta 11 | import numpy as np 12 | 13 | def progress_bar(progress, bar_length=20, bar_character='#'): 14 | """ 15 | Progress bar. 16 | Writes a progress bar in place in the output 17 | 18 | Args: 19 | :progress: value between 0 and 1 20 | :bar_length: number of characters to consider in the bar 21 | """ 22 | 23 | if isinstance(progress, int): 24 | progress = float(progress) 25 | if not isinstance(progress, float): 26 | progress = 0 27 | if progress < 0: 28 | progress = 0 29 | if progress >= 1: 30 | progress = 1 31 | block = int(round(bar_length * progress)) 32 | # clear_output(wait = True) 33 | text = "Progress: [{0}] {1:.1f}%".format( bar_character * block + "-" * (bar_length - block), progress * 100) 34 | if progress <1: 35 | end = '\r' 36 | else: 37 | end = "\n" 38 | print(text, end=end) 39 | 40 | 41 | 42 | class Timer(object): 43 | """ 44 | Timer helper class to calculated elapsed time of chunk of code, from https://stackoverflow.com/a/5849861/7996766 45 | """ 46 | def __init__(self, name=None): 47 | self.name = name 48 | 49 | def __enter__(self): 50 | self.tstart = time.time() 51 | 52 | def __exit__(self, type, value, traceback): 53 | if self.name: 54 | print('[%s]' % self.name,) 55 | print('Elapsed: %s' % str(timedelta(seconds=(time.time() - self.tstart)))) 56 | 57 | 58 | 59 | def mean_squared_error(new, original): 60 | """ Calculates mean squared error between the new array and the original""" 61 | return (np.square(new-original)).mean() 62 | 63 | 64 | def create_levels(start, end, levels): 65 | """ Creates linear levels list with N""" 66 | return np.linspace(start, end, levels, endpoint=False) 67 | 68 | def digitize_array_to_bins(array, levels): 69 | """Digitizes the given array to within the number of levels provided 70 | 71 | Args: 72 | :array: input array of values 73 | :levels: integer number of levels to consider or array of levels 74 | 75 | Returns: 76 | :bins: bins corresponding to the levels 77 | :digitized: digitized array 78 | 79 | To do: 80 | Consider the midpoint selection in the future 81 | """ 82 | assert isinstance(levels, (np.ndarray, int)), "levels must be a scalar or numpy array" 83 | if isinstance(levels, int): 84 | bins = np.linspace(np.nanmin(array), np.nanmax(array) , levels, endpoint=False) 85 | else: 86 | bins = levels 87 | 88 | dig = np.digitize(array, bins, ) 89 | 90 | # Everything below the minimum bin level is changed to the minimum level 91 | dig[dig==0] = 1 92 | dig = dig-1 93 | # dig[np.isnan(array)] = np.nan 94 | return bins, dig 95 | 96 | 97 | def discretize_array(array, levels): 98 | bins, dig = digitize_array_to_bins(array, levels) 99 | 100 | return bins[dig] 101 | 102 | def simpson2d(f,ax,bx,ay,by): 103 | """ 104 | Implements Simpson method for calculating a double integral in 2D array f 105 | 106 | Arguments: 107 | :f: 2D array to calculate integral 108 | :[ax, bx]: limits of integration in x, [lower, upper] 109 | :[ay, by]: limits of integration in y, [lower, upper] 110 | """ 111 | 112 | num = len(f) 113 | hx = (bx-ax)/(num-1) 114 | hy = (by-ay)/(num-1) 115 | h = hx * hy / 9 116 | 117 | # Simpson coefficients 118 | #1 4 2 4 ...2 4 1 119 | sc = 2*np.ones(num) 120 | sc[np.arange(1,num-1,2)] = 4 121 | sc[0] = 1 122 | sc[num-1] = 1 123 | #print(sc) 124 | 125 | scx = np.meshgrid(sc,sc)[0] 126 | scxy = np.ones((num,num)) 127 | scxy[np.arange(1,num-1,2,dtype=int),:] = scx[np.arange(1,num-1,2,dtype=int),:]*sc[1] 128 | scxy[np.arange(2,num-2,2, dtype=int),:] = scx[np.arange(2,num-2,2,dtype=int),:]*sc[2] 129 | scxy[0,:] = sc 130 | scxy[num-1,:] = sc 131 | 132 | #print(scxy) 133 | 134 | # integral 135 | tint = h * np.sum(np.sum(scxy * f)) 136 | 137 | return tint 138 | 139 | 140 | 141 | def find_closest_indices(x, y): 142 | from scipy.spatial.distance import cdist 143 | 144 | # Reshape y to a column vector 145 | y = y.reshape(-1, 1) 146 | 147 | # Calculate the distances between each value of y and x 148 | distances = cdist(y, x.reshape(-1, 1)) 149 | 150 | # Find the index of the closest value in x for each value of y 151 | closest_indices = np.argmin(distances, axis=1) 152 | 153 | return closest_indices 154 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | opencv-python 2 | gdspy==1.6.3 3 | klayout==0.26.12 4 | Pillow==8.1.1 5 | scipy 6 | matplotlib 7 | numpy 8 | dask 9 | zernike 10 | shapely==1.7.1 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__ for pytest 2 | 3 | # > pytest tests\ -------------------------------------------------------------------------------- /tests/test_aperture.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # sys.path.insert(0,'..') 3 | 4 | 5 | import pyMOE as moe 6 | import numpy as np 7 | 8 | print("moe") 9 | np.random.seed(123) 10 | 11 | 12 | def test_aperture_create_aperture(): 13 | x = np.linspace(-500,500,101) 14 | y = np.linspace(-500,500,101) 15 | mask = moe.Aperture(x,y) 16 | 17 | assert mask.aperture.size >0 18 | 19 | 20 | def test_aperture_set_phase(): 21 | x = np.linspace(-500,500,101) 22 | y = np.linspace(-500,500,101) 23 | mask = moe.Aperture(x,y) 24 | phase = np.random.random(mask.shape) 25 | phase = np.ones(mask.shape) 26 | mask.phase = phase 27 | 28 | phase = mask.phase 29 | 30 | assert np.all(mask.phase == phase) 31 | 32 | 33 | 34 | def test_aperture_create_aperture_from_array(): 35 | phase_mask = np.random.random((128,128)) 36 | pixel_size = 1e-6 37 | mask = moe.generate.create_aperture_from_array(phase_mask, pixel_size=pixel_size, center=True, ) 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/test_basic.py: -------------------------------------------------------------------------------- 1 | 2 | def test_always_passes(): 3 | assert True 4 | 5 | def test_always_fails(): 6 | assert False==False -------------------------------------------------------------------------------- /tests/test_gdsconverter.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import pyMOE as moe 5 | 6 | milli = 1e-3 7 | micro = 1e-6 8 | nano = 1e-9 9 | N = 50 10 | 11 | def test_GDSMask(): 12 | 13 | mask = moe.generate.create_empty_aperture(-500*micro, 500*micro, N, -500*micro, 500*micro, N,) 14 | # f=50mm, lambda=532nm, R=500µm 15 | mask = moe.generate.fresnel_phase(mask, 50*milli, 532*nano, radius=500*micro) 16 | mask.discretize(4) 17 | gdsmask = moe.GDSMask(mask) 18 | 19 | 20 | def test_GDSMask_create_layout(): 21 | 22 | mask = moe.generate.create_empty_aperture(-500*micro, 500*micro, N, -500*micro, 500*micro, N,) 23 | # f=50mm, lambda=532nm, R=500µm 24 | mask = moe.generate.fresnel_phase(mask, 50*milli, 532*nano, radius=500*micro) 25 | mask.discretize(4) 26 | gdsmask = moe.GDSMask(mask) 27 | 28 | gdsmask.create_layout(merge=True) -------------------------------------------------------------------------------- /tests/test_generate.py: -------------------------------------------------------------------------------- 1 | 2 | import pyMOE as moe 3 | import numpy as np 4 | 5 | 6 | 7 | def test_create_empty_aperture(): 8 | micro = 1e-6 9 | mask = moe.generate.create_empty_aperture(-500*micro, 500*micro, 1001, -500*micro, 500*micro, 1001,) 10 | assert type(mask) is moe.Aperture 11 | 12 | def test_circular_aperture(): 13 | micro = 1e-6 14 | aperture = moe.generate.create_empty_aperture(-500*micro, 500*micro, 1001, -500*micro, 500*micro, 1001,) 15 | mask = moe.generate.circular_aperture(aperture, radius=250*micro, center=(100*micro, 150*micro)) 16 | 17 | def test_rectangular_aperture(): 18 | micro = 1e-6 19 | aperture = moe.generate.create_empty_aperture(-500*micro, 500*micro, 1001, -500*micro, 500*micro, 1001,) 20 | mask = moe.generate.rectangular_aperture(aperture, width=250*micro, height=100*micro, center=(100*micro, 150*micro)) 21 | mask = moe.generate.rectangular_aperture(aperture, width=250*micro, height=100*micro, corner=(-100*micro, 150*micro)) 22 | mask = moe.generate.rectangular_aperture(aperture, width=250*micro, height=100*micro) 23 | 24 | def test_fresnel_phase(): 25 | micro = 1e-6 26 | milli = 1e-3 27 | aperture = moe.generate.create_empty_aperture(-5000*micro, 5000*micro, 1001, -5000*micro, 5000*micro, 1001,) 28 | mask = moe.generate.fresnel_phase(aperture, 2*milli, 532*micro, radius=5000*micro) 29 | 30 | def test_fresnel_zone_plate(): 31 | micro = 1e-6 32 | milli = 1e-3 33 | aperture = moe.generate.create_empty_aperture(-5000*micro, 5000*micro, 1001, -5000*micro, 5000*micro, 1001,) 34 | mask = moe.generate.fresnel_zone_plate_aperture(aperture, 0.5*milli, 532*micro, radius=5000*micro) 35 | 36 | 37 | def test_aperture_operand(): 38 | micro = 1e-6 39 | nano = 1e-9 40 | milli = 1e-3 41 | aperture1 = moe.generate.create_empty_aperture(-500*micro, 1500*micro, 1001, -500*micro, 500*micro, 1001,) 42 | aperture1 = moe.generate.fresnel_phase(aperture1, 50*milli, 532*nano, radius=500*micro) 43 | 44 | aperture2 = moe.generate.create_empty_aperture(-500*micro, 1500*micro, 1001, -500*micro, 500*micro, 1001,) 45 | aperture2 = moe.generate.arbitrary_aperture_function(aperture2, moe.sag.spiral, L=8) 46 | 47 | operand = np.multiply 48 | 49 | aperture3 = moe.generate.aperture_operation(aperture1, aperture2, operand) 50 | 51 | assert np.all(operand(aperture1.aperture, aperture2.aperture) == aperture3.aperture) 52 | 53 | def test_aperture_add(): 54 | micro = 1e-6 55 | nano = 1e-9 56 | milli = 1e-3 57 | aperture1 = moe.generate.create_empty_aperture(-500*micro, 1500*micro, 1001, -500*micro, 500*micro, 1001,) 58 | aperture1 = moe.generate.fresnel_phase(aperture1, 50*milli, 532*nano, radius=500*micro) 59 | 60 | aperture2 = moe.generate.create_empty_aperture(-500*micro, 1500*micro, 1001, -500*micro, 500*micro, 1001,) 61 | aperture2 = moe.generate.arbitrary_aperture_function(aperture2, moe.sag.spiral, L=8) 62 | 63 | aperture3 = moe.generate.aperture_add(aperture1, aperture2) 64 | 65 | assert np.all(np.add(aperture1.aperture, aperture2.aperture) == aperture3.aperture) 66 | 67 | def test_aperture_subtract(): 68 | micro = 1e-6 69 | nano = 1e-9 70 | milli = 1e-3 71 | aperture1 = moe.generate.create_empty_aperture(-500*micro, 1500*micro, 1001, -500*micro, 500*micro, 1001,) 72 | aperture1 = moe.generate.fresnel_phase(aperture1, 50*milli, 532*nano, radius=500*micro) 73 | 74 | aperture2 = moe.generate.create_empty_aperture(-500*micro, 1500*micro, 1001, -500*micro, 500*micro, 1001,) 75 | aperture2 = moe.generate.arbitrary_aperture_function(aperture2, moe.sag.spiral, L=8) 76 | 77 | aperture3 = moe.generate.aperture_subtract(aperture1, aperture2) 78 | 79 | assert np.all(np.subtract(aperture1.aperture, aperture2.aperture) == aperture3.aperture) 80 | 81 | def test_aperture_multiply(): 82 | micro = 1e-6 83 | nano = 1e-9 84 | milli = 1e-3 85 | aperture1 = moe.generate.create_empty_aperture(-500*micro, 1500*micro, 1001, -500*micro, 500*micro, 1001,) 86 | aperture1 = moe.generate.fresnel_phase(aperture1, 50*milli, 532*nano, radius=500*micro) 87 | 88 | aperture2 = moe.generate.create_empty_aperture(-500*micro, 1500*micro, 1001, -500*micro, 500*micro, 1001,) 89 | aperture2 = moe.generate.arbitrary_aperture_function(aperture2, moe.sag.spiral, L=8) 90 | 91 | aperture3 = moe.generate.aperture_multiply(aperture1, aperture2) 92 | 93 | assert np.all(np.multiply(aperture1.aperture, aperture2.aperture) == aperture3.aperture) 94 | -------------------------------------------------------------------------------- /tests/test_holograms.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import pyMOE as moe 7 | import numpy as np 8 | 9 | milli = 1e-3 10 | micro = 1e-6 11 | nano = 1e-9 12 | N = 50 13 | 14 | 15 | def test_algorithm_Gerchberg_Saxton(): 16 | 17 | target = np.random.random((128,128)) 18 | 19 | # Binary level phase mask 20 | levels = 4 21 | iterations = 2 22 | levels = moe.utils.create_levels(-np.pi, np.pi, levels,) 23 | phase_mask, errors = moe.holograms.algorithm_Gerchberg_Saxton(target, iterations=iterations, levels=levels) 24 | far_field = moe.holograms.calculate_phase_farfield(phase_mask) 25 | -------------------------------------------------------------------------------- /tests/test_imports.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # sys.path.insert(0,'..') 3 | 4 | 5 | import pyMOE as moe 6 | 7 | 8 | 9 | def test_version(): 10 | assert moe.__version__ == 1.0 --------------------------------------------------------------------------------