├── .coveragerc ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTIONS.md ├── LICENSE.md ├── MANIFEST ├── MANIFEST.in ├── README.md ├── appveyor.yml ├── bin ├── soapy └── soapy.bat ├── conf ├── ELT_SH_SCAO.yaml ├── HARMONI.yaml ├── correlationRef │ ├── refImLGS.fits │ └── refImNat.fits ├── pwfs.yaml ├── py_config │ ├── sh_8x8.py │ ├── sh_8x8_corr.py │ ├── sh_8x8_learn&apply.py │ ├── sh_8x8_lgs-elongation.py │ ├── sh_8x8_lgs-uplink.py │ └── sh_8x8_lgs.py ├── sh_8x8.yaml ├── sh_8x8_learn&apply.yaml ├── sh_8x8_lgs-elongation.yaml ├── sh_8x8_lgs-uplink-precomp.yaml ├── sh_8x8_lgs-uplink.yaml ├── sh_8x8_lgs.yaml ├── sh_8x8_mcao.yaml └── sh_8x8_openloop.yaml ├── doc ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── Configuration.rst │ ├── _static │ └── theme_overrides.css │ ├── architecture.rst │ ├── atmos.rst │ ├── basicUsage.rst │ ├── conf.py │ ├── dataSources.rst │ ├── dms.rst │ ├── gui.rst │ ├── imgs │ ├── DataFlow.svg │ ├── FullClassDiagram.svg │ ├── SimpleClassDiagram.svg │ ├── annotatedGUI.png │ ├── gui_shot.png │ └── phaseScreen.png │ ├── index.rst │ ├── install.rst │ ├── intro.rst │ ├── lgs.rst │ ├── lineofsight.rst │ ├── modules.rst │ ├── recon.rst │ ├── sci.rst │ ├── sim.rst │ ├── simpleTutorial.rst │ ├── utils.rst │ └── wfs.rst ├── gui ├── AoGui.ui └── AoWidget.ui ├── res └── guiLUT.json ├── setup.cfg ├── setup.py ├── soapy ├── AOFFT.py ├── DM.py ├── LGS.py ├── __init__.py ├── _version.py ├── atmosphere.py ├── confParse.py ├── gui │ ├── __init__.py │ ├── aogui_ui5.py │ ├── gui.py │ └── jupyterconsolewidget.py ├── interp.py ├── lineofsight.py ├── lineofsight_legacy.py ├── logger.py ├── numbalib │ ├── __init__.py │ ├── numbalib.py │ └── wfslib.py ├── reconstruction.py ├── scienceinstrument.py ├── simulation.py └── wfs │ ├── __init__.py │ ├── extendedshackhartmann.py │ ├── gradient.py │ ├── pyramid.py │ ├── shackhartmann.py │ ├── shackhartmann_legacy.py │ ├── wfs.py │ └── zernike.py ├── test ├── infinite_phase_test.py ├── testAtmos.py ├── testConf.py ├── testDM.py ├── testLOS.py ├── testLgs.py ├── testSci.py ├── testSimulation.py ├── testSimulationlegacy.py ├── testWfs.py ├── test_infinitephasescreen.py ├── test_numbalib.py └── test_reconstructors.py └── versioneer.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = *AOGUIui.py, *gui.py, *_version.py, *aotools/*, *pyqtgraph/*, *aogui_ui* 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | soapy/_version.py export-subst 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # vim stuff 57 | *.swp 58 | *.un~ 59 | *.swo 60 | 61 | # Any backups 62 | *_bkup 63 | 64 | *.DS_Store 65 | 66 | .idea/ 67 | .vscode/ 68 | 69 | *.fits -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | branches: 4 | only: 5 | - master 6 | - develop 7 | 8 | python: 9 | - 2.7 10 | - 3.7 11 | addons: 12 | apt: 13 | packages: 14 | - libfftw3-dev 15 | before_install: 16 | - wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh 17 | - chmod +x miniconda.sh 18 | - ./miniconda.sh -b 19 | - export PATH=/home/travis/miniconda3/bin:$PATH 20 | - ls 21 | - conda update --yes conda 22 | install: 23 | - conda install --yes python=$TRAVIS_PYTHON_VERSION numpy scipy nose astropy pyyaml matplotlib numba 24 | - pip install pyfftw 25 | - pip install codecov 26 | - pip install aotools 27 | - python setup.py install 28 | script: 29 | - nosetests -vv --with-coverage --cover-package=soapy test/ 30 | after_success: 31 | - codecov 32 | notifications: 33 | slack: 34 | secure: W2egnr70ShnIIhYi38CAOWaY8QMCkRB6T43UqqBjuHsoXiU9Sx92VNuFFwAoXjNhLn5T7zax1Q2sSwUP9WrYNC+jbjaZlGI3EY2j3/POCRd4wASZEkW1nD7TsITCVZxnTbBOU6nF69YhyTCx69R/tjbViYqbzcwMlEVb8CN7v6vtpS27eeaK9eUAmkcoXtxlQKdtMjyoPt/aq6ep/+KQPVPJSvk4de0/eq70HfHi1QKGq2aaWlgxsLO4XrVCf6WDadRIYrpfIHZE13+iyT0LNadYp+0+xLEcyy8NbcYwdMzQnDG43967qhDYauGxjKqn408Ocv4O0c2jUBqcPC+KcgRlYWTzqMpDBSiZ4smRX8Gw/jq+xWQHy43XWnbz+yRHjJ/5ztxg1KtUiFJEULbSi5Iusi7SRnnZ4u9OjGuS43qDn2Nw6espvoFrtD1zw95wPvvyN3CNalfdjC1Z04/v1O9/EA3VV7Ov6GjXPgTQu1zOhCv8mWyTOCzo+L2vhS8Y12jSOU3swnStfj9O0TKs/HQrzBoMFSLAyodiLNsRyajlYEJmEOtiyyNf/vaJLQp2yERQdkI+UqV2i8btpWkOPmKd/am8V38yTR08FRkBdS9BkQ8cBlncmPRwz7HrCxcTOw7rfOr2/OMNnZZH1xx0Li7IPFxY4cqWCR3VAH2vDmA= 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #0.14.0 (in development) 2 | - Massive changes to internals of simulation 3 | - Many slow algorithms and classes rewritten using "numba" library - much faster, ELT scale problems now reasonable. 4 | - Rewritten parts are the lineofsight moduel and the ShackHartmann WFS 5 | - Added infinite phase screens 6 | - Interaction matrices now measured in the "reconstructor" modules rather than by the DMs themselves 7 | - Added reconstructor section in config file to allow for more complex reconstructor specifications 8 | - Improved GUI with some extra buttons! 9 | - Now depends on external aotools package (available on pip and here), internal aotools will be removed 10 | 11 | 12 | #0.13.1 (18th April 2017) 13 | - Fix issue where all required packages not installed by moving to setuptools for installation 14 | - added options to Gaussian DM 15 | 16 | #0.13.0 (10th January 2017) 17 | - Use "aotools" packages instead of "AOSimLib" (aotools packaged with Soapy) 18 | - Move pupil mask loading and formatting to seperate function in Simulation 19 | - Update GUI to use PyQt5 if available (still bugs need ironed out of this implementation) 20 | - Can set a SH WFS `pxlScale`, which overrides the FOV parameter 21 | - Improve extended SH WFS implementation 22 | - fix bug in infinite phase screen covariance calculation 23 | - Bug fixes 24 | - Docs updates 25 | 26 | #0.12.0 (18th May 2016) 27 | - Shift lots of light propagation code to a separate `LineOfSight` object, which is shared by WFSs, LGSs and Science targets 28 | - Add optional YAML configuration style. This will now be the main supported configuration method 29 | - Added ability to conjugate DMs at a finite altitude 30 | - Re-worked API to objects so they only need the simulation config file 31 | - Fix "Gradient" WFS so slopes outputted in arcseconds 32 | - Other fixes and small features 33 | 34 | #0.11.0 (3rd March 2016) 35 | - Begun restructuring of components as files were getting very large. Now components will be kept in sub-modules. Done WFS module. 36 | - Re-worked correlation WFSing - now there is an "ExtendedSH" WFS, which can take an extended object, that is convolved with the sub-aperture PSF to form a wide-field WFSer. A correlation centroiding technique is used to get centroids (thanks to @matthewtownson!) 37 | - Added options of integer frame loop delay (thanks to @jwoillez!) 38 | - Can set tau_0 parameters in atmosphere. Atmosphere also prints out atmosphere summary on init. (thanks to @jwoillez) 39 | 40 | #0.10.1 (9th February 2016) 41 | - Config objects can be converted to dictionaries 42 | 43 | #0.10.0 (7th December 2015) 44 | - Phase is now internally passed around in nano-metres 45 | - Added both WFS photon and read noise. Still needs comparisons to verify though 46 | - Added a DM which is interpolated on each frame - better for larger systems 47 | - Can now access and save the corrected EField (thanks @Robjharris37!) 48 | - Added testing infrastructure and code coverage checking - also added more tests 49 | - Data now saved with common FITS header 50 | 51 | 52 | #0.9.1 (23rd June 2015) 53 | - Tutorial added to docs 54 | - Fix bug in off-axis science cameras, where Interp2d wasnt imported 55 | - Can use a random phase screen for each loop iteration 56 | 57 | #0.9 (12th May 2015) 58 | - Name change to Soapy! 59 | 60 | - Some parameters changed name 61 | ```python 62 | 63 | sim 64 | filePrefix --> simName 65 | 66 | tel 67 | obs --> obsDiam 68 | 69 | wfs 70 | subaps --> nxSubaps 71 | subapOversamp --> fftOversamp 72 | 73 | lgs 74 | lgsUplink --> uplink 75 | lgsPupilDiam --> pupilDiam 76 | 77 | dm 78 | dmType --> type 79 | dmActs --> nxActuators (Now only specify the 1-d size) 80 | dmCond --> svdConditioning 81 | 82 | sci 83 | oversamp --> fftOversamp 84 | 85 | ``` 86 | - Configuration parameters in lists are now accessed as plurals, e.g. `sim.config.wfs[0].nxSubaps` --> `sim.config.wfss[0].nxSubaps`. 87 | 88 | #0.8.1 (22nd April 2015) 89 | - Some bugs fixed with WFSs 90 | 91 | - Made it easier to change shack-hartmann WFS centroider. Add new centroider to pyaos/tool/centroiders.py, then use the function name in the config file -- the simulation will now use the new centroider 92 | 93 | - continued adding to docs and tests 94 | 95 | #0.8.0 (15th April 2015) 96 | - `simSize` is now defined, which is the size of phase arrays to be passed around. This is currently sized at 1.2 x pupilSize, though this can be altered, and eliminates any edge effects which used to appear when interpolating near edges. This broke lots of things, which I think have all been fixed. If any exceptions which feature simSize or pupilSize occur, they could be caused by his change and if reported should be quickly fixed. Now, phase from DMs and the expected correction given to WFSs should be of shape `config.sim.simSize`, rather than `config.sim.pupilSize` as previously. 97 | 98 | - A major bug in scaling phase for r0 has been fixed, which caused a big degradation in AO performance. AO Performance now matched YAO well for single NGS cases. 99 | 100 | - A correlation centroiding method has been added by new contributor @mathewtownson 101 | 102 | - Unit testing has begun to be integrated into the simulation, with the eventual aim of test driven development. Currently, the tests are system tests which run the entire simulation in a variety of configurations. More atomic testing will continue to be added. 103 | 104 | - Documentation has been updated and is getting to the point where all existing code structures are explained. The target of the Docs will now turn to more explanatory stuff, such as a tutorial, and how to extract Data from a simulation run. 105 | 106 | - Various arrays in the WFS module now use circular NumPy buffers to avoid excessive data array creation. 107 | 108 | ###Numba Branch 109 | - Due to all the code using either pure python or NumPy or SciPy routines, the code is currently not competitive performance wise with other AO codes. To address this shortcoming, we've begun to develop a version of the code with performance critical sections accelerated using Numba, a free library which uses LLVM to compile pure python to machine code. This branch can be accessed through git and is kept up to date with the master branch, Numba is included with the Anaconda python distribution, or can be installed seperately, though it requires LLVM 3.5 to also be installed. Early results are very promising, showing over twice speed-ups for lots of applications. 110 | -------------------------------------------------------------------------------- /CONTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | #Contributing to SOAPY 2 | --------------------- 3 | 4 | Contributing to SOPY is very much encouraged. I haven't an enormous amount of time to add new code and features to SOAPY, so if you can and want to help please do! 5 | 6 | To begin, make yourself a fork using the Github interface, then use the resulting repository as your origin for local git repositories. Once you've implemented a feature or fix, again use the github interface to issue a "pull-request". 7 | 8 | ##Style Guidelines 9 | ________________ 10 | 11 | Broadly, it's good if new code follows the [pep8](https://www.python.org/dev/peps/pep-0008/), with a few other additions. In summary: 12 | - All modules, classes and methods should have decent doc strings, which say what inputs and outputs are 13 | - Doc-strings should be in the ["google style"](http://sphinxcontrib-napoleon.readthedocs.org/en/latest/example_google.html), so indented ``Parameters``, and then ``Return`` values. There are many examples in the code already. 14 | - Try to use "new-style" printing, so use ``print`` as a function rather than a statement 15 | - And "new-style" string formatting, so ``"number is: {}".format(number)`` 16 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | README.md 3 | setup.py 4 | versioneer.py 5 | bin/soapy 6 | soapy/AOFFT.py 7 | soapy/DM.py 8 | soapy/LGS.py 9 | soapy/SCI.py 10 | soapy/__init__.py 11 | soapy/_version.py 12 | soapy/aogui_ui4.py 13 | soapy/aogui_ui5.py 14 | soapy/atmosphere.py 15 | soapy/confParse.py 16 | soapy/gui.py 17 | soapy/interp.py 18 | soapy/lineofsight.py 19 | soapy/lineofsight_legacy.py 20 | soapy/logger.py 21 | soapy/reconstruction.py 22 | soapy/simulation.py 23 | soapy/numbalib/__init__.py 24 | soapy/numbalib/los.py 25 | soapy/numbalib/numbalib.py 26 | soapy/numbalib/wfs.py 27 | soapy/pyqtgraph/PlotData.py 28 | soapy/pyqtgraph/Point.py 29 | soapy/pyqtgraph/Qt.py 30 | soapy/pyqtgraph/SRTTransform.py 31 | soapy/pyqtgraph/SRTTransform3D.py 32 | soapy/pyqtgraph/SignalProxy.py 33 | soapy/pyqtgraph/ThreadsafeTimer.py 34 | soapy/pyqtgraph/Transform3D.py 35 | soapy/pyqtgraph/Vector.py 36 | soapy/pyqtgraph/WidgetGroup.py 37 | soapy/pyqtgraph/__init__.py 38 | soapy/pyqtgraph/colormap.py 39 | soapy/pyqtgraph/configfile.py 40 | soapy/pyqtgraph/debug.py 41 | soapy/pyqtgraph/exceptionHandling.py 42 | soapy/pyqtgraph/frozenSupport.py 43 | soapy/pyqtgraph/functions.py 44 | soapy/pyqtgraph/graphicsWindows.py 45 | soapy/pyqtgraph/numpy_fix.py 46 | soapy/pyqtgraph/ordereddict.py 47 | soapy/pyqtgraph/pgcollections.py 48 | soapy/pyqtgraph/ptime.py 49 | soapy/pyqtgraph/python2_3.py 50 | soapy/pyqtgraph/reload.py 51 | soapy/pyqtgraph/units.py 52 | soapy/pyqtgraph/GraphicsScene/GraphicsScene.py 53 | soapy/pyqtgraph/GraphicsScene/__init__.py 54 | soapy/pyqtgraph/GraphicsScene/exportDialog.py 55 | soapy/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py 56 | soapy/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt5.py 57 | soapy/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py 58 | soapy/pyqtgraph/GraphicsScene/mouseEvents.py 59 | soapy/pyqtgraph/canvas/Canvas.py 60 | soapy/pyqtgraph/canvas/CanvasItem.py 61 | soapy/pyqtgraph/canvas/CanvasManager.py 62 | soapy/pyqtgraph/canvas/CanvasTemplate_pyqt.py 63 | soapy/pyqtgraph/canvas/CanvasTemplate_pyqt5.py 64 | soapy/pyqtgraph/canvas/CanvasTemplate_pyside.py 65 | soapy/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py 66 | soapy/pyqtgraph/canvas/TransformGuiTemplate_pyqt5.py 67 | soapy/pyqtgraph/canvas/TransformGuiTemplate_pyside.py 68 | soapy/pyqtgraph/canvas/__init__.py 69 | soapy/pyqtgraph/console/CmdInput.py 70 | soapy/pyqtgraph/console/Console.py 71 | soapy/pyqtgraph/console/__init__.py 72 | soapy/pyqtgraph/console/template_pyqt.py 73 | soapy/pyqtgraph/console/template_pyqt5.py 74 | soapy/pyqtgraph/console/template_pyside.py 75 | soapy/pyqtgraph/dockarea/Container.py 76 | soapy/pyqtgraph/dockarea/Dock.py 77 | soapy/pyqtgraph/dockarea/DockArea.py 78 | soapy/pyqtgraph/dockarea/DockDrop.py 79 | soapy/pyqtgraph/dockarea/__init__.py 80 | soapy/pyqtgraph/exporters/CSVExporter.py 81 | soapy/pyqtgraph/exporters/Exporter.py 82 | soapy/pyqtgraph/exporters/HDF5Exporter.py 83 | soapy/pyqtgraph/exporters/ImageExporter.py 84 | soapy/pyqtgraph/exporters/Matplotlib.py 85 | soapy/pyqtgraph/exporters/PrintExporter.py 86 | soapy/pyqtgraph/exporters/SVGExporter.py 87 | soapy/pyqtgraph/exporters/__init__.py 88 | soapy/pyqtgraph/exporters/tests/__init__.py 89 | soapy/pyqtgraph/exporters/tests/test_csv.py 90 | soapy/pyqtgraph/exporters/tests/test_svg.py 91 | soapy/pyqtgraph/flowchart/Flowchart.py 92 | soapy/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py 93 | soapy/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt5.py 94 | soapy/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py 95 | soapy/pyqtgraph/flowchart/FlowchartGraphicsView.py 96 | soapy/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py 97 | soapy/pyqtgraph/flowchart/FlowchartTemplate_pyqt5.py 98 | soapy/pyqtgraph/flowchart/FlowchartTemplate_pyside.py 99 | soapy/pyqtgraph/flowchart/Node.py 100 | soapy/pyqtgraph/flowchart/NodeLibrary.py 101 | soapy/pyqtgraph/flowchart/Terminal.py 102 | soapy/pyqtgraph/flowchart/__init__.py 103 | soapy/pyqtgraph/flowchart/library/Data.py 104 | soapy/pyqtgraph/flowchart/library/Display.py 105 | soapy/pyqtgraph/flowchart/library/Filters.py 106 | soapy/pyqtgraph/flowchart/library/Operators.py 107 | soapy/pyqtgraph/flowchart/library/__init__.py 108 | soapy/pyqtgraph/flowchart/library/common.py 109 | soapy/pyqtgraph/flowchart/library/functions.py 110 | soapy/pyqtgraph/graphicsItems/ArrowItem.py 111 | soapy/pyqtgraph/graphicsItems/AxisItem.py 112 | soapy/pyqtgraph/graphicsItems/BarGraphItem.py 113 | soapy/pyqtgraph/graphicsItems/ButtonItem.py 114 | soapy/pyqtgraph/graphicsItems/CurvePoint.py 115 | soapy/pyqtgraph/graphicsItems/ErrorBarItem.py 116 | soapy/pyqtgraph/graphicsItems/FillBetweenItem.py 117 | soapy/pyqtgraph/graphicsItems/GradientEditorItem.py 118 | soapy/pyqtgraph/graphicsItems/GradientLegend.py 119 | soapy/pyqtgraph/graphicsItems/GraphItem.py 120 | soapy/pyqtgraph/graphicsItems/GraphicsItem.py 121 | soapy/pyqtgraph/graphicsItems/GraphicsLayout.py 122 | soapy/pyqtgraph/graphicsItems/GraphicsObject.py 123 | soapy/pyqtgraph/graphicsItems/GraphicsWidget.py 124 | soapy/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py 125 | soapy/pyqtgraph/graphicsItems/GridItem.py 126 | soapy/pyqtgraph/graphicsItems/HistogramLUTItem.py 127 | soapy/pyqtgraph/graphicsItems/ImageItem.py 128 | soapy/pyqtgraph/graphicsItems/InfiniteLine.py 129 | soapy/pyqtgraph/graphicsItems/IsocurveItem.py 130 | soapy/pyqtgraph/graphicsItems/ItemGroup.py 131 | soapy/pyqtgraph/graphicsItems/LabelItem.py 132 | soapy/pyqtgraph/graphicsItems/LegendItem.py 133 | soapy/pyqtgraph/graphicsItems/LinearRegionItem.py 134 | soapy/pyqtgraph/graphicsItems/MultiPlotItem.py 135 | soapy/pyqtgraph/graphicsItems/PlotCurveItem.py 136 | soapy/pyqtgraph/graphicsItems/PlotDataItem.py 137 | soapy/pyqtgraph/graphicsItems/ROI.py 138 | soapy/pyqtgraph/graphicsItems/ScaleBar.py 139 | soapy/pyqtgraph/graphicsItems/ScatterPlotItem.py 140 | soapy/pyqtgraph/graphicsItems/TextItem.py 141 | soapy/pyqtgraph/graphicsItems/UIGraphicsItem.py 142 | soapy/pyqtgraph/graphicsItems/VTickGroup.py 143 | soapy/pyqtgraph/graphicsItems/__init__.py 144 | soapy/pyqtgraph/graphicsItems/PlotItem/PlotItem.py 145 | soapy/pyqtgraph/graphicsItems/PlotItem/__init__.py 146 | soapy/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py 147 | soapy/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt5.py 148 | soapy/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py 149 | soapy/pyqtgraph/graphicsItems/ViewBox/ViewBox.py 150 | soapy/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py 151 | soapy/pyqtgraph/graphicsItems/ViewBox/__init__.py 152 | soapy/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py 153 | soapy/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt5.py 154 | soapy/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py 155 | soapy/pyqtgraph/imageview/ImageView.py 156 | soapy/pyqtgraph/imageview/ImageViewTemplate_pyqt.py 157 | soapy/pyqtgraph/imageview/ImageViewTemplate_pyqt5.py 158 | soapy/pyqtgraph/imageview/ImageViewTemplate_pyside.py 159 | soapy/pyqtgraph/imageview/__init__.py 160 | soapy/pyqtgraph/metaarray/MetaArray.py 161 | soapy/pyqtgraph/metaarray/__init__.py 162 | soapy/pyqtgraph/multiprocess/__init__.py 163 | soapy/pyqtgraph/multiprocess/bootstrap.py 164 | soapy/pyqtgraph/multiprocess/parallelizer.py 165 | soapy/pyqtgraph/multiprocess/processes.py 166 | soapy/pyqtgraph/multiprocess/remoteproxy.py 167 | soapy/pyqtgraph/opengl/GLGraphicsItem.py 168 | soapy/pyqtgraph/opengl/GLViewWidget.py 169 | soapy/pyqtgraph/opengl/MeshData.py 170 | soapy/pyqtgraph/opengl/__init__.py 171 | soapy/pyqtgraph/opengl/glInfo.py 172 | soapy/pyqtgraph/opengl/shaders.py 173 | soapy/pyqtgraph/opengl/items/GLAxisItem.py 174 | soapy/pyqtgraph/opengl/items/GLBarGraphItem.py 175 | soapy/pyqtgraph/opengl/items/GLBoxItem.py 176 | soapy/pyqtgraph/opengl/items/GLGridItem.py 177 | soapy/pyqtgraph/opengl/items/GLImageItem.py 178 | soapy/pyqtgraph/opengl/items/GLLinePlotItem.py 179 | soapy/pyqtgraph/opengl/items/GLMeshItem.py 180 | soapy/pyqtgraph/opengl/items/GLScatterPlotItem.py 181 | soapy/pyqtgraph/opengl/items/GLSurfacePlotItem.py 182 | soapy/pyqtgraph/opengl/items/GLVolumeItem.py 183 | soapy/pyqtgraph/opengl/items/__init__.py 184 | soapy/pyqtgraph/parametertree/Parameter.py 185 | soapy/pyqtgraph/parametertree/ParameterItem.py 186 | soapy/pyqtgraph/parametertree/ParameterSystem.py 187 | soapy/pyqtgraph/parametertree/ParameterTree.py 188 | soapy/pyqtgraph/parametertree/SystemSolver.py 189 | soapy/pyqtgraph/parametertree/__init__.py 190 | soapy/pyqtgraph/parametertree/parameterTypes.py 191 | soapy/pyqtgraph/pixmaps/__init__.py 192 | soapy/pyqtgraph/pixmaps/compile.py 193 | soapy/pyqtgraph/pixmaps/pixmapData_2.py 194 | soapy/pyqtgraph/pixmaps/pixmapData_3.py 195 | soapy/pyqtgraph/tests/__init__.py 196 | soapy/pyqtgraph/tests/image_testing.py 197 | soapy/pyqtgraph/tests/test_exit_crash.py 198 | soapy/pyqtgraph/tests/test_functions.py 199 | soapy/pyqtgraph/tests/test_qt.py 200 | soapy/pyqtgraph/tests/test_ref_cycles.py 201 | soapy/pyqtgraph/tests/test_srttransform3d.py 202 | soapy/pyqtgraph/tests/test_stability.py 203 | soapy/pyqtgraph/tests/ui_testing.py 204 | soapy/pyqtgraph/util/__init__.py 205 | soapy/pyqtgraph/util/cprint.py 206 | soapy/pyqtgraph/util/garbage_collector.py 207 | soapy/pyqtgraph/util/lru_cache.py 208 | soapy/pyqtgraph/util/mutex.py 209 | soapy/pyqtgraph/util/pil_fix.py 210 | soapy/pyqtgraph/util/colorama/__init__.py 211 | soapy/pyqtgraph/util/colorama/win32.py 212 | soapy/pyqtgraph/util/colorama/winterm.py 213 | soapy/pyqtgraph/widgets/BusyCursor.py 214 | soapy/pyqtgraph/widgets/CheckTable.py 215 | soapy/pyqtgraph/widgets/ColorButton.py 216 | soapy/pyqtgraph/widgets/ColorMapWidget.py 217 | soapy/pyqtgraph/widgets/ComboBox.py 218 | soapy/pyqtgraph/widgets/DataFilterWidget.py 219 | soapy/pyqtgraph/widgets/DataTreeWidget.py 220 | soapy/pyqtgraph/widgets/FeedbackButton.py 221 | soapy/pyqtgraph/widgets/FileDialog.py 222 | soapy/pyqtgraph/widgets/GradientWidget.py 223 | soapy/pyqtgraph/widgets/GraphicsLayoutWidget.py 224 | soapy/pyqtgraph/widgets/GraphicsView.py 225 | soapy/pyqtgraph/widgets/HistogramLUTWidget.py 226 | soapy/pyqtgraph/widgets/JoystickButton.py 227 | soapy/pyqtgraph/widgets/LayoutWidget.py 228 | soapy/pyqtgraph/widgets/MatplotlibWidget.py 229 | soapy/pyqtgraph/widgets/MultiPlotWidget.py 230 | soapy/pyqtgraph/widgets/PathButton.py 231 | soapy/pyqtgraph/widgets/PlotWidget.py 232 | soapy/pyqtgraph/widgets/ProgressDialog.py 233 | soapy/pyqtgraph/widgets/RawImageWidget.py 234 | soapy/pyqtgraph/widgets/RemoteGraphicsView.py 235 | soapy/pyqtgraph/widgets/ScatterPlotWidget.py 236 | soapy/pyqtgraph/widgets/SpinBox.py 237 | soapy/pyqtgraph/widgets/TableWidget.py 238 | soapy/pyqtgraph/widgets/TreeWidget.py 239 | soapy/pyqtgraph/widgets/ValueLabel.py 240 | soapy/pyqtgraph/widgets/VerticalLabel.py 241 | soapy/pyqtgraph/widgets/__init__.py 242 | soapy/wfs/__init__.py 243 | soapy/wfs/base.py 244 | soapy/wfs/extendedshackhartmann.py 245 | soapy/wfs/gradient.py 246 | soapy/wfs/pyramid.py 247 | soapy/wfs/shackhartmann.py 248 | soapy/wfs/shackhartmann_legacy.py 249 | soapy/wfs/zernike.py 250 | test/testAtmos.py 251 | test/testConf.py 252 | test/testDM.py 253 | test/testLOS.py 254 | test/testLgs.py 255 | test/testSci.py 256 | test/testSimulation.py 257 | test/testSimulationlegacy.py 258 | test/testWfs.py 259 | test/test_infinitephasescreen.py 260 | test/test_numbalib.py 261 | test/test_reconstructors.py 262 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include versioneer.py 2 | include soapy/_version.py 3 | include README.md 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simulation 'Optique Adaptative' with Python 2 | (formerly PyAOS) 3 | 4 | [![Build Status](https://travis-ci.org/AOtools/soapy.svg?branch=master)](https://travis-ci.org/AOtools/soapy) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/ea65yv0p7s32ejxx/branch/master?svg=true)](https://ci.appveyor.com/project/Soapy/soapy/branch/master) 6 | [![codecov](https://codecov.io/gh/AOtools/soapy/branch/master/graph/badge.svg)](https://codecov.io/gh/AOtools/soapy) 7 | [![Documentation Status](http://readthedocs.org/projects/soapy/badge/?version=latest)](http://soapy.readthedocs.io/en/latest/?badge=latest) 8 | 9 | 10 | ## Introduction 11 | 12 | 13 | Soapy is a Monte-Carlo Adaptive Optics Simulation toolkit written in Python. soapy can be used as a conventional end-to-end simulation, where a large number of AO configurations can be created simply by editing a configuration file. Its real power lays in modular nature of objects, such as WFSs, DMs and reconstructors which can be taken and used as building blocks to construct new and complex AO ideas and configurations. 14 | 15 | Please keep in mind that soapy is very much a work-in-progress and under heavy development. I've not yet settled on a completely stable API, but I will try and say when something big has changed. **For these reasons I would strongly reccomend against using soapy for critical work and would suggest contacting me to discuss its suitability for any work to be published.** 16 | 17 | There is documentation at http://soapy.readthedocs.io/en/latest/, again this is also being developed at this time! 18 | 19 | ## Quick-Start 20 | 21 | 22 | Try out some of the code examples in the ``conf`` directory, either run the ``soapy`` script in ``bin``, or load a python or IPython terminal: 23 | 24 | import soapy 25 | sim = soapy.Sim("configFilename") 26 | sim.aoinit() 27 | sim.makeIMat() 28 | sim.aoloop() 29 | 30 | All the data from the simulation exists in the ``sim`` object, the data available will depend upon parameters set in the configuration file. e.g. Slopes can be accessed by ``sim.allSlopes``. 31 | 32 | ## Required Libraries 33 | Soapy doesn't have too many requirements in terms of external libraries, though it does rely on some. There are also some optional libraries which are recommended for plotting or performance. 34 | 35 | ### Required 36 | 37 | numpy => 1.7.0 38 | scipy => 0.10 39 | pyfits *or* astropy 40 | pyfftw 41 | numba 42 | aotools 43 | 44 | ### For GUI 45 | 46 | PyQt5 (attempted to be compatibile with PyQt4 but not guaranteed) 47 | pyqtgraph (http://www.pyqtgraph.org) 48 | matplotlib 49 | ipython 50 | 51 | If your starting with python from scratch, there a couple of options. For Ubuntu linux (14.04+) users, all these packages can be installed via apt-get and pip: 52 | 53 | sudo apt-get install python-numpy python-scipy python-pyfftw python-astropy python-qt4 python-matplotlib ipython ipython-qtconsole python-pyqtgraph 54 | pip install aotools 55 | 56 | For Red-hat based systems these packages should also be available from repositories, though I'm not sure of their names. 57 | 58 | for mac os, all of these packages can be install via macports and pip, with 59 | 60 | sudo port install python27 py27-numpy py27-scipy py27-astropy py27-pyfftw py27-pyqt4 py27-ipython py27-pyqtgraph py27-jupyter 61 | pip install aotools 62 | 63 | For any OS (including Windows), python distributions exist which include lots of python packages useful for science. A couple of good examples are Enthought Canopy (https://www.enthought.com), which is free for academics, and Anaconda (https://store.continuum.io/cshop/anaconda/) which is also free. 64 | 65 | A lot of python packages are listed on https://pypi.python.org/pypi. Usually when python is installed, a script called ``easy_install`` is installed also, which can be used to get any package on pypi with ``easy_install ``. Pip is a more recent python package manager which is currently reccommended for use, which can be found either through your system package manager or with ``easy_install pip``. Then to install packages use ``pip install ``. 66 | 67 | 68 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | build: false 2 | 3 | environment: 4 | matrix: 5 | - PYTHON_VERSION: 2.7 6 | MINICONDA: C:\Miniconda-x64 7 | PYFFTW: pyFFTW-0.10.1-cp27-none-win_amd64.whl 8 | PYFFTW_URL: https://pypi.python.org/packages/2.7/p/pyFFTW/pyFFTW-0.10.1-cp27-none-win_amd64.whl 9 | - PYTHON_VERSION: 3.5 10 | MINICONDA: C:\Miniconda35-x64 11 | PYFFTW: pyFFTW-0.10.1-cp35-none-win_amd64.whl 12 | PYFFTW_URL: https://pypi.python.org/packages/3.5/p/pyFFTW/pyFFTW-0.10.1-cp35-none-win_amd64.whl 13 | 14 | branches: 15 | only: 16 | - master 17 | - numba 18 | - lineOfSightObj 19 | init: 20 | - "ECHO %PYTHON_VERSION% %MINICONDA%" 21 | 22 | install: 23 | - "set PATH=%MINICONDA%;%MINICONDA%\\Scripts;%PATH%" 24 | - conda config --set always_yes yes --set changeps1 no 25 | - conda update -q conda 26 | - conda info -a 27 | - "conda create -q -n test-environment python=%PYTHON_VERSION% numpy scipy nose astropy numba pyyaml" 28 | - activate test-environment 29 | - pip install coverage 30 | - pip install codecov 31 | - "curl -O %PYFFTW_URL% -o %PYFFTW%" 32 | - dir 33 | - "pip install %PYFFTW%" 34 | 35 | test_script: 36 | - nosetests --verbosity=3 test/ 37 | 38 | notifications: 39 | - provider: Slack 40 | auth_token: 41 | secure: 5vJXGvK/N3QwoJETf9FLT5FkGoGgxmjpTLfxvV/3+jD/oOmSbWly9LUpoivTdNeEP4ZS6Jo5B/QBiwdeOaxteg== 42 | channel: continuousintegration 43 | -------------------------------------------------------------------------------- /bin/soapy: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | ''' 3 | The python AO simulation startup script. 4 | either starts a command line or gui version of the simulation. 5 | ''' 6 | import sys 7 | import os 8 | import matplotlib.pyplot as plt 9 | import numpy as np 10 | 11 | from argparse import ArgumentParser 12 | import IPython 13 | import soapy 14 | # from soapy import gui 15 | 16 | 17 | def runCmdSim(sim, newIMat=False, interactive = False): 18 | ''' 19 | Runs the simulation in the command line. 20 | ''' 21 | 22 | simConfig = sim.config.sim 23 | for w in range(simConfig.nGS): 24 | exec("wfs{0}Config = sim.config.wfss[{0}]".format(w)) 25 | for d in range(simConfig.nDM): 26 | exec("dm{0}Config = sim.config.dms[{0}]".format(d)) 27 | telConf = sim.config.tel 28 | 29 | if interactive: 30 | #sim.aoinit() 31 | IPython.embed() 32 | raise SystemExit 33 | 34 | else: 35 | sim.aoinit() 36 | sim.makeIMat() 37 | sim.aoloop() 38 | 39 | 40 | 41 | if __name__=="__main__": 42 | 43 | #Define parser and add arguments 44 | parser = ArgumentParser() 45 | parser.add_argument("configFile", action="store", default=None, 46 | help="The AO config file you wish to simulate.") 47 | 48 | parser.add_argument("--gui","-g", action="store_true", dest="gui", 49 | help="Starts the AO gui to visualise your simulation") 50 | 51 | parser.add_argument("-gl", "--with-opengl", dest="gl", 52 | action="store_true", help="Use opengl for gui plotting") 53 | parser.add_argument("-i", "--interactive", dest="interactive", 54 | action="store_true", help="Open sim using ipython interactive mode") 55 | parser.add_argument("-V", "--verbosity", dest="verbosity", 56 | action="store", default=2, 57 | help="How much information to print about the simulation") 58 | 59 | parser.add_argument("-v", "--version", action="version", 60 | version=soapy.__version__) 61 | 62 | parser.add_argument('-d', "--debug", action='store_true', dest='debug', 63 | help='Sets the verbosity to highest level for debugging purposes (same is -v 3)') 64 | 65 | bin_path = os.path.abspath(os.path.realpath(__file__)+"/..") 66 | 67 | #Finally, parse args 68 | args = parser.parse_args() 69 | 70 | if args.configFile is None: 71 | raise ValueError("Must supply a configuration file") 72 | 73 | #default confFile 74 | if args.configFile!=None: 75 | configFile = args.configFile 76 | else: 77 | configFile = bin_path+"/../conf/sh_8x8.yaml" 78 | 79 | # Run sim with given args 80 | # Set the verbosity 81 | if args.debug: 82 | verbosity = 3 83 | else: 84 | verbosity = args.verbosity 85 | soapy.logger.setLoggingLevel(verbosity) 86 | 87 | #init sim with conf file 88 | sim = soapy.Sim(configFile) 89 | sim.config.sim.verbosity = args.verbosity 90 | 91 | #if gui start gui 92 | if args.gui: 93 | from soapy import gui 94 | simGUI = gui.start_gui(sim, useOpenGL=args.gl, verbosity=verbosity) 95 | 96 | else: 97 | runCmdSim(sim, interactive=args.interactive) 98 | -------------------------------------------------------------------------------- /bin/soapy.bat: -------------------------------------------------------------------------------- 1 | python.exe %~dp0\soapy %* -------------------------------------------------------------------------------- /conf/ELT_SH_SCAO.yaml: -------------------------------------------------------------------------------- 1 | simName: ELT_SH_SCAO 2 | pupilSize: 444 3 | nGS: 1 4 | nDM: 2 5 | nSci: 1 6 | nIters: 100 7 | loopTime: 0.00125 8 | reconstructor: "MVM" 9 | 10 | verbosity: 2 11 | 12 | threads: 4 13 | 14 | saveSlopes: True 15 | saveCMat: True 16 | saveIMat: True 17 | 18 | Atmosphere: 19 | scrnNo: 10 20 | scrnHeights: [0., 526., 1053., 1579., 2105., 2632., 3158., 21 | 3684., 4211., 4737., 5263., 5789., 6316., 6842., 22 | 7368., 7895., 8421., 8947., 9474., 10000., 10526., 23 | 11053., 11579., 12105., 12632., 13158., 13684., 14211., 24 | 14737., 15263., 15789., 16316., 16842., 17368., 17895., 25 | 18421., 18947., 19474., 20000.] 26 | scrnStrengths: [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 27 | 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 28 | 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.] 29 | windDirs: [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 30 | 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 31 | 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.] 32 | windSpeeds: [ 10., 11., 11., 12., 12., 13., 13., 14., 14., 15., 15., 33 | 16., 16., 17., 17., 18., 18., 19., 19., 20., 21., 21., 34 | 22., 22., 23., 23., 24., 24., 25., 25., 26., 26., 27., 35 | 27., 28., 28., 29., 29., 30.] 36 | L0: [50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 37 | 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 38 | 50] 39 | wholeScrnSize: 1024 40 | r0: 0.16 41 | infinite: True 42 | 43 | Telescope: 44 | telDiam: 39. 45 | obsDiam: 10. 46 | mask: circle 47 | 48 | WFS: 49 | 0: 50 | type: ShackHartmann 51 | GSPosition: [0, 0] 52 | GSHeight: 0 53 | GSMag: 0 54 | nxSubaps: 74 55 | pxlsPerSubap: 10 #pxl scale of ~0.3 - (0.5* diffraction limit) 56 | subapFOV: 3. 57 | fftOversamp: 1 58 | wavelength: 550e-9 59 | subapFieldStop: True 60 | centThreshold: 0 61 | 62 | 63 | DM: 64 | 0: 65 | type: TT 66 | closed: True 67 | gain: 0.6 68 | iMatValue: 0.25 69 | 70 | 1: 71 | type: FastPiezo 72 | closed: True 73 | nxActuators: 75 74 | iMatValue: 500 75 | 76 | 77 | Reconstructor: 78 | type: MVM 79 | svdConditioning: 0.0005 80 | gain: 0.6 81 | 82 | 83 | Science: 84 | 0: 85 | type: PSF 86 | position: [0, 0] 87 | FOV: 0.5 88 | wavelength: 1.65e-6 89 | pxls: 128 90 | 91 | -------------------------------------------------------------------------------- /conf/HARMONI.yaml: -------------------------------------------------------------------------------- 1 | simName: HARMONI 2 | pupilSize: 444 3 | nGS: 6 4 | nDM: 2 5 | nSci: 1 6 | nIters: 100 7 | loopTime: 0.00125 8 | reconstructor: "MVM" 9 | 10 | verbosity: 2 11 | 12 | threads: 4 13 | 14 | saveSlopes: True 15 | 16 | Atmosphere: 17 | scrnNo: 35 18 | scrnHeights: [0., 526., 1053., 1579., 2105., 2632., 3158., 19 | 3684., 4211., 4737., 5263., 5789., 6316., 6842., 20 | 7368., 7895., 8421., 8947., 9474., 10000., 10526., 21 | 11053., 11579., 12105., 12632., 13158., 13684., 14211., 22 | 14737., 15263., 15789., 16316., 16842., 17368., 17895., 23 | 18421., 18947., 19474., 20000.] 24 | scrnStrengths: [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 25 | 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 26 | 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.] 27 | windDirs: [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 28 | 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 29 | 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.] 30 | windSpeeds: [ 10., 11., 11., 12., 12., 13., 13., 14., 14., 15., 15., 31 | 16., 16., 17., 17., 18., 18., 19., 19., 20., 21., 21., 32 | 22., 22., 23., 23., 24., 24., 25., 25., 26., 26., 27., 33 | 27., 28., 28., 29., 29., 30.] 34 | L0: [50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 35 | 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 36 | 50] 37 | wholeScrnSize: 1024 38 | r0: 0.16 39 | infinite: True 40 | 41 | Telescope: 42 | telDiam: 39. 43 | obsDiam: 10. 44 | mask: circle 45 | 46 | WFS: 47 | 0: 48 | type: ShackHartmann 49 | GSPosition: [45, 0] 50 | GSHeight: 90000 51 | GSMag: 0 52 | nxSubaps: 74 53 | pxlsPerSubap: 10 #pxl scale of ~0.3 - (0.5* diffraction limit) 54 | subapFOV: 3. 55 | fftOversamp: 1 56 | wavelength: 550e-9 57 | subapFieldStop: True 58 | 1: 59 | type: ShackHartmann 60 | GSPosition: [22.5, -39] 61 | GSHeight: 90000 62 | GSMag: 0 63 | nxSubaps: 74 64 | pxlsPerSubap: 10 #pxl scale of ~0.3 - (0.5* diffraction limit) 65 | subapFOV: 3. 66 | fftOversamp: 1 67 | wavelength: 550e-9 68 | subapFieldStop: True 69 | 70 | 2: 71 | type: ShackHartmann 72 | GSPosition: [-22.5, -39] 73 | GSHeight: 90000 74 | GSMag: 0 75 | nxSubaps: 74 76 | pxlsPerSubap: 10 #pxl scale of ~0.3 - (0.5* diffraction limit) 77 | subapFOV: 3. 78 | fftOversamp: 1 79 | wavelength: 550e-9 80 | subapFieldStop: True 81 | 82 | 3: 83 | type: ShackHartmann 84 | GSPosition: [-45, 0] 85 | GSHeight: 90000 86 | GSMag: 0 87 | nxSubaps: 74 88 | pxlsPerSubap: 10 #pxl scale of ~0.3 - (0.5* diffraction limit) 89 | subapFOV: 3. 90 | fftOversamp: 1 91 | wavelength: 550e-9 92 | subapFieldStop: True 93 | 94 | 4: 95 | type: ShackHartmann 96 | GSPosition: [-22.5, 39] 97 | GSHeight: 90000 98 | GSMag: 0 99 | nxSubaps: 74 100 | pxlsPerSubap: 10 #pxl scale of ~0.3 - (0.5* diffraction limit) 101 | subapFOV: 3. 102 | fftOversamp: 1 103 | wavelength: 550e-9 104 | subapFieldStop: True 105 | 106 | 5: 107 | type: ShackHartmann 108 | GSPosition: [22.5, 39] 109 | GSHeight: 90000 110 | GSMag: 0 111 | nxSubaps: 74 112 | pxlsPerSubap: 10 #pxl scale of ~0.3 - (0.5* diffraction limit) 113 | subapFOV: 3. 114 | fftOversamp: 1 115 | wavelength: 550e-9 116 | subapFieldStop: True 117 | 118 | 119 | 120 | DM: 121 | 0: 122 | type: TT 123 | closed: True 124 | gain: 0.6 125 | iMatValue: 5. 126 | 127 | 1: 128 | type: FastPiezo 129 | closed: True 130 | nxActuators: 75 131 | iMatValue: 500 132 | 133 | 134 | Reconstructor: 135 | type: MVM 136 | svdConditioning: 0.001 137 | gain: 0.6 138 | 139 | 140 | Science: 141 | 0: 142 | type: PSF 143 | position: [0, 0] 144 | FOV: 0.5 145 | wavelength: 1.65e-6 146 | pxls: 128 147 | 148 | -------------------------------------------------------------------------------- /conf/correlationRef/refImLGS.fits: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AOtools/soapy/6c981a645478e0d0b8b078e40c9efb64aef6c940/conf/correlationRef/refImLGS.fits -------------------------------------------------------------------------------- /conf/correlationRef/refImNat.fits: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AOtools/soapy/6c981a645478e0d0b8b078e40c9efb64aef6c940/conf/correlationRef/refImNat.fits -------------------------------------------------------------------------------- /conf/pwfs.yaml: -------------------------------------------------------------------------------- 1 | simName: PWFS_100 2 | pupilSize: 100 3 | nGS: 1 4 | nDM: 1 5 | nSci: 1 6 | nIters: 500 7 | loopTime: 0.002 8 | threads: 4 9 | simOversize: 2.50 10 | 11 | 12 | verbosity: 0 13 | 14 | saveCMat: False 15 | saveSlopes: False 16 | saveDmCommands: True 17 | saveLgsPsf: True 18 | saveSciPsf: True 19 | saveStrehl : True 20 | saveWfe : True 21 | saveSciRes : True 22 | saveCalic : False 23 | saveWfsFrames: False 24 | 25 | Atmosphere: 26 | scrnNo: 1 27 | scrnHeights: [0, 5000, 10000, 15000] 28 | scrnStrengths: [0.5, 0.3, 0.1, 0.1] 29 | windDirs: [0, 45, 90, 135] 30 | windSpeeds: [10, 10, 10, 20] 31 | wholeScrnSize: 1024 32 | r0: 0.12 33 | L0: [20, 20, 20, 20] 34 | infinite: True 35 | 36 | 37 | 38 | Telescope: 39 | telDiam: 4 40 | mask: circle 41 | 42 | 43 | 44 | WFS: 45 | 0: 46 | type: Pyramid #Pyramid #ShackHartmann 47 | GSPosition: [0, 0] 48 | nxSubaps: 20 49 | FOV: 1 50 | wavelength: .5e-6 51 | nb_modulation: 32 52 | amplitude_modulation: 0.1 53 | eReadNoise: 0 54 | detector_size: 150 55 | nb_of_photon: 1000 56 | detector: CCD 57 | pupil_separation: 6 58 | DM: 59 | 0: 60 | type: Zernike #Zernike #KarhunenLoeve 61 | closed: True 62 | nxActuators: 100 63 | gain: 0.6 64 | # svdConditioning: 0.01 # 0.03 65 | iMatValue: 10 66 | 67 | 68 | 69 | Reconstructor: 70 | type: MVM 71 | svdConditioning: 0.001 72 | gain: 0.6 73 | 74 | Science: 75 | 0: 76 | position: [0, 0] 77 | FOV: .5 78 | wavelength: 1.65e-6 79 | pxls: 100 80 | 81 | -------------------------------------------------------------------------------- /conf/py_config/sh_8x8.py: -------------------------------------------------------------------------------- 1 | """ 2 | Config File for python tomographic AO simulation. 3 | 4 | This configuration file contains a python dictionary in which all simulation parameters are saved. The main dictionary ``simConfig``, is split into several sections corresponding to different parts of the simulation, each of which is itself another python dictionary 5 | 6 | This file will be parsed and missing parameters will trigger warnings, either defaulting to a reasonable value or exiting the simulation. Python syntax errors in this file can stop the simulation from running, ensure that every pararmeter finishes with a comma, including the sub-dictionaries. 7 | """ 8 | import numpy 9 | 10 | simConfiguration = { 11 | 12 | "Sim":{ 13 | "simName" : "sh_8x8", 14 | "logfile" : "sh_8x8.log", 15 | "pupilSize" : 128, 16 | "nGS" : 1, 17 | "nDM" : 2, 18 | "nSci" : 1, 19 | "nIters" : 5000, 20 | "loopTime" : 1/400.0, 21 | "reconstructor" : "MVM", 22 | 23 | "verbosity" : 2, 24 | 25 | "saveCMat" : False, 26 | "saveSlopes" : True, 27 | "saveDmCommands": False, 28 | "saveLgsPsf" : False, 29 | "saveSciPsf" : True, 30 | }, 31 | 32 | "Reconstructor":{ 33 | "type": "MVM", 34 | "svdConditioning": 0.05, 35 | "gain": 0.6, 36 | }, 37 | 38 | "Atmosphere":{ 39 | "scrnNo" : 4, 40 | "scrnHeights" : numpy.array([0, 5000, 10000, 15000]), 41 | "scrnStrengths" : numpy.array([0.5, 0.3, 0.1, 0.1]), 42 | "windDirs" : numpy.array([0, 45, 90, 135]), 43 | "windSpeeds" : numpy.array([10, 10, 15, 20]), 44 | "wholeScrnSize" : 2048, 45 | "r0" : 0.16, 46 | }, 47 | 48 | "Telescope":{ 49 | "telDiam" : 8., #Metres 50 | "obsDiam" : 1.1, #Central Obscuration 51 | "mask" : "circle", 52 | }, 53 | 54 | "WFS":{ 55 | "GSPosition" : [(0,0)], 56 | "GSHeight" : [0], 57 | "GSMag" : [8], 58 | "nxSubaps" : [8], 59 | "pxlsPerSubap" : [10], 60 | "subapFOV" : [2.5], 61 | "fftOversamp" : [3], 62 | "wavelength" : [600e-9], 63 | "centMethod" : ["brightestPxl"], 64 | "centThreshold" : [0.1], 65 | 66 | }, 67 | 68 | "LGS":{ 69 | 70 | }, 71 | 72 | "DM":{ 73 | "type" : ["TT", "Piezo"], 74 | "nxActuators" : [2, 9], 75 | "svdConditioning": [1e-15, 0.05], 76 | "closed" : [True, True], 77 | "gain" : [0.6, 0.7], 78 | "iMatValue" : [2e3, 500], 79 | }, 80 | 81 | "Science":{ 82 | "position" : [(0,0)], 83 | "FOV" : [2.0], 84 | "wavelength" : [1.65e-6], 85 | "pxls" : [128], 86 | "fftOversamp" : [2], 87 | } 88 | } 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /conf/py_config/sh_8x8_corr.py: -------------------------------------------------------------------------------- 1 | """ 2 | Config File for python tomographic AO simulation. 3 | 4 | This configuration file contains a python dictionary in which all simulation parameters are saved. The main dictionary ``simConfig``, is split into several sections corresponding to different parts of the simulation, each of which is itself another python dictionary 5 | 6 | This file will be parsed and missing parameters will trigger warnings, either defaulting to a reasonable value or exiting the simulation. Python syntax errors in this file can stop the simulation from running, ensure that every pararmeter finishes with a comma, including the sub-dictionaries. 7 | """ 8 | import numpy 9 | 10 | simConfiguration = { 11 | 12 | "Sim":{ 13 | "simName" : "sh_8x8", 14 | "logfile" : "sh_8x8.log", 15 | "pupilSize" : 128, 16 | "nGS" : 1, 17 | "nDM" : 2, 18 | "nSci" : 1, 19 | "nIters" : 5000, 20 | "loopTime" : 1/400.0, 21 | "reconstructor" : "MVM", 22 | 23 | "verbosity" : 2, 24 | 25 | "saveCMat" : False, 26 | "saveSlopes" : True, 27 | "saveDmCommands": False, 28 | "saveLgsPsf" : False, 29 | "saveSciPsf" : True, 30 | }, 31 | 32 | "Atmosphere":{ 33 | "scrnNo" : 4, 34 | "scrnHeights" : numpy.array([0,5000,10000,15000]), 35 | "scrnStrengths" : numpy.array([0.5,0.3,0.1,0.1]), 36 | "windDirs" : numpy.array([0,45,90,135]), 37 | "windSpeeds" : numpy.array([10,10,15,20]), 38 | "wholeScrnSize" : 2048, 39 | "r0" : 0.16, 40 | }, 41 | 42 | "Telescope":{ 43 | "telDiam" : 8., #Metres 44 | "obsDiam" : 1.1, #Central Obscuration 45 | "mask" : "circle", 46 | }, 47 | 48 | "WFS":{ 49 | "GSPosition" : [(0,0)], 50 | "GSHeight" : [0], 51 | "GSMag" : [8], 52 | "nxSubaps" : [8], 53 | "pxlsPerSubap" : [10], 54 | "subapFOV" : [2.5], 55 | "fftOversamp" : [3], 56 | "wavelength" : [600e-9], 57 | "centMethod" : ["centreOfGravity"], 58 | "referenceImage": ["correlationRef/refImNat.fits"], # Doesn't work, need full path 59 | "centThreshold" : [0.1], 60 | "type" : ["ExtendedSH"] 61 | }, 62 | 63 | "LGS":{ 64 | 65 | }, 66 | 67 | "DM":{ 68 | "type" : ["TT", "Piezo"], 69 | "nxActuators" : [2, 9], 70 | "svdConditioning" : [1e-15, 0.05], 71 | "closed" : [True, True], 72 | "gain" : [0.6, 0.7], 73 | "iMatValue" : [0.2, 0.2 ], 74 | }, 75 | 76 | "Science":{ 77 | "position" : [(0,0)], 78 | "FOV" : [2.0], 79 | "wavelength" : [1.65e-6], 80 | "pxls" : [128], 81 | "fftOversamp" : [2], 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /conf/py_config/sh_8x8_learn&apply.py: -------------------------------------------------------------------------------- 1 | """ 2 | Config File for python tomographic AO simulation. 3 | 4 | This configuration file contains a python dictionary in which all simulation parameters are saved. The main dictionary ``simConfig``, is split into several sections corresponding to different parts of the simulation, each of which is itself another python dictionary 5 | 6 | This file will be parsed and missing parameters will trigger warnings, either defaulting to a reasonable value or exiting the simulation. Python syntax errors in this file can stop the simulation from running, ensure that every pararmeter finishes with a comma, including the sub-dictionaries. 7 | """ 8 | import numpy 9 | 10 | simConfiguration = { 11 | 12 | "Sim":{ 13 | "simName" : "8x8_learn&apply", 14 | "pupilSize" : 64, 15 | "nGS" : 5, 16 | "nDM" : 1, 17 | "nSci" : 1, 18 | "nIters" : 1000, 19 | "loopTime" : 1/250.0, 20 | "reconstructor" : "LearnAndApply", 21 | "learnIters" : 5000, 22 | "learnAtmos" : "random", 23 | 24 | "verbosity" : 2, 25 | 26 | "saveCMat" : False, 27 | "saveSlopes" : True, 28 | "saveDmCommands": False, 29 | "saveLgsPsf" : False, 30 | "saveSciPsf" : True, 31 | }, 32 | 33 | "Atmosphere":{ 34 | "scrnNo" : 4, 35 | "scrnHeights" : numpy.array([0,5000,10000,25000]), 36 | "scrnStrengths" : numpy.array([0.5,0.3,0.1,0.1]), 37 | "windDirs" : numpy.array([0,45,90,135]), 38 | "windSpeeds" : numpy.array([10,10,15,20]), 39 | "wholeScrnSize" : 1024, 40 | "r0" : 0.16, 41 | }, 42 | 43 | "Telescope":{ 44 | "telDiam" : 8., #Metres 45 | "obsDiam" : 1.1, #Central Obscuration 46 | "mask" : "circle", 47 | }, 48 | 49 | "WFS":{ 50 | "GSPosition" : [(0,0), (-10,-10), (-10, 10), (10,-10), (10,10) ], 51 | "GSHeight" : [0, 0, 0, 0, 0 ], 52 | "GSMag" : [8, 8, 8, 8, 8 ], 53 | "nxSubaps" : [8, 8, 8, 8, 8 ], 54 | "pxlsPerSubap" : [10, 10, 10, 10, 10, ], 55 | "subapFOV" : [4.0, 4., 4., 4., 4., ], 56 | "subapOversamp" : [3, 3, 3, 3, 3, ], 57 | "wavelength" : [600e-9,600e-9, 600e-9, 600e-9, 600e-9 ], 58 | "bitDepth" : [8, 8, 8, 8, 8, ], 59 | "lgs" : [False, False, False, False, False ], 60 | }, 61 | 62 | "LGS":{ 63 | 64 | }, 65 | 66 | "DM":{ 67 | 68 | "type" : [ "Piezo"], 69 | "nxActuators" : [9], 70 | "svdConditioning" : [0.05], 71 | "closed" : [False], 72 | "gain" : [0.6], 73 | "wfs" : [0], 74 | }, 75 | 76 | "Science":{ 77 | "position" : [(0,0)], 78 | "FOV" : [3.0], 79 | "wavelength" : [1.65e-6], 80 | "pxls" : [128], 81 | "fftOversamp" : [2], 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /conf/py_config/sh_8x8_lgs-elongation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Config File for python tomographic AO simulation. 3 | 4 | This configuration file contains a python dictionary in which all simulation parameters are saved. The main dictionary ``simConfig``, is split into several sections corresponding to different parts of the simulation, each of which is itself another python dictionary 5 | 6 | This file will be parsed and missing parameters will trigger warnings, either defaulting to a reasonable value or exiting the simulation. Python syntax errors in this file can stop the simulation from running, ensure that every pararmeter finishes with a comma, including the sub-dictionaries. 7 | """ 8 | import numpy 9 | 10 | simConfiguration = { 11 | 12 | "Sim":{ 13 | "simName" : "sh_8x8_lgs", 14 | "logfile" : "sh_8x8_lgs.log", 15 | "pupilSize" : 128, 16 | "nGS" : 1, 17 | "nDM" : 2, 18 | "nSci" : 1, 19 | "nIters" : 1000, 20 | "loopTime" : 1/250.0, 21 | "gain" : 0.6, 22 | "reconstructor" : "MVM", 23 | 24 | "verbosity" : 2, 25 | 26 | "saveCMat" : False, 27 | "saveSlopes" : True, 28 | "saveDmCommands": False, 29 | "saveLgsPsf" : False, 30 | "saveSciPsf" : True, 31 | }, 32 | 33 | "Atmosphere":{ 34 | "scrnNo" : 4, 35 | "scrnHeights" : numpy.array([0,5000,10000,15000]), 36 | "scrnStrengths" : numpy.array([0.5,0.3,0.1,0.1]), 37 | "windDirs" : numpy.array([0,45,90,135]), 38 | "windSpeeds" : numpy.array([10,10,15,20]), 39 | "wholeScrnSize" : 1024, 40 | "r0" : 0.16, 41 | }, 42 | 43 | "Telescope":{ 44 | "telDiam" : 8., #Metres 45 | "obsDiam" : 1.2, #Central Obscuration 46 | "mask" : "circle", 47 | }, 48 | 49 | "WFS":{ 50 | "GSPosition" : [(0,0)]*2, 51 | "GSHeight" : [90e3], 52 | "GSMag" : [8], 53 | "nxSubaps" : [8], 54 | "pxlsPerSubap" : [20], 55 | "subapFOV" : [6.0], 56 | "subapOversamp" : [3], 57 | "wavelength" : [589e-9], 58 | "lgs" : [True], 59 | "centMethod" : ["brightestPxl"], 60 | "centThreshold" : [0.2], 61 | }, 62 | 63 | "LGS":{ 64 | "uplink" : [False], 65 | "height" : [90e3], 66 | "elongationDepth" : [8e3], 67 | "elongationLayers" : [8], 68 | "launchPosition" : [(0,0)], 69 | }, 70 | 71 | "DM":{ 72 | 73 | "type" : ["TT", "Piezo"], 74 | "nxActuators" : [2, 9], 75 | "svdConditioning" : [1e-15, 0.05], 76 | "closed" : [False, False], 77 | "gain" : [0.6, 0.6], 78 | "iMatValue" : [50, 10 ], 79 | }, 80 | 81 | "Reconstructor":{ 82 | "type": "MVM", 83 | "svdConditioning": 0.05, 84 | "gain": 0.6, 85 | }, 86 | 87 | "Science":{ 88 | "position" : [(0,0)], 89 | "FOV" : [3.0], 90 | "wavelength" : [1.65e-6], 91 | "pxls" : [128], 92 | "fftOversamp" : [2], 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /conf/py_config/sh_8x8_lgs-uplink.py: -------------------------------------------------------------------------------- 1 | """ 2 | Config File for python tomographic AO simulation. 3 | 4 | This configuration file contains a python dictionary in which all simulation parameters are saved. The main dictionary ``simConfig``, is split into several sections corresponding to different parts of the simulation, each of which is itself another python dictionary 5 | 6 | This file will be parsed and missing parameters will trigger warnings, either defaulting to a reasonable value or exiting the simulation. Python syntax errors in this file can stop the simulation from running, ensure that every pararmeter finishes with a comma, including the sub-dictionaries. 7 | """ 8 | import numpy 9 | 10 | simConfiguration = { 11 | 12 | "Sim":{ 13 | "simName" : "sh_8x8_lgsUp", 14 | "logfile" : "sh_8x8_lgsUp.log", 15 | "pupilSize" : 128, 16 | "nGS" : 2, 17 | "nDM" : 2, 18 | "nSci" : 1, 19 | "nIters" : 5000, 20 | "loopTime" : 1/400.0, 21 | "reconstructor" : "MVM_SeparateDMs", 22 | "wfsMP" : False, 23 | 24 | "verbosity" : 2, 25 | 26 | "saveCMat" : False, 27 | "saveSlopes" : True, 28 | "saveDmCommands": False, 29 | "saveLgsPsf" : False, 30 | "saveSciPsf" : True, 31 | }, 32 | 33 | "Atmosphere":{ 34 | "scrnNo" : 4, 35 | "scrnHeights" : numpy.array([0,5000,10000,15000]), 36 | "scrnStrengths" : numpy.array([0.5,0.3,0.1,0.1]), 37 | "windDirs" : numpy.array([0,45,90,135]), 38 | "windSpeeds" : numpy.array([10,10,15,20]), 39 | "wholeScrnSize" : 2048, 40 | "r0" : 0.16, 41 | }, 42 | 43 | "Telescope":{ 44 | "telDiam" : 8., #Metres 45 | "obsDiam" : 1.1, #Central Obscuration 46 | "mask" : "circle", 47 | }, 48 | 49 | "WFS":{ 50 | "GSPosition" : [(0,0), (0,0)], 51 | "GSHeight" : [0, 90e3], 52 | "GSMag" : [8, 8], 53 | "nxSubaps" : [2, 8], 54 | "pxlsPerSubap" : [10, 14], 55 | "subapFOV" : [2.0, 5.0], 56 | "wavelength" : [600e-9]*2, 57 | "lgs" : [False, True], 58 | "centThreshold" : [0.2]*2, 59 | "removeTT" : [False, True], 60 | }, 61 | 62 | "LGS":{ 63 | "uplink" : [True]*2, 64 | "pupilDiam" : [0.3]*2, 65 | "wavelength" : [600e-9]*2, 66 | "propagationMode" : ["Physical"]*2, 67 | "height" : [90e3]*2, 68 | "elongationDepth" : [0]*2, 69 | "elongationLayers" : [5]*2, 70 | }, 71 | 72 | "DM":{ 73 | "type" : ["TT", "Piezo"], 74 | "nxActuators" : [2, 9], 75 | "svdConditioning" : [1e-15, 0.07], 76 | "closed" : [True, True], 77 | "gain" : [0.6, 0.6], 78 | "iMatValue" : [1., 0.2 ], 79 | "wfs" : [0, 1], 80 | }, 81 | 82 | "Reconstructor":{ 83 | "type": "MVM", 84 | "svdConditioning": 0.05, 85 | "gain": 0.6, 86 | }, 87 | 88 | "Science":{ 89 | "position" : [(0,0)], 90 | "FOV" : [1.0], 91 | "wavelength" : [1.65e-6], 92 | "pxls" : [64], 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /conf/py_config/sh_8x8_lgs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Config File for python tomographic AO simulation. 3 | 4 | This configuration file contains a python dictionary in which all simulation parameters are saved. The main dictionary ``simConfig``, is split into several sections corresponding to different parts of the simulation, each of which is itself another python dictionary 5 | 6 | This file will be parsed and missing parameters will trigger warnings, either defaulting to a reasonable value or exiting the simulation. Python syntax errors in this file can stop the simulation from running, ensure that every pararmeter finishes with a comma, including the sub-dictionaries. 7 | """ 8 | import numpy 9 | 10 | simConfiguration = { 11 | 12 | "Sim":{ 13 | "simName" : "sh_8x8_lgs", 14 | "logfile" : "sh_8x8_lgs.log", 15 | "pupilSize" : 128, 16 | "nGS" : 2, 17 | "nDM" : 2, 18 | "nSci" : 1, 19 | "nIters" : 5000, 20 | "loopTime" : 1/400.0, 21 | "reconstructor" : "MVM_SeparateDMs", 22 | 23 | "verbosity" : 2, 24 | 25 | "saveCMat" : False, 26 | "saveSlopes" : True, 27 | "saveDmCommands": False, 28 | "saveLgsPsf" : False, 29 | "saveSciPsf" : True, 30 | }, 31 | 32 | "Atmosphere":{ 33 | "scrnNo" : 4, 34 | "scrnHeights" : numpy.array([0,5000,10000,15000]), 35 | "scrnStrengths" : numpy.array([0.5,0.3,0.1,0.1]), 36 | "windDirs" : numpy.array([0,45,90,135]), 37 | "windSpeeds" : numpy.array([10,10,15,20]), 38 | "wholeScrnSize" : 2048, 39 | "r0" : 0.16, 40 | }, 41 | 42 | "Telescope":{ 43 | "telDiam" : 8., #Metres 44 | "obsDiam" : 1.1, #Central Obscuration 45 | "mask" : "circle", 46 | }, 47 | 48 | "WFS":{ 49 | "GSPosition" : [(10,0),(0,0)], 50 | "GSHeight" : [0, 90000], 51 | "GSMag" : [8, 8], 52 | "nxSubaps" : [1, 8], 53 | "pxlsPerSubap" : [20, 10], 54 | "subapFOV" : [3., 2.5], 55 | "fftOversamp" : [3, 3], 56 | "wavelength" : [600e-9, 589e-9], 57 | "lgs" : [None, None], 58 | "centMethod" : ["brightestPxl", "brightestPxl"], 59 | "removeTT" : [False, True], 60 | 61 | }, 62 | 63 | "LGS":{ 64 | 65 | }, 66 | 67 | "DM":{ 68 | "type" : ["TT", "Piezo"], 69 | "nxActuators" : [2, 9], 70 | "svdConditioning" : [1e-15, 0.05], 71 | "closed" : [True, True], 72 | "gain" : [0.6, 0.7], 73 | "iMatValue" : [0.2, 0.2 ], 74 | "wfs" : [ 0, 1], 75 | }, 76 | 77 | "Science":{ 78 | "position" : [(0,0)], 79 | "FOV" : [2.0], 80 | "wavelength" : [1.65e-6], 81 | "pxls" : [128], 82 | "fftOversamp" : [2], 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /conf/sh_8x8.yaml: -------------------------------------------------------------------------------- 1 | simName: 2 | pupilSize: 100 3 | nGS: 1 4 | nDM: 2 5 | nSci: 1 6 | nIters: 5000 7 | loopTime: 0.0025 8 | threads: 4 9 | 10 | verbosity: 2 11 | 12 | saveCMat: False 13 | saveSlopes: True 14 | saveDmCommands: False 15 | saveLgsPsf: False 16 | saveSciPsf: True 17 | 18 | 19 | Atmosphere: 20 | scrnNo: 4 21 | scrnHeights: [0, 5000, 10000, 15000] 22 | scrnStrengths: [0.5, 0.3, 0.1, 0.1] 23 | windDirs: [0, 45, 90, 135] 24 | windSpeeds: [10, 10, 15, 20] 25 | wholeScrnSize: 2048 26 | r0: 0.16 27 | L0: [20, 20, 20, 20] 28 | infinite: True 29 | 30 | 31 | Telescope: 32 | telDiam: 8. 33 | obsDiam: 1.1 34 | mask: circle 35 | 36 | WFS: 37 | 0: 38 | type: ShackHartmann 39 | GSPosition: [0, 0] 40 | GSHeight: 0 41 | GSMag: 8 42 | nxSubaps: 8 43 | pxlsPerSubap: 10 44 | subapFOV: 2.5 45 | wavelength: 600e-9 46 | 47 | DM: 48 | 0: 49 | type: TT 50 | closed: True 51 | iMatValue: 0.25 52 | 53 | 54 | 1: 55 | type: FastPiezo 56 | closed: True 57 | nxActuators: 9 58 | iMatValue: 500 59 | 60 | Reconstructor: 61 | type: MVM 62 | svdConditioning: 0.03 63 | gain: 0.6 64 | 65 | Science: 66 | 0: 67 | position: [0, 0] 68 | FOV: 1.0 69 | wavelength: 1.65e-6 70 | pxls: 64 71 | 72 | -------------------------------------------------------------------------------- /conf/sh_8x8_learn&apply.yaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | simName: 8x8_learn&apply 4 | pupilSize: 64 5 | nGS: 5 6 | nDM: 1 7 | nSci: 1 8 | nIters: 1000 9 | loopTime: 0.004 10 | reconstructor: LearnAndApply 11 | learnIters: 5000 12 | learnAtmos: random 13 | 14 | verbosity: 2 15 | 16 | saveCMat: False 17 | saveSlopes: True 18 | saveSciPsf: True 19 | 20 | Atmosphere: 21 | scrnNo: 4 22 | scrnHeights: [0, 5000, 10000, 15000] 23 | scrnStrengths: [0.5, 0.3, 0.1, 0.1] 24 | windDirs: [0, 45, 90, 135] 25 | windSpeeds: [10, 10, 15, 20] 26 | wholeScrnSize: 2048 27 | r0: 0.16 28 | 29 | Telescope: 30 | telDiam: 8 31 | obsDiam: 1.1 32 | mask: circle 33 | 34 | WFS: 35 | 0: 36 | type: ShackHartmann 37 | GSPosition: [0, 0] 38 | GSHeight: 0 39 | GSMag: 8 40 | nxSubaps: 8 41 | pxlsPerSubap: 10 42 | subapFOV: 4. 43 | subapOversamp: 3 44 | wavelength: 600e-9 45 | 46 | 1: 47 | type: ShackHartmann 48 | GSPosition: [-10, -10] 49 | GSHeight: 0 50 | GSMag: 8 51 | nxSubaps: 8 52 | pxlsPerSubap: 10 53 | subapFOV: 4. 54 | subapOversamp: 3 55 | wavelength: 600e-9 56 | 57 | 2: 58 | type: ShackHartmann 59 | GSPosition: [-10, 10] 60 | GSHeight: 0 61 | GSMag: 8 62 | nxSubaps: 8 63 | pxlsPerSubap: 10 64 | subapFOV: 4. 65 | subapOversamp: 3 66 | wavelength: 600e-9 67 | 68 | 3: 69 | type: ShackHartmann 70 | GSPosition: [10, -10] 71 | GSHeight: 0 72 | GSMag: 8 73 | nxSubaps: 8 74 | pxlsPerSubap: 10 75 | subapFOV: 4. 76 | subapOversamp: 3 77 | wavelength: 600e-9 78 | 79 | 4: 80 | type: ShackHartmann 81 | GSPosition: [10, 10] 82 | GSHeight: 0 83 | GSMag: 8 84 | nxSubaps: 8 85 | pxlsPerSubap: 10 86 | subapFOV: 4. 87 | subapOversamp: 3 88 | wavelength: 600e-9 89 | 90 | DM: 91 | 0: 92 | type: FastPiezo 93 | closed: True 94 | nxActuators: 9 95 | svdConditioning: 0.05 96 | gain: 0.7 97 | iMatValue: 500 98 | wfs: 0 99 | 100 | Reconstructor: 101 | type: LearnAndApply 102 | svdConditioning: 0.03 103 | gain: 0.6 104 | 105 | Science: 106 | 0: 107 | position: [0, 0] 108 | FOV: 2.0 109 | wavelength: 1.65e-6 110 | pxls: 128 111 | -------------------------------------------------------------------------------- /conf/sh_8x8_lgs-elongation.yaml: -------------------------------------------------------------------------------- 1 | # Example configuration file for AO system with single, on-axis LGS. 2 | # LGS features realistic uplink propagation, so a NGS TT sensor also present 3 | 4 | simName: sh_8x8_lgsElong 5 | pupilSize: 128 6 | nGS: 1 7 | nDM: 1 8 | nSci: 1 9 | nIters: 5000 10 | loopTime: 0.0025 11 | reconstructor: MVM 12 | 13 | verbosity: 2 14 | 15 | saveCMat: False 16 | saveSlopes: True 17 | saveDmCommands: False 18 | saveLgsPsf: False 19 | saveSciPsf: True 20 | 21 | Atmosphere: 22 | scrnNo: 4 23 | scrnHeights: [0, 5000, 10000, 15000] 24 | scrnStrengths: [0.5, 0.3, 0.1, 0.1] 25 | windDirs: [0, 45, 90, 135] 26 | windSpeeds: [10, 10, 15, 20] 27 | wholeScrnSize: 2048 28 | r0: 0.16 29 | 30 | Telescope: 31 | telDiam: 8 32 | obsDiam: 1.1 33 | mask: circle 34 | 35 | WFS: 36 | 0: 37 | type: ShackHartmann 38 | GSPosition: [0, 0] 39 | GSHeight: 90000 40 | GSMag: 8 41 | nxSubaps: 8 42 | pxlsPerSubap: 10 43 | subapFOV: 5.0 44 | wavelength: 600e-9 45 | lgs: 46 | pupilDiam: 0.3 47 | wavelength: 600e-9 48 | height: 90000 49 | elongationDepth: 12000 50 | elongationLayers: 10 51 | launchPosition: [1, 0] 52 | 53 | DM: 54 | 0: 55 | type: Piezo 56 | nxActuators: 9 57 | svdConditioning: 0.07 58 | gain: 0.6 59 | iMatValue: 0.2 60 | 61 | Reconstructor: 62 | type: MVM 63 | svdConditioning: 0.03 64 | gain: 0.6 65 | 66 | Science: 67 | 0: 68 | position: [0,0] 69 | FOV: 1.5 70 | wavelength: 1.65e-6 71 | pxls: 64 72 | -------------------------------------------------------------------------------- /conf/sh_8x8_lgs-uplink-precomp.yaml: -------------------------------------------------------------------------------- 1 | # Example configuration file for AO system with single, on-axis LGS. 2 | # LGS features realistic uplink propagation, so a NGS TT sensor also present 3 | # LGS launched from primary 1m telescope aperture ("monostatic" configuration), 4 | # the size of the launched beam requires the LGS to be 1) focused at 90km and 2) 5 | # precompensated by applying DM correction before launch. However, a much tighter 6 | # (and therefore brighter) LGS spot results. 7 | 8 | simName: sh_8x8_lgsUp_precomp 9 | pupilSize: 128 10 | nGS: 2 11 | nDM: 2 12 | nSci: 1 13 | nIters: 5000 14 | loopTime: 0.0025 15 | 16 | verbosity: 2 17 | 18 | saveCMat: False 19 | saveSlopes: True 20 | saveDmCommands: False 21 | saveLgsPsf: True 22 | saveSciPsf: True 23 | 24 | Atmosphere: 25 | scrnNo: 4 26 | scrnHeights: [0,5000,10000,15000] 27 | scrnStrengths: [0.5,0.3,0.1,0.1] 28 | windDirs: [0,45,90,135] 29 | windSpeeds: [10,10,15,20] 30 | wholeScrnSize: 2048 31 | r0: 0.15 32 | 33 | Telescope: 34 | telDiam: 1 35 | obsDiam: 0.35 36 | mask: circle 37 | 38 | WFS: 39 | 0: 40 | type: ShackHartmann 41 | GSPosition: [0,0] 42 | nxSubaps: 2 43 | pxlsPerSubap: 10 44 | subapFOV: 5.0 45 | wavelength: 600e-9 46 | 47 | 48 | 1: 49 | type: ShackHartmann 50 | GSPosition: [0, 0] 51 | GSHeight: 90000 52 | GSMag: 8 53 | nxSubaps: 10 54 | pxlsPerSubap: 10 55 | subapFOV: 6.0 56 | wavelength: 589e-9 57 | removeTT: True 58 | centThreshold: 0.3 59 | fftOversamp: 6 60 | propagationMode: Physical 61 | 62 | lgs: 63 | propagationMode: Physical 64 | uplink: True 65 | pupilDiam: 1.0 66 | obsDiam: 0.35 67 | wavelength: 589e-9 68 | height: 90e3 69 | precompensated: True 70 | 71 | DM: 72 | 0: 73 | type: TT 74 | nxActuators: 2 75 | svdConditioning: 0. 76 | gain: 0.6 77 | iMatValue: 0.25 78 | wfs: 0 79 | 80 | 1: 81 | type: Piezo 82 | nxActuators: 11 83 | svdConditioning: 0.05 84 | gain: 0.6 85 | iMatValue: 500 86 | wfs: 1 87 | 88 | 89 | Reconstructor: 90 | type: MVM_SeparateDMs 91 | svdConditioning: 0.03 92 | gain: 0.6 93 | 94 | Science: 95 | 0: 96 | type: PSF 97 | position: [0,0] 98 | FOV: 4.0 99 | wavelength: 600e-9 100 | pxls: 120 101 | -------------------------------------------------------------------------------- /conf/sh_8x8_lgs-uplink.yaml: -------------------------------------------------------------------------------- 1 | # Example configuration file for AO system with single, on-axis LGS. 2 | # LGS features realistic uplink propagation, so a NGS TT sensor also present 3 | 4 | simName: sh_8x8_lgsUp 5 | pupilSize: 128 6 | nGS: 2 7 | nDM: 2 8 | nSci: 1 9 | nIters: 5000 10 | loopTime: 0.0025 11 | 12 | verbosity: 2 13 | 14 | saveCMat: False 15 | saveSlopes: True 16 | saveDmCommands: False 17 | saveLgsPsf: True 18 | saveSciPsf: True 19 | 20 | Atmosphere: 21 | scrnNo: 4 22 | scrnHeights: [0,5000,10000,15000] 23 | scrnStrengths: [0.5,0.3,0.1,0.1] 24 | windDirs: [0,45,90,135] 25 | windSpeeds: [10,10,15,20] 26 | wholeScrnSize: 2048 27 | r0: 0.16 28 | 29 | Telescope: 30 | telDiam: 8 31 | obsDiam: 1.1 32 | mask: circle 33 | 34 | WFS: 35 | 0: 36 | type: ShackHartmann 37 | GSPosition: [0,0] 38 | GSHeight: 0 39 | GSMag: 8 40 | nxSubaps: 2 41 | pxlsPerSubap: 10 42 | subapFOV: 2.0 43 | wavelength: 600e-9 44 | 45 | 1: 46 | type: ShackHartmann 47 | GSPosition: [0, 0] 48 | GSHeight: 90000 49 | GSMag: 8 50 | nxSubaps: 8 51 | pxlsPerSubap: 10 52 | subapFOV: 5.0 53 | wavelength: 600e-9 54 | removeTT: True 55 | centThreshold: 0.3 56 | lgs: 57 | propagationMode: Geometric 58 | uplink: True 59 | pupilDiam: 0.3 60 | wavelength: 600e-9 61 | height: 90e3 62 | 63 | DM: 64 | 0: 65 | type: TT 66 | nxActuators: 2 67 | wfs: 0 68 | iMatValue: 0.2 69 | gain: 0.6 70 | 71 | 1: 72 | type: Piezo 73 | nxActuators: 9 74 | svdConditioning: 0.07 75 | gain: 0.6 76 | iMatValue: 500 77 | wfs: 1 78 | 79 | Reconstructor: 80 | type: MVM_SeparateDMs 81 | svdConditioning: 0.03 82 | gain: 0.6 83 | 84 | Science: 85 | 0: 86 | type: PSF 87 | position: [0,0] 88 | FOV: 1.0 89 | wavelength: 1.65e-6 90 | pxls: 64 91 | -------------------------------------------------------------------------------- /conf/sh_8x8_lgs.yaml: -------------------------------------------------------------------------------- 1 | # Example configuration file for AO system with single, on-axis LGS. 2 | # LGS features realistic uplink propagation, so a NGS TT sensor also present 3 | 4 | simName: sh_8x8_lgsUp 5 | pupilSize: 128 6 | nGS: 2 7 | nDM: 2 8 | nSci: 1 9 | nIters: 5000 10 | loopTime: 0.0025 11 | reconstructor: MVM_SeparateDMs 12 | 13 | verbosity: 2 14 | 15 | saveCMat: False 16 | saveSlopes: True 17 | saveDmCommands: False 18 | saveLgsPsf: False 19 | saveSciPsf: True 20 | 21 | Atmosphere: 22 | scrnNo: 4 23 | scrnHeights: [0,5000,10000,15000] 24 | scrnStrengths: [0.5,0.3,0.1,0.1] 25 | windDirs: [0,45,90,135] 26 | windSpeeds: [10,10,15,20] 27 | wholeScrnSize: 2048 28 | r0: 0.16 29 | 30 | Telescope: 31 | telDiam: 8 32 | obsDiam: 1.1 33 | mask: circle 34 | 35 | WFS: 36 | 0: 37 | type: ShackHartmann 38 | GSPosition: [0,0] 39 | GSHeight: 0 40 | GSMag: 8 41 | nxSubaps: 2 42 | pxlsPerSubap: 10 43 | subapFOV: 2.0 44 | wavelength: 600e-9 45 | 46 | 1: 47 | type: ShackHartmann 48 | GSPosition: [0, 0] 49 | GSHeight: 90000 50 | GSMag: 8 51 | nxSubaps: 8 52 | pxlsPerSubap: 10 53 | subapFOV: 2.5 54 | wavelength: 600e-9 55 | removeTT: True 56 | 57 | DM: 58 | 0: 59 | type: TT 60 | nxActuators: 2 61 | wfs: 0 62 | iMatValue: 0.25 63 | gain: 0.6 64 | svdConditioning: 0 65 | 66 | 1: 67 | type: Piezo 68 | closed: True 69 | nxActuators: 9 70 | svdConditioning: 0.05 71 | gain: 0.7 72 | iMatValue: 500 73 | wfs: 1 74 | 75 | Reconstructor: 76 | type: MVM 77 | svdConditioning: 0.03 78 | gain: 0.6 79 | 80 | Science: 81 | 0: 82 | type: PSF 83 | position: [0,0] 84 | FOV: 2.0 85 | wavelength: 1.65e-6 86 | pxls: 200 87 | -------------------------------------------------------------------------------- /conf/sh_8x8_mcao.yaml: -------------------------------------------------------------------------------- 1 | simName: sh_8x8 2 | pupilSize: 64 3 | nGS: 3 4 | nDM: 2 5 | nSci: 3 6 | nIters: 5000 7 | loopTime: 0.0025 8 | reconstructor: "MVM" 9 | 10 | verbosity: 2 11 | 12 | saveCMat: False 13 | saveSlopes: True 14 | saveDmCommands: False 15 | saveLgsPsf: False 16 | saveSciPsf: True 17 | 18 | 19 | Atmosphere: 20 | scrnNo: 2 21 | scrnHeights: [0, 10000] 22 | scrnStrengths: [0.5, 0.5] 23 | windDirs: [0, 90] 24 | windSpeeds: [10, 10] 25 | wholeScrnSize: 2048 26 | r0: 0.16 27 | L0: [100, 100] 28 | infinite: True 29 | 30 | 31 | Telescope: 32 | telDiam: 4.2 33 | obsDiam: 1. 34 | mask: circle 35 | 36 | # WFSs in equalateral triange, with asterism radius 25" 37 | WFS: 38 | 0: 39 | type: ShackHartmann 40 | GSPosition: [0, 25] 41 | GSHeight: 0 42 | GSMag: 8 43 | nxSubaps: 8 44 | pxlsPerSubap: 10 45 | subapFOV: 2.5 46 | wavelength: 600e-9 47 | 48 | 1: 49 | type: ShackHartmann 50 | GSPosition: [-21.5, -12.5] 51 | GSHeight: 0 52 | GSMag: 8 53 | nxSubaps: 8 54 | pxlsPerSubap: 10 55 | subapFOV: 2.5 56 | wavelength: 600e-9 57 | 2: 58 | type: ShackHartmann 59 | GSPosition: [21.5, -12.5] 60 | GSHeight: 0 61 | GSMag: 8 62 | nxSubaps: 8 63 | pxlsPerSubap: 10 64 | subapFOV: 2.5 65 | wavelength: 600e-9 66 | 67 | DM: 68 | 0: 69 | type: FastPiezo 70 | closed: True 71 | nxActuators: 9 72 | svdConditioning: 0.05 73 | iMatValue: 500 74 | gain: 0.7 75 | 76 | 1: 77 | type: FastPiezo 78 | closed: True 79 | nxActuators: 9 80 | svdConditioning: 0.05 81 | gain: 0.7 82 | iMatValue: 500 83 | altitude: 10000 84 | diameter: 10 85 | 86 | Reconstructor: 87 | type: MVM 88 | svdConditioning: 0.03 89 | gain: 0.6 90 | 91 | Science: 92 | 0: 93 | position: [0, 0] 94 | FOV: 2.0 95 | wavelength: 1.65e-6 96 | pxls: 128 97 | 98 | 1: 99 | position: [20, 0] 100 | FOV: 2.0 101 | wavelength: 1.65e-6 102 | pxls: 128 103 | 104 | 2: 105 | position: [0, 20] 106 | FOV: 2.0 107 | wavelength: 1.65e-6 108 | pxls: 128 -------------------------------------------------------------------------------- /conf/sh_8x8_openloop.yaml: -------------------------------------------------------------------------------- 1 | simName: 2 | pupilSize: 100 3 | nGS: 1 4 | nDM: 2 5 | nSci: 1 6 | nIters: 5000 7 | loopTime: 0.0025 8 | threads: 4 9 | 10 | verbosity: 2 11 | 12 | saveCMat: False 13 | saveSlopes: True 14 | saveDmCommands: False 15 | saveLgsPsf: False 16 | saveSciPsf: True 17 | 18 | 19 | Atmosphere: 20 | scrnNo: 4 21 | scrnHeights: [0, 5000, 10000, 15000] 22 | scrnStrengths: [0.5, 0.3, 0.1, 0.1] 23 | windDirs: [0, 45, 90, 135] 24 | windSpeeds: [10, 10, 15, 20] 25 | wholeScrnSize: 2048 26 | r0: 0.16 27 | L0: [20, 20, 20, 20] 28 | infinite: True 29 | 30 | 31 | Telescope: 32 | telDiam: 8. 33 | obsDiam: 1.1 34 | mask: circle 35 | 36 | WFS: 37 | 0: 38 | type: ShackHartmann 39 | GSPosition: [0, 0] 40 | GSHeight: 0 41 | GSMag: 8 42 | nxSubaps: 8 43 | pxlsPerSubap: 10 44 | subapFOV: 2.5 45 | wavelength: 600e-9 46 | 47 | DM: 48 | 0: 49 | type: TT 50 | closed: False 51 | iMatValue: 0.25 52 | 53 | 54 | 1: 55 | type: FastPiezo 56 | closed: False 57 | nxActuators: 9 58 | iMatValue: 500 59 | 60 | Reconstructor: 61 | type: MVM 62 | svdConditioning: 0.03 63 | gain: 0.6 64 | 65 | Science: 66 | 0: 67 | position: [0, 0] 68 | FOV: 1.0 69 | wavelength: 1.65e-6 70 | pxls: 64 71 | 72 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Soapy.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Soapy.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Soapy" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Soapy" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Soapy.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Soapy.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | mock 2 | numpy 3 | sphinxcontrib-napoleon 4 | sphinx_rtd_theme 5 | ipython 6 | aotools -------------------------------------------------------------------------------- /doc/source/Configuration.rst: -------------------------------------------------------------------------------- 1 | .. _configuration: 2 | 3 | Configuration 4 | ************* 5 | 6 | Configuration of the system is handled by the ``confParse`` module, that reads the simulation parameters from a given configuration file. This file should be a YAML file, which contains groups for each simulation sub-module. Where a sub-module may consist of multiple components i.e. Wave-front sensors, each WFS must be specified seperately, with an integer index, for example:: 7 | 8 | WFS: 9 | 0: 10 | GSMag: 0 11 | GSPosition: (0, 0) 12 | 1: 13 | GSMag: 1 14 | GSPosition: (1, 0) 15 | 16 | Example configuration files can be found in the ``conf`` directory of the soapy package. 17 | (Note: Previously, a Python file was used for configuration. This format is still supported but can lead to messy configuration files! There are still examples of these in the source repository if you prefer.) 18 | 19 | Below is a list of all possible simulation parameters. Parameters which have a description ending in \** can be altered while the simulation is running. When others are changed and ``aoinit`` must be run before they will take effect and they may break a running simulation. 20 | 21 | Simulation Parameters 22 | --------------------- 23 | .. autoclass:: soapy.confParse.SimConfig 24 | :members: 25 | 26 | Telescope Parameters 27 | -------------------- 28 | 29 | .. autoclass:: soapy.confParse.TelConfig 30 | :members: 31 | 32 | Atmosphere Parameters 33 | --------------------- 34 | 35 | .. autoclass:: soapy.confParse.AtmosConfig 36 | :members: 37 | 38 | Wave-front Sensor Parameters 39 | ---------------------------- 40 | 41 | .. autoclass:: soapy.confParse.WfsConfig 42 | :members: 43 | 44 | Laser Guide Star Parameters 45 | --------------------------- 46 | 47 | .. autoclass:: soapy.confParse.LgsConfig 48 | :members: 49 | 50 | Deformable Mirror Parameters 51 | ---------------------------- 52 | 53 | .. autoclass:: soapy.confParse.DmConfig 54 | :members: 55 | 56 | Reconstructor Parameters 57 | ------------------------ 58 | 59 | .. autoclass:: soapy.confParse.ReconstructorConfig 60 | :members: 61 | 62 | Science Camera Parameters 63 | ------------------------- 64 | 65 | .. autoclass:: soapy.confParse.SciConfig 66 | :members: 67 | -------------------------------------------------------------------------------- /doc/source/_static/theme_overrides.css: -------------------------------------------------------------------------------- 1 | /* override table width restrictions */ 2 | .wy-table-responsive table td, .wy-table-responsive table th { 3 | /* !important prevents the common CSS stylesheets from 4 | overriding this as on RTD they are loaded after this stylesheet */ 5 | white-space: normal !important; 6 | } 7 | 8 | .wy-table-responsive { 9 | overflow: visible !important; 10 | } 11 | -------------------------------------------------------------------------------- /doc/source/architecture.rst: -------------------------------------------------------------------------------- 1 | ***************** 2 | Simulation Design 3 | ***************** 4 | 5 | Data flow and modularity 6 | ------------------------ 7 | Soapy has been designed from the beginning to be extremely modular, where each AO component can be used individually. In fact, the file `simulation.py`, really only acts as a shepherd, moving data around between the components, with some fancy bits for saving data and printing nice outputs. A simple control loop to replace that file could be written from scratch in only 5-10 lines of Python! 8 | 9 | This modularity is well illustrated by a data flow diagram describing the simulations, show in Figure 1, below. 10 | 11 | .. image:: imgs/DataFlow.svg 12 | :align: center 13 | 14 | Figure 1. Soapy Data Flow 15 | 16 | Class Hierarchy 17 | --------------- 18 | Pythons Object Orientated nature has also been exploited. Categories of AO component have a `base class`, which deals with most of the interfaces to the main simulation module and other boiler-plate style code. The classes which represent actual AO modules inherit this base class, and hopefully need only add interesting functionality specific to that new component. This is illustrated in the class diagram in Figure 2, with some example methods and attributes of each class. 19 | 20 | .. image:: imgs/FullClassDiagram.svg 21 | :align: center 22 | 23 | Figure 2. Class diagram with example attributes and methods 24 | 25 | It is aimed that in future developments of Soapy, this philosophy will be extended. Currently the WFS, science camera and LGS modules all deal with optical propagation through turbulence separately, clearly this should be combined into one place to ease code readability and maintenance. This work is currently under development. Figure 3 shows all the Soapy classes in a simplified class diagram, including the new `LineOfSight` class currently under construction. 26 | 27 | .. image:: imgs/SimpleClassDiagram.svg 28 | :align: center 29 | 30 | Figure 3. Full, simplified class diagram with the lineOfSight class under construction. 31 | -------------------------------------------------------------------------------- /doc/source/atmos.rst: -------------------------------------------------------------------------------- 1 | ********** 2 | Atmosphere 3 | ********** 4 | .. automodule:: soapy.atmosphere 5 | 6 | Atmosphere Class 7 | ================ 8 | .. autoclass:: soapy.atmosphere.atmos 9 | :members: 10 | 11 | Phase Screen Creation and Saving 12 | ================================ 13 | .. autofunction:: soapy.atmosphere.makePhaseScreens 14 | 15 | .. autofunction:: soapy.atmosphere.ft_phase_screen 16 | 17 | .. autofunction:: soapy.atmosphere.ft_sh_phase_screen 18 | 19 | 20 | -------------------------------------------------------------------------------- /doc/source/basicUsage.rst: -------------------------------------------------------------------------------- 1 | .. _basicUsage: 2 | 3 | Basic Usage 4 | *********** 5 | 6 | This section describes how to the simulation for basic cases, that is, using the full end to end code to create and save data which can then be analysed afterwards. Such a scenario is a common one when exploring parameters on conventional AO systems. 7 | 8 | Configuration 9 | ------------- 10 | 11 | In Soapy, all AO parameters are controlled from the configuration file. This is a python script which contains all the information required to run many AO configurations. A few examples are provided in the ``conf`` directory when you download the code. All parameters are held in one large dictionary, titled ``simConfiguration``, and are then grouped into relavent sections. 12 | 13 | ``Sim`` parameters control simulation wide parameters, such as the filename to save data, the number of simulated phase points, the number of WFSs, DMs and Science cameras as well as the name of the reconstructor used to tie them together. The ``simName`` parameter specifies a directory, which will be created if it does not already exist, where all AO run data will be recorderd. Each run will create a new time-stamped directory within the parent ``simName`` one to save run specific data. Data applying to all runs, such as the interaction and control matrices are stored in the ``simName`` directory. 14 | 15 | ``Atmosphere`` parameters are responsible for the structure of the simulated atmosphere. This includes the number of simulated turbulence layers and the integrated seeing strength, r\ :sub:`0`. Some values in the Atmosphere group must be formatted as a list or array, as they describe parameters which apply to different turbulence layers. 16 | 17 | Parameters describing the physical telescope are given in the ``Telescope`` group. These include the telescope and central obscuration diameters, and a pupil mask. 18 | 19 | WFSs, LGSs, DMs and Science camera are configured by the ``WFS``, ``LGS``, ``DM`` and ``Science`` parameter groups. As multiple instances of each of these components may be present, every parameters in these groups is represented by either a list or numpy array, where each element specifies that component number. For WFSs and DMs, a ``type`` parameter is also given. This is a the name of the python object which will be used to represent that component, and a class of the same name must be present in the ``WFS.py`` or ``DM.py`` module, respectively. Other WFS or DM parameters may then have different behaviours depending on the type which is to be used. 20 | 21 | 22 | Each parameter that can be set is described in the :ref:`configuration` section. 23 | 24 | Creating Phase Screens 25 | ---------------------- 26 | 27 | For most applications of Soapy, some randomly generated phase screens are required. These can either be created just before the simulation begins, during the initialisation phase, or some existing screens can be specified for the simulation to use. To generate new phase screens with the parameters specified in ``Atmosphere`` each time the simulation is run, set the ``Atmosphere`` parameter, ``newScreens`` to ``True``. 28 | 29 | .. image:: imgs/phaseScreen.png 30 | :align: center 31 | 32 | If instead you wish to used existing phase screens, provide the path to, and filename of each screen in the ``screenNames`` parameter as a list. Screens specified to be loaded must be saved as FITS files, where each file contains a single, 2 dimensional phase screen. The simulation will largely trust that the screen parameters are valid, so other parameters in the ``Atmosphere`` group, such as the ``wholeScreenSize``, ``r0`` and ``L0`` may be discounted. If you would like the simulation to be able to scale your phase screens such that they adhere to the ``r0`` and ``screenStrength`` values set in the configuration file, then the FITS file header must contain a parameter ``R0`` which is expressed in units of phase pixels. 33 | 34 | Running the Simulation 35 | ---------------------- 36 | 37 | Once all the configuration parameters have been set, and you have decided how whether to load or generate phase screens, the simulation is ready to be run. This can be either from the GUI, the command line or from a script. 38 | 39 | Graphical User Interface 40 | ^^^^^^^^^^^^^^^^^^^^^^^^ 41 | 42 | When running Soapy configurations for the first time it can be a good idea to run them in the GUI to sure that components look to be operating as expected. The GUI is shown below running a simple SCAO case, with a tip-tilt mirror and a stack array DM. 43 | 44 | .. image:: imgs/annotatedGUI.png 45 | :align: center 46 | 47 | If soapy has been installed, or the ``bin`` directory is in the bash PATH, the GUI is started from the command line with the command:: 48 | 49 | soapy -g path/to/configFile.yaml 50 | 51 | The ``soapy`` script can do a few other things as well, use ``soapy --help`` to see all other available options. 52 | 53 | Once the GUI has loaded it will begin the initialisation of the simulation. This stage initialises all the simulated components, loads or generates phase screens, allocates data buffers and calculates various required parameters from the parameters given in the configuration file. If any parameters or the configuration file is changed at any point, this initialisation step can be rerun by clicking the "AO Init" button. 54 | 55 | The next step in most systems will be to record an interaction matrix, where the effect of each DM influence on the WFS(s) is recorded, and used to calculate a command matrix. From the GUI, this is achieved by clicking the "makeIMat" button. Interaction matrices, command matrices and DM influence functions can be saved in the ``simName`` directory and the simulation checks to see if there are valid ones in that directory it can load instead of making them again. If you would like to force a new interaction matrix to be made, perhaps because you've changed parameters which may effect the new interaction matrix, tick the "Force new?" box. 56 | 57 | Once this is complete, you can now click "Run!" to run the simulation. You will now see the atmospheric phase moving across the WFS(s), and the resulting measurements on the WFS. This will be recorded, and transformed to DM commands measurements via the reconstructor, and finally, the science phase will be corrected and a better PSF achieved. The loop gain for each DM can be altered using the spin boxes in the top right of the GUI. 58 | 59 | Using the GUI significantly slows down the simulation operation, but this can be aleviated by limiting the simulation update rate using the top spin box. 60 | 61 | The console in the bottom left of the GUI can be used to either change parameters of the simulation or visualise other data sources. It is a complete python console, provided by the IPython library. To load a new config file into the GUI, go the file>Load Configuration File. You will then have to click "AO Init" to begin initialisation. 62 | 63 | Command Line and Scripting 64 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 65 | 66 | To run the simulation from the command line, either use :: 67 | 68 | soapy -i /path/to/configFile.yaml 69 | 70 | which will initialise the simulation before dropping you into an interaction ipython prompt, or simply start or python interpretter of choice and run :: 71 | 72 | import soapy #Imports python library 73 | sim = soapy.Sim("/path/to/configFile.yaml") #Loads the configuration file 74 | sim.aoinit() #Initialises all AO simulated objects 75 | 76 | The above code would also be used in scripts to run the simulation. 77 | 78 | To measure the interaction matrix run:: 79 | 80 | sim.makeIMat() 81 | 82 | or:: 83 | 84 | sim.makeIMat(forceNew=True) 85 | 86 | if you'd like to force the creation of interaction matrices, command matrices and DM influence functions. 87 | 88 | Once complete, you're now ready to run the simulation with:: 89 | 90 | sim.aoloop() 91 | 92 | You should now see a rolling counter of the frame number and current Strehl ratio of each science target. 93 | 94 | Retrieving Simulation Data 95 | -------------------------- 96 | 97 | After a simulation run has completed, the resulting data must be retrieved for analysis. The data stored by Soapy depends on the parameters set in the ``sim`` group in the configuration file. Once a ``aoloop`` has completed, the data will be saved into the ``simName`` directory, in a further, time-stamped directory for that particular run. Whithin the simulation, the data is stored in numpy array structures which can be accessed either after the run has completed or during the run (if it is run in the, or in a python thread on the command line). 98 | 99 | The strehl ratio of each science target is always stored. Internally, it is kept in the arrays:: 100 | 101 | sim.instStrehl 102 | 103 | and:: 104 | 105 | sim.longStrehl 106 | 107 | Which are the instantaneous and long exposure strehl ratio for each science target. Each of these is of shape ``sim.config.sim.nSci`` by ``sim.config.sim.nIters``. Note that this is even the case for only a single science target, when the science target Strehl ratios are always accessed with ``sim.longStrehl[0]``. Strehl ratios may also saved in the ``simName`` directory as ``instStrehl.fits`` and ``longStrehl.fits``. 108 | 109 | There are many other data sources available to save or access from the simulation, these are listed in :ref:`dataSources`. 110 | -------------------------------------------------------------------------------- /doc/source/dataSources.rst: -------------------------------------------------------------------------------- 1 | .. _dataSources: 2 | 3 | Data Sources 4 | ============ 5 | 6 | In this section, the data sources which are stored in soapy are listed and a description of how they are obtained is given. 7 | 8 | 9 | Simulation Run Data 10 | ------------------- 11 | The following sources of data are recorded for each simulation run and are saved as a fits file in a time stamped run specific directory inside the ``simName`` directory. They can be accessed by ``sim.``, where ```` is listed in the "Internal data structure" column. As the storing of some of these data sources can increase memory usage significantly, they are not all saved by default, and the flag must be set in the configuration file. 12 | 13 | +-------------+-------------------+------------------+-------------------------+ 14 | |Data | Saved filename |Internal data |Description | 15 | | | |structure | | 16 | +=============+===================+==================+=========================+ 17 | |Instantaneous|``instStrehl.fits``|``instStrehl`` |The instantaneous | 18 | |Strehl ratio | | |strehl ratio for | 19 | | | | |each science target | 20 | | | | |frame | 21 | +-------------+-------------------+------------------+-------------------------+ 22 | |Long exposure|``longStrehl.fits``|``longStrehl`` |The long exposure | 23 | |Strehl ratio | | |strehl ratio for | 24 | | | | |each science target | 25 | | | | |frame | 26 | +-------------+-------------------+------------------+-------------------------+ 27 | |Wavefront |``WFE.fits`` |``WFE`` |The corrected wave- | 28 | |Error | | |front error for each | 29 | | | | |science target in nm | 30 | +-------------+-------------------+------------------+-------------------------+ 31 | |Science PSF |``sciPsf_n.fits`` |``sciImgs[n]`` |The science camera PSFs | 32 | | | | |where ``n`` indicates the| 33 | | | | |camera number | 34 | +-------------+-------------------+------------------+-------------------------+ 35 | |Residual |``sciResidual_n |``sciPhase[n]`` |The residual uncorrected | 36 | |Science phase|.fits`` | |phase across science | 37 | | | | |target ``n`` | 38 | +-------------+-------------------+------------------+-------------------------+ 39 | |WFS |``slopes.fits`` | ``allSlopes`` |All WFS measurements | 40 | |measurements | | |stored in a numpy | 41 | | | | |array of size | 42 | | | | |(nIters, totalSlopes) | 43 | +-------------+-------------------+------------------+-------------------------+ 44 | |WFS Frames |``wfsFPFrames/ |``sim.wfss[n]. |WFS detector image, only | 45 | | |wfs-n_frame-i |wfsDetectorPlane``|last frame stored | 46 | | |.fits`` | |in memory. Can save each | 47 | | | | |frame, ``i``, from wfs | 48 | | | | |``n`` | 49 | +-------------+-------------------+------------------+-------------------------+ 50 | |DM Commands |``dmCommands.fits``|``allDmCommands`` |DM commands for all | 51 | | | | |DMs present in numpy | 52 | | | | |of size | 53 | | | | |(nIters, totaldmCommands)| 54 | +-------------+-------------------+------------------+-------------------------+ 55 | -------------------------------------------------------------------------------- /doc/source/dms.rst: -------------------------------------------------------------------------------- 1 | ****************** 2 | Deformable Mirrors 3 | ****************** 4 | 5 | .. automodule:: soapy.DM 6 | 7 | Base DM Class 8 | ------------- 9 | .. autoclass:: soapy.DM.DM 10 | :members: 11 | :show-inheritance: 12 | 13 | Real DM Classes 14 | --------------- 15 | .. autoclass:: soapy.DM.TT 16 | :members: 17 | :show-inheritance: 18 | 19 | .. autoclass:: soapy.DM.Zernike 20 | :members: 21 | :show-inheritance: 22 | 23 | .. autoclass:: soapy.DM.Piezo 24 | :members: 25 | :show-inheritance: 26 | 27 | .. autoclass:: soapy.DM.GaussStack 28 | :members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /doc/source/gui.rst: -------------------------------------------------------------------------------- 1 | GUI Modules 2 | =========== 3 | 4 | Modules used to make the soapy GUI 5 | 6 | 7 | soapy.AOGUIui module 8 | -------------------- 9 | 10 | .. automodule:: soapy.AOGUIui 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | soapy.matplotlibWidget module 17 | ----------------------------- 18 | 19 | .. automodule:: soapy.matplotlibWidget 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | 25 | 26 | soapy.soapy_gui module 27 | ---------------------- 28 | 29 | .. automodule:: soapy.soapy_gui 30 | :members: 31 | :undoc-members: 32 | :show-inheritance: -------------------------------------------------------------------------------- /doc/source/imgs/annotatedGUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AOtools/soapy/6c981a645478e0d0b8b078e40c9efb64aef6c940/doc/source/imgs/annotatedGUI.png -------------------------------------------------------------------------------- /doc/source/imgs/gui_shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AOtools/soapy/6c981a645478e0d0b8b078e40c9efb64aef6c940/doc/source/imgs/gui_shot.png -------------------------------------------------------------------------------- /doc/source/imgs/phaseScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AOtools/soapy/6c981a645478e0d0b8b078e40c9efb64aef6c940/doc/source/imgs/phaseScreen.png -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. pyAOS documentation master file, created by 2 | sphinx-quickstart on Thu Aug 14 09:37:17 2014. 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 Soapy's documentation! 7 | ================================= 8 | 9 | 10 | Contents: 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | intro.rst 16 | install.rst 17 | basicUsage.rst 18 | simpleTutorial.rst 19 | Configuration.rst 20 | dataSources.rst 21 | architecture.rst 22 | sim.rst 23 | atmos.rst 24 | lineofsight.rst 25 | wfs.rst 26 | dms.rst 27 | lgs.rst 28 | recon.rst 29 | sci.rst 30 | utils.rst 31 | 32 | 33 | 34 | 35 | 36 | Indices and tables 37 | ================== 38 | 39 | * :ref:`genindex` 40 | * :ref:`modindex` 41 | * :ref:`search` 42 | -------------------------------------------------------------------------------- /doc/source/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ************ 3 | 4 | Firstly, you'll need Python. This comes with pretty much every linux distribution and is also installed by default on Mac OS X. For Windows, I'd recommend using a Python distribution, such as anaconda or enthought canopy. The code should be compatible with Windows as Python and all the libraries used are platform independent. Nonetheless, I only use it on Mac OSX and Linux so I can't guarantee that there won't be bugs not present on other OSs. 5 | 6 | ============ 7 | Installation 8 | ============ 9 | Once all the requirements outlined below are met, you are ready to install Soapy. Download the source from `github `_, either as a zip file, or clone the git repository with:: 10 | 11 | git clone https://github.com/AOtools/soapy.git 12 | 13 | If downloading the code as a zip, you can choose which version to use with the drop down box on the left of the page, entitled ``branch:master``. Whilst I try not to, the master branch will occasionally be broken so you might want to get the latest stable version by clicking "tags" in the dropdown list, and selecting the most recent version number. 14 | 15 | Once the code is downloaded (and unzipped) or cloned, navigate to the resulting directory using the command line. You can import it into python straight away from this directory. To use the ``soapy`` script, run:: 16 | 17 | python soapy 18 | 19 | 20 | If you wish to have it available elsewhere on your system, either set the relavant ``PATH`` and ``PYTHONPATH`` variables to ``/bin`` and ``/`` respectively, or run the install script with:: 21 | 22 | python setup.py install 23 | 24 | This latter method may require superuser permissions for your system and should setup the paths for you. You should now be able to run ``soapy`` and import soapy into python from any directory on your system. 25 | 26 | ================== 27 | Required Libraries 28 | ================== 29 | 30 | Soapy doesn't have too many requirements in terms of external libraries, though it does rely on some. Performance of the simulation is made reasonable (for ELT scale operation) by using pyfftw and the numba library. Pyfftw simply wraps the FFTW library for fast fourier transforms. Numba, is a clever library that leverages the LLVM compiler infrastructure to compile python code directly to machine code. A library of functions has been written for the most computationally challenging algorithms, which are in pure python so can be easily read and improved, but operate quickly with the option of using multiple threads. There are also some optional libraries which are recommended for plotting. 31 | 32 | -------- 33 | Required 34 | -------- 35 | 36 | :: 37 | 38 | numpy 39 | scipy 40 | astropy 41 | pyfftw 42 | numba 43 | yaml 44 | aotools 45 | 46 | ------- 47 | For GUI 48 | ------- 49 | :: 50 | 51 | PyQt5 (PyQt4 supported) 52 | matplotlib 53 | ipython 54 | 55 | 56 | ===== 57 | Linux 58 | ===== 59 | If your starting with python from scratch, there a couple of options. For Ubuntu (14.04+) linux users, all these packages can be installed via apt-get:: 60 | 61 | sudo apt-get install python-numpy python-scipy python-fftw python-astropy python-qt4 python-matplotlib ipython ipython-qtconsole python-yaml python-numba 62 | pip install aotools 63 | 64 | 65 | for Red-hat based systems these packages should also be available from repositories, though I'm not sure of they're names. 66 | 67 | 68 | ======= 69 | Mac OSX 70 | ======= 71 | 72 | for mac os, all of these packages can be install via macports, with:: 73 | 74 | sudo port install python36 py36-numpy py36-scipy py36-astropy py36-pyqt5 py36-ipython py36-jupyter py36-numba py36-yaml py36-qtconsole 75 | pip install aotools 76 | 77 | `pyfftw `_ is not available for python3.6 on macports, so must be installed with another method, such as pip (see below) 78 | 79 | If you're using Python 2.7:: 80 | 81 | sudo port install python27 py27-numpy py27-scipy py27-astropy py27-pyfftw py27-pyqt5 py27-ipython py27-jupyter py27-numba py27-qtconsole py27-yaml 82 | pip install aotools 83 | 84 | 85 | ====== 86 | Any OS 87 | ====== 88 | 89 | --------------- 90 | Anaconda Python 91 | --------------- 92 | For any OS, including Windows, python distributions exist which include lots of python packages useful for science. 93 | A couple of good examples are Enthought Canopy (https://www.enthought.com), which is free for academics, and Anaconda (https://store.continuum.io/cshop/anaconda/) which is also free. 94 | Anaconda includes most of the required libraries by default apart from pyfftw and pyyaml. These can be installed with:: 95 | 96 | conda install pyyaml 97 | pip install pyfftw 98 | pip install aotools 99 | 100 | 101 | --- 102 | pip 103 | --- 104 | 105 | A lot of python packages are also listed on `pypi `_. Usually when python is installed, a script called ``easy_install`` is installed also, which can be used to get any package on pypi with ``easy_install ``. Confusingly, ``pip`` is now the recommended Python package manager instead of ``easy_install``. If you've only got ``easy_install`` you can install ``pip`` using ``easy_install pip``, or it can be installed using the script linked `here `_. 106 | 107 | Once you have ``pip``, the required libraries can be installed by using the ``requirements.txt`` file. From the soapy directory, just run (may need to be as ``sudo``):: 108 | 109 | pip install numpy scipy astropy pyfftw pyyaml numba aotools 110 | 111 | and all the requirements should be installed for the simulation, though not the GUI. For the GUI PyQt4 or PyQt5 is required, I dont think these are available from pip. 112 | 113 | Sometimes pyfftw has a hard time finding your installation of fftw to link against. On a Mac, these lines usually help before running the pip command:: 114 | 115 | export DYLIB_LIBRARY_PATH=$DYLIB_LIBRARY_PATH:/lib 116 | export LDFLAGS=-L/lib 117 | export CFLAGS=-I/include/ 118 | 119 | ======= 120 | Testing 121 | ======= 122 | Once you think everything is installed, tests can be run by navigating to the ``test`` directory and running:: 123 | 124 | python testSimulation.py 125 | 126 | Currently, this only runs system wide tests, but further, more atomic tests will be added in future. To run the tests, soapy must be either "installed", or manually put into the PYTHONPATH. 127 | -------------------------------------------------------------------------------- /doc/source/intro.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ************ 3 | 4 | Soapy is a Montecarlo Adaptive Optics (AO) simulation written exclusively in the Python programming language. It is aimed at rapidly developing new AO concepts and being a learning tool for those new to the field of AO. 5 | 6 | The code can be used as an end to end simulation, where the entire system parameters are controlled by a configuration file. This can be used from the Python command line, python scripts or a GUI which is included, operation is described in the :ref:`basicUsage` section. 7 | 8 | The codes real strength lies in its modular nature. Each AO component is modelled as a Python object, with intuitive methods and attributes. Components can be imported and used separately to create novel AO configurations. Starting with the main :ref:`simulation` module, these are described in detail in this documentation. 9 | 10 | 11 | Quick-Start 12 | ----------- 13 | 14 | Try out some of the code examples in the `conf` directory, either run the `soapy` script in `bin`, or load a python or IPython terminal:: 15 | 16 | import soapy 17 | sim = soapy.Sim("configFilename") 18 | sim.aoinit() 19 | sim.makeIMat() 20 | sim.aoloop() 21 | 22 | 23 | Data will now be saved in the directory specified as `filePrefix` in the configuration file. 24 | 25 | Alternatively, the GUI can be started with:: 26 | 27 | soapy -g 28 | 29 | The use the buttons to initialise the simulation, make interaction matrices and run the AO loop. The interactive python console can be used to view data or change parameters 30 | 31 | .. image:: imgs/gui_shot.png 32 | -------------------------------------------------------------------------------- /doc/source/lgs.rst: -------------------------------------------------------------------------------- 1 | Laser Guide Stars 2 | ================= 3 | 4 | Classes simulating Laser guide stars - usually contained by a `WFS` object. 5 | 6 | soapy.LGS module 7 | ---------------- 8 | 9 | .. automodule:: soapy.LGS 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: 13 | -------------------------------------------------------------------------------- /doc/source/lineofsight.rst: -------------------------------------------------------------------------------- 1 | Line Of Sight 2 | ============= 3 | 4 | 5 | soapy.lineofsight module 6 | ---------------- 7 | 8 | .. automodule:: soapy.lineofsight 9 | :members: 10 | :undoc-members: 11 | :show-inheritance: 12 | -------------------------------------------------------------------------------- /doc/source/modules.rst: -------------------------------------------------------------------------------- 1 | Soapy 2 | ===== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | soapy -------------------------------------------------------------------------------- /doc/source/recon.rst: -------------------------------------------------------------------------------- 1 | Reconstructors 2 | ============== 3 | 4 | Classes simulating AO reconstructors. 5 | 6 | soapy.RECON module 7 | ------------------ 8 | 9 | .. automodule:: soapy.RECON 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: -------------------------------------------------------------------------------- /doc/source/sci.rst: -------------------------------------------------------------------------------- 1 | Science Camera 2 | ============== 3 | 4 | A science camera class to measure system performance 5 | 6 | soapy.SCI module 7 | ---------------- 8 | 9 | .. automodule:: soapy.SCI 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: 13 | -------------------------------------------------------------------------------- /doc/source/sim.rst: -------------------------------------------------------------------------------- 1 | .. _simulation: 2 | 3 | Simulation 4 | ========== 5 | 6 | High level interface to run and examine a simulation 7 | 8 | .. automodule:: soapy.simulation 9 | :members: 10 | :undoc-members: 11 | :show-inheritance: 12 | -------------------------------------------------------------------------------- /doc/source/utils.rst: -------------------------------------------------------------------------------- 1 | Utilities 2 | ========= 3 | 4 | Modules containing some functions and classes commonly used throughout the simulation. 5 | 6 | soapy.logger module 7 | ------------------- 8 | 9 | .. automodule:: soapy.logger 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: 13 | 14 | soapy.AOFFT module 15 | ------------------ 16 | 17 | .. automodule:: soapy.AOFFT 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | soapy.aoSimLib module 23 | --------------------- 24 | 25 | .. automodule:: soapy.aoSimLib 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | 31 | soapy.opticalPropagationLib module 32 | ---------------------------------- 33 | 34 | .. automodule:: soapy.opticalPropagationLib 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | soapy.confParse module 40 | ---------------------- 41 | 42 | .. automodule:: soapy.confParse 43 | :noindex: 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: -------------------------------------------------------------------------------- /doc/source/wfs.rst: -------------------------------------------------------------------------------- 1 | ****************** 2 | Wave-front Sensors 3 | ****************** 4 | 5 | ========== 6 | WFS Module 7 | ========== 8 | .. automodule:: soapy.wfs.base 9 | 10 | Base WFS Class 11 | ============== 12 | 13 | .. autoclass:: soapy.wfs.wfs.WFS 14 | :members: 15 | 16 | .. autoclass:: soapy.wfs.shackhartmann.ShackHartmann 17 | :members: 18 | -------------------------------------------------------------------------------- /gui/AoWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 922 10 | 663 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ImageView 34 | QGraphicsView 35 |
pyqtgraph
36 |
37 |
38 | 39 | 40 |
41 | -------------------------------------------------------------------------------- /res/guiLUT.json: -------------------------------------------------------------------------------- 1 | {"ticks": [[0, [9, 13, 230, 255]], [0.17256637168141592, [11, 187, 190, 255]], [1, [255, 0, 0, 255]], [0.47345132743362833, [52, 187, 44, 255]], [0.7300884955752213, [199, 187, 13, 255]]], "mode": "rgb"} -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | 2 | # See the docstring in versioneer.py for instructions. Note that you must 3 | # re-run 'versioneer.py setup' after changing this section, and commit the 4 | # resulting files. 5 | 6 | [versioneer] 7 | VCS = git 8 | style = pep440 9 | versionfile_source = soapy/_version.py 10 | versionfile_build = soapy/_version.py 11 | tag_prefix = v 12 | parentdir_prefix = soapy- 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | import platform 3 | from setuptools import setup, find_packages 4 | import versioneer 5 | scripts = ["bin/soapy"] 6 | 7 | if (platform.system() == "Windows"): 8 | scripts.append("bin/soapy.bat") 9 | 10 | 11 | setup( 12 | name='soapy', 13 | author='Andrew P. Reeves', 14 | author_email='andrewpaulreeves@gmail.com', 15 | packages=find_packages(), 16 | scripts=scripts, 17 | description='A tomographic astronomical adaptive optics simulation with realistic laser guide star propagation.', 18 | long_description=open('README.md').read(), 19 | install_requires=[ 20 | "numpy >= 1.7.0", 21 | "scipy >= 0.15.0", 22 | "astropy >= 1.0", 23 | "aotools >= 1.0", 24 | "pyfftw >= 0.12.0", 25 | "pyyaml >= 5.1.1", 26 | "pyqtgraph >= 0.12.0", 27 | "numba >= 0.40" 28 | ], 29 | classifiers=[ 30 | "Programming Language :: Python", 31 | "Programming Language :: Python :: 3", 32 | "Programming Language :: Python :: 3.5", 33 | "Programming Language :: Python :: 3.6", 34 | "Programming Language :: Python :: 3.7", 35 | ], 36 | version=versioneer.get_version(), 37 | cmdclass=versioneer.get_cmdclass() 38 | ) 39 | -------------------------------------------------------------------------------- /soapy/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | #Copyright Durham University and Andrew Reeves 4 | #2015 5 | 6 | # This file is part of soapy. 7 | 8 | # soapy is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | 13 | # soapy is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | 18 | # You should have received a copy of the GNU General Public License 19 | # along with soapy. If not, see . 20 | 21 | import os 22 | import sys 23 | 24 | # Useful things to have in top namespace 25 | from .simulation import Sim, make_mask 26 | from .confParse import loadSoapyConfig 27 | from .atmosphere import makePhaseScreens 28 | 29 | # Compatability with older API 30 | from . import wfs as WFS 31 | from . import scienceinstrument as SCI 32 | 33 | #Try to import GUI, if not then its ok 34 | # Don't do this as it slows down importing for script or CLI use 35 | # try: 36 | # from . import gui 37 | # except: 38 | # pass 39 | 40 | from . import _version 41 | __version__ = _version.get_versions()['version'] 42 | -------------------------------------------------------------------------------- /soapy/gui/__init__.py: -------------------------------------------------------------------------------- 1 | from .gui import GUI, start_gui -------------------------------------------------------------------------------- /soapy/gui/jupyterconsolewidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtWidgets 2 | 3 | from qtconsole.rich_jupyter_widget import RichJupyterWidget as RichIPythonWidget 4 | from qtconsole.inprocess import QtInProcessKernelManager 5 | 6 | class JupyterConsoleWidget(QtWidgets.QWidget): 7 | def __init__(self): 8 | # Create an in-process kernel 9 | self.kernel_manager = QtInProcessKernelManager() 10 | self.kernel_manager.start_kernel() 11 | 12 | self.kernel = self.kernel_manager.kernel 13 | 14 | self.kernel_client = self.kernel_manager.client() 15 | self.kernel_client.start_channels() 16 | 17 | control = RichIPythonWidget() 18 | control.kernel_manager = self.kernel_manager 19 | control.kernel_client = self.kernel_client 20 | control.exit_requested.connect(self.stop) 21 | 22 | self.setCentralWidget(control) 23 | 24 | def stop(self): 25 | self.kernel_client.stop_channels() 26 | self.kernel_manager.shutdown_kernel() 27 | 28 | def write(self,message): 29 | self.kernel.shell.write(message) 30 | self.kernel.shell.ex("") 31 | 32 | 33 | 34 | if __name__ == "__main__": 35 | 36 | jcw = JupyterConsoleWidget() 37 | 38 | jcw.show() -------------------------------------------------------------------------------- /soapy/interp.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from scipy.interpolate import RectBivariateSpline, griddata 3 | 4 | def zoom(array, newSize, order=3): 5 | """ 6 | A Class to zoom 2-dimensional arrays using interpolation 7 | 8 | Uses the scipy `RectBivariateSpline` interpolation routine to zoom into an array. Can cope with real of complex data. 9 | 10 | Parameters: 11 | array (ndarray): 2-dimensional array to zoom 12 | newSize (tuple): the new size of the required array 13 | order (int, optional): Order of interpolation to use. default is 3 14 | 15 | Returns: 16 | ndarray : zoom array of new size. 17 | """ 18 | try: 19 | xSize = newSize[0] 20 | ySize = newSize[1] 21 | 22 | except (IndexError, TypeError): 23 | xSize = ySize = newSize 24 | 25 | coordsX = numpy.linspace(0, array.shape[0]-1, xSize) 26 | coordsY = numpy.linspace(0, array.shape[1]-1, ySize) 27 | 28 | #If array is complex must do 2 interpolations 29 | if array.dtype==numpy.complex64 or array.dtype==numpy.complex128: 30 | 31 | realInterpObj = RectBivariateSpline( 32 | numpy.arange(array.shape[0]), 33 | numpy.arange(array.shape[1]), 34 | array.real, 35 | kx=order, ky=order 36 | ) 37 | imagInterpObj = RectBivariateSpline( 38 | numpy.arange(array.shape[0]), 39 | numpy.arange(array.shape[1]), 40 | array.imag, 41 | kx=order, ky=order 42 | ) 43 | else: 44 | 45 | interpObj = RectBivariateSpline( 46 | numpy.arange(array.shape[0]), 47 | numpy.arange(array.shape[1]), 48 | array, 49 | kx=order, ky=order 50 | ) 51 | 52 | return interpObj(coordsY,coordsX) 53 | 54 | def zoom_rbs(array, newSize, order=3): 55 | """ 56 | A Class to zoom 2-dimensional arrays using RectBivariateSpline interpolation 57 | 58 | Uses the scipy ``RectBivariateSpline`` interpolation routine to zoom into an array. Can cope with real of complex data. May be slower than above ``zoom``, as RBS routine copies data. 59 | 60 | Parameters: 61 | array (ndarray): 2-dimensional array to zoom 62 | newSize (tuple): the new size of the required array 63 | order (int, optional): Order of interpolation to use. default is 3 64 | 65 | Returns: 66 | ndarray : zoom array of new size. 67 | """ 68 | try: 69 | xSize = int(newSize[0]) 70 | ySize = int(newSize[1]) 71 | 72 | except IndexError: 73 | xSize = ySize = int(newSize) 74 | 75 | coordsX = numpy.linspace(0, array.shape[0]-1, xSize) 76 | coordsY = numpy.linspace(0, array.shape[1]-1, ySize) 77 | 78 | #If array is complex must do 2 interpolations 79 | if array.dtype==numpy.complex64 or array.dtype==numpy.complex128: 80 | realInterpObj = RectBivariateSpline( 81 | numpy.arange(array.shape[0]), numpy.arange(array.shape[1]), 82 | array.real, kx=order, ky=order) 83 | imagInterpObj = RectBivariateSpline( 84 | numpy.arange(array.shape[0]), numpy.arange(array.shape[1]), 85 | array.imag, kx=order, ky=order) 86 | 87 | return (realInterpObj(coordsY,coordsX) 88 | + 1j*imagInterpObj(coordsY,coordsX)) 89 | 90 | else: 91 | 92 | interpObj = RectBivariateSpline( numpy.arange(array.shape[0]), 93 | numpy.arange(array.shape[1]), array, kx=order, ky=order) 94 | 95 | 96 | return interpObj(coordsY,coordsX) 97 | 98 | 99 | def binImgs(data, n): 100 | ''' 101 | Bins one or more images down by the given factor 102 | bins. n must be a factor of data.shape, who knows what happens 103 | otherwise...... 104 | 105 | Parameters: 106 | data (ndimage): 2 or 3d array of image(s) to bin 107 | n (int): binning factor 108 | 109 | Returns: 110 | binned image(s): ndarray 111 | ''' 112 | shape = numpy.array( data.shape ) 113 | 114 | n = int(numpy.round(n)) 115 | 116 | if len(data.shape)==2: 117 | shape[-1]/=n 118 | binnedImgTmp = numpy.zeros( shape, dtype=data.dtype ) 119 | for i in range(n): 120 | binnedImgTmp += data[:,i::n] 121 | shape[-2]/=n 122 | binnedImg = numpy.zeros( shape, dtype=data.dtype ) 123 | for i in range(n): 124 | binnedImg += binnedImgTmp[i::n,:] 125 | 126 | return binnedImg 127 | else: 128 | shape[-1]/=n 129 | binnedImgTmp = numpy.zeros ( shape, dtype=data.dtype ) 130 | for i in range(n): 131 | binnedImgTmp += data[...,i::n] 132 | 133 | shape[-2] /= n 134 | binnedImg = numpy.zeros( shape, dtype=data.dtype ) 135 | for i in range(n): 136 | binnedImg += binnedImgTmp[...,i::n,:] 137 | 138 | return binnedImg 139 | 140 | 141 | def zoomWithMissingData(data, newSize, 142 | method='linear', 143 | non_valid_value=numpy.nan): 144 | ''' 145 | Zoom 2-dimensional or 3D arrays using griddata interpolation. 146 | This allows interpolation over unstructured data, e.g. interpolating values 147 | inside a pupil but excluding everything outside. 148 | See also DM.CustomShapes. 149 | 150 | Note that it can be time consuming, particularly on 3D data 151 | 152 | Parameters 153 | ---------- 154 | data : ndArray 155 | 2d or 3d array. If 3d array, interpolate by slices of the first dim. 156 | newSize : tuple 157 | 2 value for the new array (or new slices) size. 158 | method: str 159 | 'linear', 'cubic', 'nearest' 160 | non_valid_value: float 161 | typically, NaN or 0. value in the array that are not valid for the 162 | interpolation. 163 | 164 | Returns 165 | ------- 166 | arr : ndarray 167 | of dimension (newSize[0], newSize[1]) or 168 | (data.shape[0], newSize[0], newSize[1]) 169 | ''' 170 | if len(data.shape) == 3: 171 | arr = data[0, :, :] 172 | else: 173 | assert len(data.shape) == 2 174 | arr = data 175 | 176 | Nx = arr.shape[0] 177 | Ny = arr.shape[1] 178 | coordX = (numpy.arange(Nx) - Nx / 2. + 0.5) / (Nx / 2.) 179 | coordY = (numpy.arange(Ny) - Ny / 2. + 0.5) / (Ny / 2.) 180 | Nx = newSize[0] 181 | Ny = newSize[1] 182 | ncoordX = (numpy.arange(Nx) - Nx / 2. + 0.5) / (Nx / 2.) 183 | ncoordY = (numpy.arange(Ny) - Ny / 2. + 0.5) / (Ny / 2.) 184 | 185 | x, y = numpy.meshgrid(coordX, coordY) 186 | xnew, ynew = numpy.meshgrid(ncoordX, ncoordY) 187 | 188 | if len(data.shape) == 2: 189 | idx = ~(arr == non_valid_value) 190 | znew = griddata((x[idx], y[idx]), arr[idx], (xnew, ynew), 191 | method=method) 192 | return znew 193 | elif len(data.shape) == 3: 194 | narr = numpy.zeros((data.shape[0], newSize[0], newSize[1])) 195 | for i in range(data.shape[0]): 196 | arr = data[i, :, :] 197 | idx = ~(arr == non_valid_value) 198 | znew = griddata((x[idx], y[idx]), arr[idx], (xnew, ynew), 199 | method=method) 200 | narr[i, :, :] = znew 201 | return narr 202 | -------------------------------------------------------------------------------- /soapy/logger.py: -------------------------------------------------------------------------------- 1 | #Copyright Durham University and Andrew Reeves 2 | #2014 3 | 4 | # This file is part of soapy. 5 | 6 | # soapy is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | 11 | # soapy is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | 16 | # You should have received a copy of the GNU General Public License 17 | # along with soapy. If not, see . 18 | """ 19 | A module to provide a common logging interface for all simulation code. 20 | 21 | Contains a ``Logger`` object, which can either, print information, save to file 22 | or both. The verbosity can also be adjusted between 0 and 3, where all is logged when verbosity is 3, debugging and warning information is logged when verbosity is 2, warnings logged when verbosity is 1 and nothing is logged when verbosity is 0. 23 | """ 24 | import inspect 25 | import sys 26 | try: 27 | import colorama 28 | colorama.init() 29 | RED = colorama.Fore.RED 30 | BLUE = colorama.Fore.BLUE 31 | GREEN = colorama.Fore.GREEN 32 | MAGENTA = colorama.Fore.MAGENTA 33 | COLOUR_RESET = colorama.Style.RESET_ALL 34 | except: 35 | COLOUR_RESET = RED = BLUE = GREEN = MAGENTA = "" 36 | 37 | COLOURS = {1 :RED, 2 :BLUE, 3:GREEN, 4:MAGENTA} 38 | 39 | 40 | 41 | LOGGING_LEVEL = 1 42 | LOGGING_FILE = None 43 | STATUS_FUNC = None 44 | 45 | def setLoggingLevel(level): 46 | """ 47 | sets which messages are printed from logger. 48 | 49 | if logging level is set to 0, nothing is printed. if set to 1, only 50 | warnings are printed. if set to 2, warnings and info is printed. if set 51 | to 3 detailed debugging info is printed. 52 | 53 | parameters: 54 | level (int): the desired logging level 55 | """ 56 | global LOGGING_LEVEL 57 | LOGGING_LEVEL = level 58 | 59 | def setLoggingFile(logFile): 60 | global LOGGING_FILE 61 | LOGGING_FILE = logFile 62 | 63 | def setStatusFunc(func): 64 | global STATUS_FUNC 65 | STATUS_FUNC = func 66 | 67 | 68 | def statusMessage(i, maxIter, message): 69 | if not STATUS_FUNC: 70 | sys.stdout.flush() 71 | sys.stdout.write(COLOURS[4]+"\r{0} of {1}: {2}".format(i+1,maxIter, message)+COLOUR_RESET) 72 | 73 | else: 74 | STATUS_FUNC(message, i, maxIter) 75 | 76 | if i+1==maxIter: 77 | if not STATUS_FUNC: 78 | sys.stdout.flush() 79 | sys.stdout.write("\n") 80 | 81 | def _printMessage(message, level=3): 82 | """ 83 | Internal function to add debug info to message and print 84 | 85 | Args: 86 | message(str): The message to log 87 | """ 88 | 89 | 90 | if LOGGING_LEVEL>=level: 91 | if LOGGING_LEVEL>2 or level==1: 92 | curframe = inspect.currentframe() 93 | calframe = inspect.getouterframes(curframe, 2) 94 | message = calframe[2][1].split("/")[-1]+" -> "+calframe[2][3] + ": " + message 95 | 96 | if LOGGING_FILE: 97 | with open(LOGGING_FILE, "a") as File: 98 | File.write(COLOURS[level]+message+"\n") 99 | 100 | if STATUS_FUNC: 101 | STATUS_FUNC(message) 102 | 103 | print(message) 104 | 105 | def print_(message): 106 | """ 107 | Always logs message, regardless of verbosity level 108 | 109 | Args: 110 | message(str): The message to log 111 | """ 112 | 113 | _printMessage(message) 114 | 115 | 116 | def info(message): 117 | """ 118 | Logs message if verbosity is 2 or higher. Useful for information which is not vital, but good to know. 119 | 120 | Args: 121 | message (string): The message to log 122 | """ 123 | 124 | _printMessage(message, 2) 125 | 126 | def debug(message): 127 | """ 128 | Logs messages if debug level is 3. Intended for very detailed debugging information. 129 | 130 | Args: 131 | message (string): The message to log 132 | """ 133 | _printMessage(message, 3) 134 | 135 | def warning(message): 136 | """ 137 | Logs messages if debug level is 1 or over. Intended for warnings 138 | 139 | Args: 140 | message (string): The message to log 141 | """ 142 | _printMessage(message,1) 143 | -------------------------------------------------------------------------------- /soapy/numbalib/__init__.py: -------------------------------------------------------------------------------- 1 | from .numbalib import * 2 | from . import wfslib -------------------------------------------------------------------------------- /soapy/numbalib/numbalib.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | N_CPU = multiprocessing.cpu_count() 3 | from threading import Thread 4 | 5 | # python3 has queue, python2 has Queue 6 | try: 7 | import queue 8 | except ImportError: 9 | import Queue as queue 10 | 11 | import numpy 12 | import numba 13 | 14 | def bilinear_interp(data, xCoords, yCoords, interpArray, bounds_check=True): 15 | """ 16 | A function which deals with numba interpolation. 17 | 18 | Parameters: 19 | array (ndarray): The 2-D array to interpolate 20 | xCoords (ndarray): A 1-D array of x-coordinates 21 | yCoords (ndarray): A 2-D array of y-coordinates 22 | interpArray (ndarray): The array to place the calculation 23 | bounds_check (bool, optional): Do bounds checkign in algorithm? Faster if False, but dangerous! Default is True 24 | Returns: 25 | interpArray (ndarray): A pointer to the calculated ``interpArray'' 26 | """ 27 | if bounds_check: 28 | bilinear_interp_numba(data, xCoords, yCoords, interpArray) 29 | else: 30 | bilinear_interp_numba_inbounds(data, xCoords, yCoords, interpArray) 31 | 32 | return interpArray 33 | 34 | 35 | 36 | 37 | @numba.jit(nopython=True, parallel=True) 38 | def bilinear_interp_numba(data, xCoords, yCoords, interpArray): 39 | """ 40 | 2-D interpolation using purely python - fast if compiled with numba 41 | This version also accepts a parameter specifying how much of the array 42 | to operate on. This is useful for multi-threading applications. 43 | 44 | Bounds are checks to ensure no out of bounds access is attempted to avoid 45 | mysterious seg-faults 46 | 47 | Parameters: 48 | array (ndarray): The 2-D array to interpolate 49 | xCoords (ndarray): A 1-D array of x-coordinates 50 | yCoords (ndarray): A 2-D array of y-coordinates 51 | interpArray (ndarray): The array to place the calculation 52 | 53 | Returns: 54 | interpArray (ndarray): A pointer to the calculated ``interpArray'' 55 | """ 56 | jRange = range(yCoords.shape[0]) 57 | for i in numba.prange(xCoords.shape[0]): 58 | x = xCoords[i] 59 | if x >= data.shape[0] - 1: 60 | x = data.shape[0] - 1 - 1e-9 61 | x1 = numba.int32(x) 62 | for j in jRange: 63 | y = yCoords[j] 64 | if y >= data.shape[1] - 1: 65 | y = data.shape[1] - 1 - 1e-9 66 | y1 = numba.int32(y) 67 | 68 | xGrad1 = data[x1 + 1, y1] - data[x1, y1] 69 | a1 = data[x1, y1] + xGrad1 * (x - x1) 70 | 71 | xGrad2 = data[x1 + 1, y1 + 1] - data[x1, y1 + 1] 72 | a2 = data[x1, y1 + 1] + xGrad2 * (x - x1) 73 | 74 | yGrad = a2 - a1 75 | interpArray[i, j] = a1 + yGrad * (y - y1) 76 | return interpArray 77 | 78 | 79 | @numba.jit(nopython=True, nogil=True) 80 | def bilinear_interp_numba_inbounds(data, xCoords, yCoords, interpArray): 81 | """ 82 | 2-D interpolation using purely python - fast if compiled with numba 83 | This version also accepts a parameter specifying how much of the array 84 | to operate on. This is useful for multi-threading applications. 85 | 86 | **NO BOUNDS CHECKS ARE PERFORMED - IF COORDS REFERENCE OUT-OF-BOUNDS 87 | ELEMENTS THEN MYSTERIOUS SEGFAULTS WILL OCCURR!!!** 88 | 89 | Parameters: 90 | array (ndarray): The 2-D array to interpolate 91 | xCoords (ndarray): A 1-D array of x-coordinates 92 | yCoords (ndarray): A 2-D array of y-coordinates 93 | interpArray (ndarray): The array to place the calculation 94 | 95 | Returns: 96 | interpArray (ndarray): A pointer to the calculated ``interpArray'' 97 | """ 98 | jRange = range(yCoords.shape[0]) 99 | for i in range(xCoords.shape[0]): 100 | x = xCoords[i] 101 | x1 = numba.int32(x) 102 | for j in jRange: 103 | y = yCoords[j] 104 | y1 = numba.int32(y) 105 | 106 | xGrad1 = data[x1 + 1, y1] - data[x1, y1] 107 | a1 = data[x1, y1] + xGrad1 * (x - x1) 108 | 109 | xGrad2 = data[x1 + 1, y1 + 1] - data[x1, y1 + 1] 110 | a2 = data[x1, y1 + 1] + xGrad2 * (x - x1) 111 | 112 | yGrad = a2 - a1 113 | interpArray[i, j] = a1 + yGrad * (y - y1) 114 | return interpArray 115 | 116 | 117 | @numba.jit(nopython=True, nogil=True) 118 | def rotate(data, interpArray, rotation_angle): 119 | for i in range(interpArray.shape[0]): 120 | for j in range(interpArray.shape[1]): 121 | 122 | i1 = i - (interpArray.shape[0] / 2. - 0.5) 123 | j1 = j - (interpArray.shape[1] / 2. - 0.5) 124 | x = i1 * numpy.cos(rotation_angle) - j1 * numpy.sin(rotation_angle) 125 | y = i1 * numpy.sin(rotation_angle) + j1 * numpy.cos(rotation_angle) 126 | 127 | x += data.shape[0] / 2. - 0.5 128 | y += data.shape[1] / 2. - 0.5 129 | 130 | if x >= data.shape[0] - 1: 131 | x = data.shape[0] - 1.1 132 | x1 = numpy.int32(x) 133 | 134 | if y >= data.shape[1] - 1: 135 | y = data.shape[1] - 1.1 136 | y1 = numpy.int32(y) 137 | 138 | xGrad1 = data[x1 + 1, y1] - data[x1, y1] 139 | a1 = data[x1, y1] + xGrad1 * (x - x1) 140 | 141 | xGrad2 = data[x1 + 1, y1 + 1] - data[x1, y1 + 1] 142 | a2 = data[x1, y1 + 1] + xGrad2 * (x - x1) 143 | 144 | yGrad = a2 - a1 145 | interpArray[i, j] = a1 + yGrad * (y - y1) 146 | return interpArray 147 | 148 | 149 | @numba.vectorize(["float32(complex64)"], nopython=True, target="parallel") 150 | def abs_squared(data): 151 | return abs(data)**2 152 | 153 | 154 | @numba.jit(nopython=True, parallel=True) 155 | def bin_img(imgs, bin_size, new_img): 156 | # loop over each element in new array 157 | for i in numba.prange(new_img.shape[0]): 158 | x1 = i * bin_size 159 | 160 | for j in range(new_img.shape[1]): 161 | y1 = j * bin_size 162 | new_img[i, j] = 0 163 | 164 | # loop over the values to sum 165 | for x in range(bin_size): 166 | for y in range(bin_size): 167 | new_img[i, j] += imgs[x1 + x, y1 + y] 168 | 169 | 170 | def bin_img_slow(img, bin_size, new_img): 171 | 172 | # loop over each element in new array 173 | for i in range(new_img.shape[0]): 174 | x1 = i * bin_size 175 | for j in range(new_img.shape[1]): 176 | y1 = j * bin_size 177 | new_img[i, j] = 0 178 | # loop over the values to sum 179 | for x in range(bin_size): 180 | for y in range(bin_size): 181 | new_img[i, j] += img[x + x1, y + y1] 182 | 183 | 184 | @numba.jit(nopython=True, parallel=True) 185 | def zoom(data, zoomArray): 186 | """ 187 | 2-D zoom interpolation using purely python - fast if compiled with numba. 188 | Both the array to zoom and the output array are required as arguments, the 189 | zoom level is calculated from the size of the new array. 190 | 191 | Parameters: 192 | array (ndarray): The 2-D array to zoom 193 | zoomArray (ndarray): The array to place the calculation 194 | 195 | Returns: 196 | interpArray (ndarray): A pointer to the calculated ``zoomArray'' 197 | """ 198 | for i in numba.prange(zoomArray.shape[0]): 199 | x = i*numba.float32(data.shape[0]-1) / (zoomArray.shape[0] - 0.99999999) 200 | x1 = numba.int32(x) 201 | for j in range(zoomArray.shape[1]): 202 | y = j*numba.float32(data.shape[1]-1) / (zoomArray.shape[1] - 0.99999999) 203 | y1 = numba.int32(y) 204 | 205 | xGrad1 = data[x1+1, y1] - data[x1, y1] 206 | a1 = data[x1, y1] + xGrad1*(x-x1) 207 | 208 | xGrad2 = data[x1+1, y1+1] - data[x1, y1+1] 209 | a2 = data[x1, y1+1] + xGrad2*(x-x1) 210 | 211 | yGrad = a2 - a1 212 | zoomArray[i,j] = a1 + yGrad*(y-y1) 213 | 214 | return zoomArray 215 | 216 | 217 | @numba.jit(nopython=True, parallel=True) 218 | def fftshift_2d_inplace(data): 219 | 220 | for x in numba.prange(data.shape[0]//2): 221 | for y in range(data.shape[1]//2): 222 | 223 | tmp = data[x, y] 224 | data[x, y] = data[x + data.shape[0]//2, y + data.shape[1]//2] 225 | data[x + data.shape[0]//2, y + data.shape[1]//2] = tmp 226 | 227 | tmp = data[x + data.shape[0]//2, y] 228 | data[x + data.shape[0]//2, y] = data[x, y + data.shape[1]//2] 229 | data[x, y + data.shape[1]//2] = tmp 230 | 231 | return data -------------------------------------------------------------------------------- /soapy/numbalib/wfslib.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | N_CPU = multiprocessing.cpu_count() 3 | from threading import Thread 4 | from . import numbalib 5 | 6 | import numpy 7 | import numba 8 | 9 | 10 | @numba.jit(nopython=True, parallel=True) 11 | def zoomtoefield(data, zoomArray): 12 | """ 13 | 2-D zoom interpolation using purely python - fast if compiled with numba. 14 | Both the array to zoom and the output array are required as arguments, the 15 | zoom level is calculated from the size of the new array. 16 | 17 | Parameters: 18 | array (ndarray): The 2-D array to zoom 19 | zoomArray (ndarray): The array to place the calculation 20 | 21 | Returns: 22 | interpArray (ndarray): A pointer to the calculated ``zoomArray'' 23 | """ 24 | 25 | for i in numba.prange(zoomArray.shape[0]): 26 | x = i * numba.float32(data.shape[0] - 1) / (zoomArray.shape[0] - 0.99999999) 27 | x1 = numba.int32(x) 28 | for j in range(zoomArray.shape[1]): 29 | y = j * numba.float32(data.shape[1] - 1) / (zoomArray.shape[1] - 0.99999999) 30 | y1 = numba.int32(y) 31 | 32 | xGrad1 = data[x1 + 1, y1] - data[x1, y1] 33 | a1 = data[x1, y1] + xGrad1 * (x - x1) 34 | 35 | xGrad2 = data[x1 + 1, y1 + 1] - data[x1, y1 + 1] 36 | a2 = data[x1, y1 + 1] + xGrad2 * (x - x1) 37 | 38 | yGrad = a2 - a1 39 | phase_value = (a1 + yGrad * (y - y1)) 40 | zoomArray[i, j] = numpy.exp(1j * phase_value) 41 | 42 | return zoomArray 43 | 44 | 45 | @numba.jit(nopython=True, parallel=True) 46 | def chop_subaps_mask(phase, subap_coords, nx_subap_size, subap_array, mask): 47 | for i in numba.prange(subap_coords.shape[0]): 48 | x1 = int(subap_coords[i, 0]) 49 | x2 = int(subap_coords[i, 0] + nx_subap_size) 50 | y1 = int(subap_coords[i, 1]) 51 | y2 = int(subap_coords[i, 1] + nx_subap_size) 52 | 53 | subap_array[i, :nx_subap_size, :nx_subap_size] = phase[x1: x2, y1: y2] * mask[x1: x2, y1: y2] 54 | 55 | return subap_array 56 | 57 | 58 | def chop_subaps_mask_slow(phase, subap_coords, nx_subap_size, subap_array, mask): 59 | for i in range(len(subap_coords)): 60 | x = int(subap_coords[i, 0]) 61 | y = int(subap_coords[i, 1]) 62 | 63 | subap_array[i, :nx_subap_size, :nx_subap_size] = phase[x: x + nx_subap_size, y: y + nx_subap_size] * mask[x: x + nx_subap_size, y: y + nx_subap_size] 64 | 65 | return subap_array 66 | 67 | 68 | @numba.jit(nopython=True, parallel=True) 69 | def chop_subaps(phase, subap_coords, nx_subap_size, subap_array): 70 | for i in numba.prange(subap_coords.shape[0]): 71 | x = int(subap_coords[i, 0]) 72 | y = int(subap_coords[i, 1]) 73 | 74 | subap_array[i, :nx_subap_size, :nx_subap_size] = phase[x:x + nx_subap_size, y:y + nx_subap_size] 75 | 76 | return subap_array 77 | 78 | 79 | def chop_subaps_slow(phase, subap_coords, nx_subap_size, subap_array, threads=None): 80 | for i in range(len(subap_coords)): 81 | x = int(subap_coords[i, 0]) 82 | y = int(subap_coords[i, 1]) 83 | 84 | subap_array[i, :nx_subap_size, :nx_subap_size] = phase[x: x + nx_subap_size, y: y + nx_subap_size] 85 | 86 | return subap_array 87 | 88 | 89 | 90 | @numba.jit(nopython=True, parallel=True) 91 | def chop_subaps_efield(phase, subap_coords, nx_subap_size, subap_array): 92 | for i in numpy.prange(subap_coords.shape[0]): 93 | x = int(subap_coords[i, 0]) 94 | y = int(subap_coords[i, 1]) 95 | 96 | subap_array[i, :nx_subap_size, :nx_subap_size] = numpy.exp(1j * phase[x:x + nx_subap_size, y:y + nx_subap_size]) 97 | 98 | return subap_array 99 | 100 | 101 | def chop_subaps_efield_slow(phase, subap_coords, nx_subap_size, subap_array, threads=None): 102 | for i in range(len(subap_coords)): 103 | x = int(subap_coords[i, 0]) 104 | y = int(subap_coords[i, 1]) 105 | 106 | subap_array[i, :nx_subap_size, :nx_subap_size] = numpy.exp( 107 | 1j * phase[x: x + nx_subap_size, y: y + nx_subap_size]) 108 | 109 | return subap_array 110 | 111 | 112 | @numba.jit(nopython=True) 113 | def place_subaps_on_detector(subap_imgs, detector_img, detector_positions, subap_coords): 114 | """ 115 | Puts a set of sub-apertures onto a detector image 116 | """ 117 | 118 | for i in range(subap_imgs.shape[0]): 119 | x1, x2, y1, y2 = detector_positions[i] 120 | sx1 ,sx2, sy1, sy2 = subap_coords[i] 121 | detector_img[x1: x2, y1: y2] += subap_imgs[i, sx1: sx2, sy1: sy2] 122 | 123 | return detector_img 124 | 125 | 126 | def place_subaps_on_detector_slow(subap_imgs, detector_img, subap_positions, threads=None): 127 | """ 128 | Puts a set of sub-apertures onto a detector image 129 | """ 130 | 131 | for i in range(subap_positions.shape[0]): 132 | x1, x2, y1, y2 = subap_positions[i] 133 | 134 | detector_img[x1: x2, y1: y2] = subap_imgs[i] 135 | 136 | return detector_img 137 | 138 | 139 | @numba.jit(nopython=True, parallel=True) 140 | def bin_imgs(imgs, bin_size, new_img): 141 | # loop over subaps 142 | for n in numba.prange(imgs.shape[0]): 143 | # loop over each element in new array 144 | for i in range(new_img.shape[1]): 145 | x1 = i * bin_size 146 | for j in range(new_img.shape[2]): 147 | y1 = j * bin_size 148 | new_img[n, i, j] = 0 149 | # loop over the values to sum 150 | for x in range(bin_size): 151 | for y in range(bin_size): 152 | new_img[n, i, j] += imgs[n, x + x1, y + y1] 153 | 154 | 155 | def bin_imgs_slow(imgs, bin_size, new_img): 156 | # loop over subaps 157 | for n in range(imgs.shape[0]): 158 | # loop over each element in new array 159 | for i in range(new_img.shape[1]): 160 | x1 = i * bin_size 161 | for j in range(new_img.shape[2]): 162 | y1 = j * bin_size 163 | new_img[n, i, j] = 0 164 | # loop over the values to sum 165 | for x in range(bin_size): 166 | for y in range(bin_size): 167 | new_img[n, i, j] += imgs[n, x + x1, y + y1] 168 | 169 | 170 | 171 | @numba.jit(nopython=True, parallel=True) 172 | def abs_squared_numba(data, output_data): 173 | for n in range(data.shape[0]): 174 | for x in range(data.shape[1]): 175 | for y in range(data.shape[2]): 176 | output_data[n, x, y] = data[n, x, y].real ** 2 + data[n, x, y].imag ** 2 177 | 178 | 179 | def abs_squared_slow(data, output_data, threads=None): 180 | for n in range(data.shape[0]): 181 | for x in range(data.shape[1]): 182 | for y in range(data.shape[2]): 183 | output_data[n, x, y] = data[n, x, y].real ** 2 + data[n, x, y].imag ** 2 184 | 185 | -------------------------------------------------------------------------------- /soapy/wfs/__init__.py: -------------------------------------------------------------------------------- 1 | from .wfs import * 2 | 3 | from . import shackhartmann, gradient, pyramid 4 | 5 | # for Compatability with older Soapy versions 6 | 7 | 8 | from .shackhartmann import ShackHartmann 9 | from .gradient import Gradient 10 | from .pyramid import Pyramid 11 | from .extendedshackhartmann import ExtendedSH 12 | from .shackhartmann_legacy import ShackHartmannLegacy 13 | from .zernike import Zernike 14 | -------------------------------------------------------------------------------- /soapy/wfs/extendedshackhartmann.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Shack Hartmann WFS for use with extended reference sources, such as solar AO, where correlation centroiding techniques are required. 3 | 4 | """ 5 | 6 | import numpy 7 | import scipy.signal 8 | 9 | try: 10 | from astropy.io import fits 11 | except ImportError: 12 | try: 13 | import pyfits as fits 14 | except ImportError: 15 | raise ImportError("PyAOS requires either pyfits or astropy") 16 | 17 | from aotools.image_processing import centroiders 18 | 19 | from .. import AOFFT, logger 20 | from . import shackhartmann 21 | 22 | # xrange now just "range" in python3. 23 | # Following code means fastest implementation used in 2 and 3 24 | try: 25 | xrange 26 | except NameError: 27 | xrange = range 28 | 29 | # The data type of data arrays (complex and real respectively) 30 | CDTYPE = numpy.complex64 31 | DTYPE = numpy.float32 32 | 33 | class ExtendedSH(shackhartmann.ShackHartmann): 34 | 35 | def calcInitParams(self): 36 | super(ExtendedSH, self).calcInitParams() 37 | 38 | # For correlation centroider, open reference image. 39 | self.referenceImage = self.wfsConfig.referenceImage 40 | 41 | def initFFTs(self): 42 | """ 43 | Initialise an extra FFT for the convolution in the correlation 44 | """ 45 | super(ExtendedSH, self).initFFTs() 46 | 47 | self.corrFFT = AOFFT.FFT( 48 | inputSize=( 49 | self.activeSubaps, self.wfsConfig.pxlsPerSubap, 50 | self.wfsConfig.pxlsPerSubap), 51 | axes=(-2,-1), mode="pyfftw",dtype=CDTYPE, 52 | THREADS=self.wfsConfig.fftwThreads, 53 | fftw_FLAGS=(self.wfsConfig.fftwFlag,"FFTW_DESTROY_INPUT"), 54 | ) 55 | 56 | self.corrIFFT = AOFFT.FFT( 57 | inputSize=( 58 | self.activeSubaps, self.wfsConfig.pxlsPerSubap, 59 | self.wfsConfig.pxlsPerSubap), 60 | axes=(-2,-1), mode="pyfftw",dtype=CDTYPE, 61 | THREADS=self.wfsConfig.fftwThreads, 62 | fftw_FLAGS=(self.wfsConfig.fftwFlag,"FFTW_DESTROY_INPUT"), 63 | direction="BACKWARD") 64 | 65 | # Also open object if its given 66 | self.extendedObject = self.wfsConfig.extendedObject 67 | 68 | def allocDataArrays(self): 69 | super(ExtendedSH, self).allocDataArrays() 70 | 71 | # Make a convolution object to apply the object 72 | if self.extendedObject is None: 73 | self.objectConv = None 74 | else: 75 | self.objectConv = AOFFT.Convolve( 76 | self.FPSubapArrays.shape, self.extendedObject.shape, 77 | threads=self.wfsConfig.fftwThreads, axes=(-2, -1) 78 | ) 79 | 80 | self.corrSubapArrays = numpy.zeros(self.centSubapArrays.shape, dtype=DTYPE) 81 | 82 | def integrateDetectorPlane(self): 83 | """ 84 | If an extended object is supplied, convolve with spots to make 85 | the detector images 86 | """ 87 | if self.extendedObject is not None: 88 | # Perform correlation to get subap images 89 | self.FPSubapArrays[:] = self.objectConv( 90 | self.FPSubapArrays, self.extendedObject).real 91 | 92 | # If sub-ap is oversized, apply field mask (TODO:make more general) 93 | if self.SUBAP_OVERSIZE!=1: 94 | coord = int(self.subapFFTPadding/(2*self.SUBAP_OVERSIZE)) 95 | fieldMask = numpy.zeros((self.subapFFTPadding,)*2) 96 | fieldMask[coord:-coord, coord:-coord] = 1 97 | 98 | self.FPSubapArrays *= fieldMask 99 | 100 | # Finally, run put these arrays onto the simulated detector 101 | super(ExtendedSH, self).integrateDetectorPlane() 102 | 103 | def makeCorrelationImgs(self): 104 | """ 105 | Use a convolution method to retrieve the 2d correlation peak between the subaperture and reference images. 106 | """ 107 | 108 | # Remove the min from each sub-ap to increase contrast 109 | self.centSubapArrays[:] = ( 110 | self.centSubapArrays.T-self.centSubapArrays.min((1,2))).T 111 | 112 | # Now do convolution 113 | # Get inverse FT of subaps 114 | # iCentSubapArrays = self.corrFFT(self.centSubapArrays) 115 | # 116 | # # Multiply by inverse of reference image FFT (made when set in property) 117 | # # Do FFT to get correlation 118 | # self.corrSubapArrays = self.corrIFFT( 119 | # iCentSubapArrays*self.iReferenceImage).real 120 | 121 | # for i, subap in enumerate(self.centSubapArrays): 122 | # self.corrSubapArrays[i] = scipy.signal.fftconvolve(subap, self.referenceImage[i], mode='same') 123 | 124 | if self.config.correlationFFTPad is None: 125 | subap_pad = self.centSubapArrays 126 | ref_pad = self.referenceImage 127 | else: 128 | PAD = round(0.5*(self.config.correlationFFTPad - self.config.pxlsPerSubap)) 129 | subap_pad = numpy.pad( 130 | self.centSubapArrays, mode='constant', 131 | pad_width=((0,0), (PAD, PAD), (PAD, PAD))) 132 | ref_pad = numpy.pad( 133 | self.referenceImage, mode='constant', 134 | pad_width=((0,0), (PAD, PAD), (PAD, PAD))) 135 | 136 | self.corrSubapArrays = numpy.fft.fftshift(numpy.fft.ifft2( 137 | numpy.fft.fft2(subap_pad, axes=(1,2)) * numpy.fft.fft2(ref_pad, axes=(1,2)))).real 138 | 139 | 140 | def calculateSlopes(self): 141 | ''' 142 | Calculates WFS slopes from wfsFocalPlane 143 | 144 | Returns: 145 | ndarray: array of all WFS measurements 146 | ''' 147 | 148 | # Sort out FP into subaps 149 | for i in xrange(self.activeSubaps): 150 | x, y = self.detectorSubapCoords[i] 151 | x = int(x) 152 | y = int(y) 153 | self.centSubapArrays[i] = self.wfsDetectorPlane[x:x+self.wfsConfig.pxlsPerSubap, 154 | y:y+self.wfsConfig.pxlsPerSubap ].astype(DTYPE) 155 | # If a reference image is supplied, use it for correlation centroiding 156 | if self.referenceImage is not None: 157 | self.makeCorrelationImgs() 158 | # Else: Just centroid on the extended image 159 | else: 160 | self.corrSubapArrays = self.FPSubapArrays 161 | 162 | slopes = eval("centroiders."+self.wfsConfig.centMethod)( 163 | self.corrSubapArrays, 164 | threshold=self.wfsConfig.centThreshold, 165 | ) 166 | 167 | # shift slopes relative to subap centre and remove static offsets 168 | slopes -= self.wfsConfig.pxlsPerSubap/2.0 169 | 170 | if numpy.any(self.staticData): 171 | slopes -= self.staticData 172 | 173 | self.slopes[:] = slopes.reshape(self.activeSubaps*2) 174 | 175 | if self.wfsConfig.removeTT == True: 176 | self.slopes[:self.activeSubaps] -= self.slopes[:self.activeSubaps].mean() 177 | self.slopes[self.activeSubaps:] -= self.slopes[self.activeSubaps:].mean() 178 | 179 | if self.wfsConfig.angleEquivNoise and not self.iMat: 180 | pxlEquivNoise = ( 181 | self.wfsConfig.angleEquivNoise * 182 | float(self.wfsConfig.pxlsPerSubap) 183 | /self.wfsConfig.subapFOV ) 184 | self.slopes += numpy.random.normal( 185 | 0, pxlEquivNoise, 2*self.activeSubaps) 186 | 187 | return self.slopes 188 | 189 | @property 190 | def referenceImage(self): 191 | """ 192 | A reference image to be used by a correlation centroider. 193 | """ 194 | return self._referenceImage 195 | 196 | @referenceImage.setter 197 | def referenceImage(self, referenceImage): 198 | if referenceImage is not None: 199 | # If given value is a string, assume a filename of fits file 200 | if isinstance(referenceImage, str): 201 | referenceImage = fits.getdata(referenceImage) 202 | 203 | # Shape of expected ref values 204 | refShape = ( 205 | self.activeSubaps, self.wfsConfig.pxlsPerSubap, 206 | self.wfsConfig.pxlsPerSubap) 207 | self._referenceImage = numpy.zeros(refShape) 208 | 209 | # if its an array of sub-aps, no work needed 210 | if referenceImage.shape == refShape: 211 | self._referenceImage = referenceImage 212 | 213 | # If its the size of a sub-ap, set all subaps to that value 214 | elif referenceImage.shape == (self.wfsConfig.pxlsPerSubap,)*2: 215 | # Make a placeholder for the reference image 216 | self._referenceImage = numpy.zeros( 217 | (self.activeSubaps, self.wfsConfig.pxlsPerSubap, 218 | self.wfsConfig.pxlsPerSubap)) 219 | self._referenceImage[:] = referenceImage 220 | 221 | # If its the size of the detector, assume its a tiled array of sub-aps 222 | elif referenceImage.shape == (self.detectorPxls,)*2: 223 | 224 | for i, (x, y) in enumerate(self.detectorSubapCoords): 225 | self._referenceImage[i] = referenceImage[ 226 | x:x+self.wfsConfig.pxlsPerSubap, 227 | y:y+self.wfsConfig.pxlsPerSubap] 228 | 229 | # Do the FFT of the reference image for the correlation 230 | self.iReferenceImage = numpy.fft.ifft2( 231 | self._referenceImage, axes=(1,2)) 232 | else: 233 | self._referenceImage = None 234 | 235 | @property 236 | def extendedObject(self): 237 | return self._extendedObject 238 | 239 | @extendedObject.setter 240 | def extendedObject(self, extendedObject): 241 | if extendedObject is not None: 242 | # If a string, assume a fits file 243 | if isinstance(extendedObject, str): 244 | extendedObject = fits.getdata(extendedObject) 245 | 246 | if extendedObject.shape!=(self.subapFFTPadding,)*2: 247 | raise ValueError("Shape of extended object must be ({}, {}). This is `pxlsPersubap * fftOversamp`".format(self.subapFFTPadding, self.subapFFTPadding)) 248 | 249 | self._extendedObject = extendedObject 250 | else: 251 | self._extendedObject = None 252 | -------------------------------------------------------------------------------- /soapy/wfs/gradient.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple WFS that directly measures the gradient of the wavefront across a number of 3 | pupil sub-apertures. Much faster to compute than a full SH simulation, though doesn't 4 | include the many experimental effects of such a WFS 5 | """ 6 | import numpy 7 | import numpy.random 8 | from scipy.interpolate import interp2d 9 | try: 10 | from astropy.io import fits 11 | except ImportError: 12 | try: 13 | import pyfits as fits 14 | except ImportError: 15 | raise ImportError("PyAOS requires either pyfits or astropy") 16 | 17 | import aotools 18 | 19 | from .. import AOFFT, LGS, logger 20 | from . import wfs 21 | 22 | # xrange now just "range" in python3. 23 | # Following code means fastest implementation used in 2 and 3 24 | try: 25 | xrange 26 | except NameError: 27 | xrange = range 28 | 29 | # The data type of data arrays (complex and real respectively) 30 | CDTYPE = numpy.complex64 31 | DTYPE = numpy.float32 32 | 33 | RAD2ASEC = 206264.849159 34 | ASEC2RAD = 1./RAD2ASEC 35 | 36 | class Gradient(wfs.WFS): 37 | """ 38 | The Grandient WFS class. 39 | 40 | Phase is propagated through turbulence through a "lineofsight", and is then split into a 41 | number of sub-apertures. The phase is then measured directly by multiplying element wise 42 | by a tip and tilt mode and calculating the sum. 43 | """ 44 | 45 | def calcInitParams(self): 46 | super(Gradient, self).calcInitParams() 47 | self.subapSpacing = self.pupil_size/self.wfsConfig.nxSubaps 48 | self.findActiveSubaps() 49 | 50 | # Normalise gradient measurement to 1 radian 51 | self.subapDiam = float(self.telescope_diameter) / self.wfsConfig.nxSubaps 52 | 53 | # Amp in m of 1 arcsecond tilt for single sub-aperture 54 | amp = self.telescope_diameter * 1. * ASEC2RAD 55 | 56 | # amp of 1" tilt in rads of the light 57 | amp *= ((2 * numpy.pi) / self.config.wavelength) 58 | 59 | # Arrays to be used for gradient calculation 60 | telCoord = numpy.linspace(0, amp, self.pupil_size) 61 | subapCoord = telCoord[:int(self.subapSpacing)] 62 | 63 | # Remove piston 64 | subapCoord -= subapCoord.mean() 65 | subapCoord *= -1 66 | 67 | self.xGrad_1, self.yGrad_1 = numpy.meshgrid(subapCoord, subapCoord) 68 | 69 | self.xGrad = self.xGrad_1/((self.xGrad_1**2).sum()) 70 | self.yGrad = self.yGrad_1/((self.yGrad_1**2).sum()) 71 | 72 | def findActiveSubaps(self): 73 | ''' 74 | Finds the subapertures which are not empty space 75 | determined if mean of subap coords of the mask is above threshold. 76 | ''' 77 | pupilMask = self.mask[ 78 | self.sim_pad : -self.sim_pad, 79 | self.sim_pad : -self.sim_pad 80 | ] 81 | self.subapCoords, self.subapFillFactor = aotools.wfs.findActiveSubaps( 82 | self.wfsConfig.nxSubaps, pupilMask, 83 | self.wfsConfig.subapThreshold, returnFill=True) 84 | 85 | self.activeSubaps = int(self.subapCoords.shape[0]) 86 | 87 | def allocDataArrays(self): 88 | """ 89 | Allocate the data arrays the WFS will require 90 | 91 | Determines and allocates the various arrays the WFS will require to 92 | avoid having to re-alloc memory during the running of the WFS and 93 | keep it fast. 94 | """ 95 | 96 | super(Gradient, self).allocDataArrays() 97 | 98 | self.subapArrays = numpy.zeros( 99 | (int(self.activeSubaps), int(self.subapSpacing), int(self.subapSpacing)), 100 | dtype=DTYPE) 101 | 102 | self.slopes = numpy.zeros(2 * self.activeSubaps) 103 | 104 | 105 | def calcFocalPlane(self, intensity=1): 106 | ''' 107 | Calculates the wfs focal plane, given the phase across the WFS. For this WFS, chops the pupil phase up into sub-apertures. 108 | 109 | Parameters: 110 | intensity (float): The relative intensity of this frame, is used when multiple WFS frames taken for extended sources. 111 | ''' 112 | 113 | # Apply the scaled pupil mask 114 | self.los.phase *= self.mask 115 | 116 | # Now cut out only the phase across the pupilSize 117 | coord = self.sim_pad 118 | self.pupilPhase = self.los.phase[coord:-coord, coord:-coord] 119 | 120 | # Create an array of individual subap phase 121 | for i, (x, y) in enumerate(self.subapCoords): 122 | x1 = int(round(x)) 123 | x2 = int(round(x + self.subapSpacing)) 124 | y1 = int(round(y)) 125 | y2 = int(round(y + self.subapSpacing)) 126 | self.subapArrays[i] = self.pupilPhase[x1: x2, y1: y2] 127 | 128 | 129 | def integrateDetectorPlane(self): 130 | ''' 131 | Creates a 'detector' image suitable for plotting 132 | ''' 133 | self.wfsDetectorPlane = numpy.zeros((self.wfsConfig.nxSubaps,)*2) 134 | 135 | coords = (self.subapCoords/self.subapSpacing).astype('int') 136 | self.wfsDetectorPlane[coords[:, 0], coords[:, 1]] = self.subapArrays.mean((1, 2)) 137 | 138 | def calculateSlopes(self): 139 | ''' 140 | Calculates WFS slopes from wfsFocalPlane 141 | 142 | Returns: 143 | ndarray: array of all WFS measurements 144 | ''' 145 | # Remove all piston from the sub-apertures 146 | # self.subapArrays = (self.subapArrays.T-self.subapArrays.mean((1,2))).T 147 | 148 | # Integrate with tilt/tip to get slope measurements 149 | for i, subap in enumerate(self.subapArrays): 150 | subap -= subap.mean() 151 | self.slopes[i] = (subap * self.xGrad).sum() 152 | self.slopes[i+self.activeSubaps] = (subap * self.yGrad).sum() 153 | 154 | # self.slopes[:self.activeSubaps] = self.xSlopes 155 | # self.slopes[self.activeSubaps:] = self.ySlopes 156 | 157 | # Remove tip-tilt if required 158 | if self.wfsConfig.removeTT == True: 159 | self.slopes[:self.activeSubaps] -= self.slopes[:self.activeSubaps].mean() 160 | self.slopes[self.activeSubaps:] -= self.slopes[self.activeSubaps:].mean() 161 | 162 | # Add 'angle equivalent noise' if asked for 163 | if self.wfsConfig.angleEquivNoise and not self.iMat: 164 | pxlEquivNoise = ( 165 | self.wfsConfig.angleEquivNoise * 166 | float(self.wfsConfig.pxlsPerSubap) 167 | /self.wfsConfig.subapFOV ) 168 | self.slopes += numpy.random.normal( 0, pxlEquivNoise, 169 | 2*self.activeSubaps) 170 | 171 | return self.slopes 172 | -------------------------------------------------------------------------------- /soapy/wfs/zernike.py: -------------------------------------------------------------------------------- 1 | """ 2 | A 'Zernike WFS' that can magically detect the Zernikes in the phase. 3 | 4 | Simply keeps an array of Zernikes that will be dotted wiht hte phase 5 | to calculate the Zernike coefficients. An imaginary simulated WFS that 6 | doesnt represent a physical device, just useful for testing purposes. 7 | """ 8 | import numpy 9 | 10 | import aotools 11 | 12 | from . import wfs 13 | 14 | 15 | class Zernike(wfs.WFS): 16 | 17 | def calcInitParams(self, phaseSize=None): 18 | # Call the case WFS methoc 19 | super(Zernike, self).calcInitParams(phaseSize=phaseSize) 20 | 21 | # Generate an array of Zernikes to use in the loop 22 | self.n_zerns = self.config.nxSubaps 23 | 24 | self.n_measurements = self.n_zerns 25 | 26 | # Make Zernike array 27 | self.zernike_array = aotools.zernikeArray(self.n_zerns, self.pupil_size) 28 | self.zernike_array.shape = self.n_zerns, self.pupil_size**2 29 | 30 | mask_sum = self.zernike_array[0].sum() 31 | 32 | # Scale to be 1rad in nm ## SHOULDNT NEED THIS AS PHASE ALREADY IN RAD 33 | # self.zernike_array *= ((2 * numpy.pi) / (10e9*self.config.wavelength)) 34 | 35 | # Normalise for hte number of points in the Zernike anlaysis 36 | self.zernike_array /= mask_sum 37 | 38 | 39 | def allocDataArrays(self): 40 | self.wfsDetectorPlane = numpy.zeros( 41 | (self.pupil_size, self.pupil_size), 42 | dtype=base.DTYPE 43 | ) 44 | 45 | def integrateDetectorPlane(self): 46 | # Cut out the phase from the oversized data 47 | sim_pad = (self.sim_size - self.pupil_size) // 2 48 | self.pupil_phase = self.los.phase[sim_pad: -sim_pad, sim_pad: -sim_pad] 49 | 50 | self.wfsDetectorPlane += self.pupil_phase 51 | 52 | def readDetectorPlane(self): 53 | self.detector_data = self.wfsDetectorPlane.copy() 54 | 55 | def calculateSlopes(self): 56 | 57 | 58 | z_coeffs = self.zernike_array.dot(self.detector_data.flatten()) 59 | 60 | self.slopes = z_coeffs 61 | 62 | 63 | 64 | def zeroData(self, detector=True, FP=True): 65 | super(Zernike, self).zeroData(detector, FP) 66 | if detector: 67 | self.wfsDetectorPlane[:] = 0 68 | -------------------------------------------------------------------------------- /test/infinite_phase_test.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot 2 | 3 | from soapy import atmosphere 4 | 5 | 6 | if __name__ == "__main__": 7 | 8 | nx_size = 32 9 | pixel_scale = 4/32 10 | r0 = 0.16 11 | L0 = 100. 12 | wind_speed = pixel_scale/2 13 | time_step = 1. 14 | wind_direction = 90 15 | 16 | phase_screen = atmosphere.InfinitePhaseScreen(nx_size, pixel_scale, r0, L0, wind_speed, time_step, wind_direction) 17 | 18 | fig = pyplot.figure() 19 | for i in range(100): 20 | screen = phase_screen.move_screen() 21 | 22 | pyplot.cla() 23 | pyplot.imshow(screen) 24 | pyplot.pause(0.001) 25 | 26 | -------------------------------------------------------------------------------- /test/testAtmos.py: -------------------------------------------------------------------------------- 1 | from soapy import confParse, atmosphere 2 | import unittest 3 | import numpy 4 | import os 5 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 6 | 7 | import shutil 8 | 9 | class TestAtmos(unittest.TestCase): 10 | 11 | def test_initAtmos(self): 12 | 13 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH,"sh_8x8.yaml")) 14 | 15 | atmos = atmosphere.atmos(config) 16 | 17 | def test_moveAtmos(self): 18 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH,"sh_8x8.yaml")) 19 | 20 | atmos = atmosphere.atmos(config) 21 | atmos.moveScrns() 22 | 23 | def test_randomAtmos(self): 24 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH,"sh_8x8.yaml")) 25 | 26 | atmos = atmosphere.atmos(config) 27 | atmos.randomScrns() 28 | 29 | def test_saveloadScrn(self): 30 | # test the saving and loading phase screens 31 | 32 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH,"sh_8x8.yaml")) 33 | config.atmos.wholeScrnSize = 512 34 | config.atmos.infinite = False 35 | atmos = atmosphere.atmos(config) 36 | 37 | # Make a directory to save the screens in 38 | if os.path.exists('testscrns'): 39 | shutil.rmtree('testscrns') 40 | 41 | os.makedirs('testscrns') 42 | atmos.saveScrns('testscrns') 43 | try: 44 | # change config to load scrns 45 | config.atmos.scrnNames = [] 46 | for i in range(config.atmos.scrnNo): 47 | config.atmos.scrnNames.append('testscrns/scrn{}.fits'.format(i)) 48 | 49 | atmos2 = atmosphere.atmos(config) 50 | 51 | # Check that all scrns are identical 52 | 53 | for i in range(config.atmos.scrnNo): 54 | assert numpy.allclose(atmos.wholeScrns[i], atmos2.wholeScrns[i]) 55 | 56 | # delete that dir 57 | shutil.rmtree('testscrns') 58 | 59 | except: 60 | # always delete that dir - even in case of failure 61 | shutil.rmtree('testscrns') 62 | raise -------------------------------------------------------------------------------- /test/testConf.py: -------------------------------------------------------------------------------- 1 | from soapy import confParse 2 | import unittest 3 | import os 4 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 5 | 6 | class TestConf(unittest.TestCase): 7 | 8 | def test_loadSh8x8_py(self): 9 | 10 | config = confParse.loadSoapyConfig( 11 | os.path.join(CONFIG_PATH, "py_config/sh_8x8.py")) 12 | 13 | def test_loadSh8x8_yaml(self): 14 | 15 | config = confParse.loadSoapyConfig( 16 | os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 17 | 18 | def testa_loadSh8x8_lgsElong_py(self): 19 | 20 | config = confParse.loadSoapyConfig( 21 | os.path.join(CONFIG_PATH, "py_config/sh_8x8_lgs-elongation.py")) 22 | 23 | 24 | def testa_loadSh8x8_lgsElong_yaml(self): 25 | 26 | config = confParse.loadSoapyConfig( 27 | os.path.join(CONFIG_PATH, "sh_8x8_lgs-elongation.yaml")) 28 | 29 | def testa_loadSh8x8_lgsUplink_py(self): 30 | 31 | config = confParse.loadSoapyConfig( 32 | os.path.join(CONFIG_PATH, "py_config/sh_8x8_lgs-uplink.py")) 33 | 34 | 35 | def testa_loadSh8x8_lgsUplink_yaml(self): 36 | 37 | config = confParse.loadSoapyConfig( 38 | os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 39 | 40 | def test_config_dict(self): 41 | 42 | config = confParse.loadSoapyConfig( 43 | os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 44 | config_dict = dict(config) 45 | -------------------------------------------------------------------------------- /test/testDM.py: -------------------------------------------------------------------------------- 1 | from soapy import confParse, DM, WFS 2 | import unittest 3 | import numpy 4 | import aotools 5 | import os 6 | import astropy.io.fits as fits 7 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 8 | 9 | def testa_initDM(): 10 | 11 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 12 | 13 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 14 | 15 | wfs = WFS.ShackHartmann(config, mask=mask) 16 | dm = DM.DM(config, wfss=[wfs], mask=mask) 17 | 18 | 19 | def testb_initPiezo(): 20 | 21 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 22 | 23 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 24 | 25 | wfs = WFS.ShackHartmann(config, mask=mask) 26 | dm = DM.Piezo(config, n_dm=1, wfss=[wfs], mask=mask) 27 | 28 | def testd_initGauss(): 29 | 30 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 31 | 32 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 33 | 34 | wfs = WFS.ShackHartmann(config, mask=mask) 35 | dm = DM.GaussStack(config, n_dm=1, wfss=[wfs], mask=mask) 36 | 37 | 38 | def testf_initTT(): 39 | 40 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 41 | 42 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 43 | 44 | wfs = WFS.ShackHartmann(config, mask=mask) 45 | dm = DM.TT(config, wfss=[wfs], mask=mask) 46 | 47 | def testf_initFastPiezo(): 48 | 49 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 50 | 51 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 52 | 53 | wfs = WFS.ShackHartmann(config, mask=mask) 54 | dm = DM.FastPiezo(config, n_dm=1, wfss=[wfs], mask=mask) 55 | 56 | 57 | def testg_initKL(): 58 | 59 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 60 | 61 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 62 | 63 | wfs = WFS.ShackHartmann(config, mask=mask) 64 | dm = DM.KarhunenLoeve(config, n_dm=1, wfss=[wfs], mask=mask) 65 | 66 | def testg_initCustomShape(): 67 | 68 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 69 | 70 | config.sim.pupilSize = 64 71 | config.sim.simSize = 66 72 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 73 | wfs = WFS.ShackHartmann(config, mask=mask) 74 | dm = DM.KarhunenLoeve(config, n_dm=1, wfss=[wfs], mask=mask) 75 | customShape = dm.iMatShapes 76 | 77 | # saving temporary shapes 78 | fname = os.path.dirname(os.path.abspath(__file__)) + '/tmp_CustomDmShapes.fits' 79 | fits.writeto(fname, customShape, overwrite=True) 80 | 81 | # change size to ensure it tests interpolation 82 | config.sim.pupilSize = 100 83 | config.sim.simSize = 104 84 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 85 | wfs = WFS.ShackHartmann(config, mask=mask) 86 | config.dms[1].dmShapesFilename = fname 87 | dm = DM.CustomShapes(config, n_dm=1, wfss=[wfs], mask=mask) 88 | # remove temporary shapes 89 | os.remove(fname) 90 | 91 | 92 | def test_set_valid_actuators(): 93 | """ 94 | Tests that when you set the "valid actuators", the DM computes how many valid actuators there are correctly 95 | """ 96 | 97 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 98 | 99 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 100 | 101 | wfs = WFS.ShackHartmann(config, mask=mask) 102 | dm = DM.DM(config, n_dm=1, wfss=[wfs], mask=mask) 103 | 104 | valid_actuators = numpy.ones(dm.n_acts, dtype=int) 105 | valid_actuators[0] = valid_actuators[-1] = 0 106 | 107 | dm.valid_actuators = valid_actuators 108 | 109 | assert dm.n_valid_actuators == (dm.n_acts - 2) 110 | 111 | def test_Piezo_valid_actuators(): 112 | """ 113 | Tests that when you set the "valid actuators", the DM doesn't use actuators marked 'invalid' 114 | """ 115 | 116 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 117 | 118 | mask = aotools.circle(config.sim.pupilSize / 2., config.sim.simSize) 119 | 120 | wfs = WFS.ShackHartmann(config, mask=mask) 121 | dm = DM.FastPiezo(config, n_dm=1, wfss=[wfs], mask=mask) 122 | 123 | act_coord1 = dm.valid_act_coords[0] 124 | act_coord_last = dm.valid_act_coords[-1] 125 | act_coord2 = dm.valid_act_coords[1] 126 | 127 | valid_actuators = numpy.ones(dm.n_acts, dtype=int) 128 | valid_actuators[0] = valid_actuators[-1] = 0 129 | 130 | dm.valid_actuators = valid_actuators 131 | 132 | assert dm.n_valid_actuators == (dm.n_acts - 2) 133 | assert not numpy.array_equal(dm.valid_act_coords[0], act_coord1) 134 | assert not numpy.array_equal(dm.valid_act_coords[-1], act_coord_last) 135 | assert numpy.array_equal(dm.valid_act_coords[0], act_coord2) 136 | -------------------------------------------------------------------------------- /test/testLOS.py: -------------------------------------------------------------------------------- 1 | from soapy import confParse, lineofsight 2 | import unittest 3 | import numpy 4 | import os 5 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 6 | 7 | class TestLOS(unittest.TestCase): 8 | 9 | def test_initLOS(self): 10 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 11 | 12 | los = lineofsight.LineOfSight(config.wfss[0], config) 13 | 14 | def test_runLOS(self): 15 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 16 | 17 | los = lineofsight.LineOfSight(config.wfss[0], config) 18 | 19 | testPhase = numpy.arange(config.sim.simSize**2).reshape( 20 | (config.sim.simSize,)*2) 21 | 22 | phs = los.frame(testPhase) 23 | -------------------------------------------------------------------------------- /test/testLgs.py: -------------------------------------------------------------------------------- 1 | from soapy import confParse, LGS 2 | import aotools 3 | import unittest 4 | import numpy 5 | import os 6 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 7 | 8 | class TestLgs(unittest.TestCase): 9 | 10 | def testa_initLgs(self): 11 | 12 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 13 | 14 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 15 | 16 | lgs = LGS.LGS(config.wfss[1], config) 17 | 18 | 19 | def testb_initGeoLgs(self): 20 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 21 | config.wfss[1].lgs.propagationMode = "Geometric" 22 | 23 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 24 | 25 | lgs = LGS.LGS_Geometric(config.wfss[1], config) 26 | 27 | def testc_geoLgsPsf(self): 28 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 29 | 30 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 31 | config.wfss[1].lgs.propagationMode = "Geometric" 32 | lgs = LGS.LGS_Geometric(config.wfss[1], config) 33 | psf = lgs.getLgsPsf( 34 | numpy.zeros((config.atmos.scrnNo, config.sim.simSize, config.sim.simSize))) 35 | 36 | def testd_initPhysLgs(self): 37 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 38 | config.wfss[1].lgs.propagationMode = "Physical" 39 | 40 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 41 | 42 | lgs = LGS.LGS_Physical(config.wfss[1], config) 43 | 44 | def teste_physLgsPsf(self): 45 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 46 | 47 | config.wfss[1].lgs.propagationMode = "Physical" 48 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 49 | 50 | lgs = LGS.LGS_Physical(config.wfss[1], config, nOutPxls=10) 51 | psf = lgs.getLgsPsf( 52 | numpy.zeros((config.atmos.scrnNo, config.sim.simSize, config.sim.simSize))) 53 | 54 | if __name__=="__main__": 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /test/testSci.py: -------------------------------------------------------------------------------- 1 | from soapy import confParse, scienceinstrument 2 | import aotools 3 | import unittest 4 | import numpy 5 | import os 6 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 7 | 8 | class TestSci(unittest.TestCase): 9 | 10 | def test_sciInit(self): 11 | 12 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 13 | 14 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 15 | 16 | sci = scienceinstrument.PSFCamera(config, 0, mask) 17 | 18 | def test_sciFrame(self): 19 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 20 | 21 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 22 | 23 | sci = scienceinstrument.PSFCamera(config, 0, mask) 24 | 25 | sci.frame(numpy.ones((config.atmos.scrnNo, config.sim.scrnSize, config.sim.scrnSize))) 26 | 27 | def test_sciStrehl(self): 28 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 29 | 30 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 31 | 32 | sci = scienceinstrument.PSFCamera( 33 | config, 0, mask) 34 | 35 | sci.frame(numpy.ones((config.atmos.scrnNo, config.sim.scrnSize, config.sim.scrnSize))) 36 | 37 | self.assertTrue(numpy.allclose(sci.instStrehl, 1.)) 38 | 39 | def test_fibreInit(self): 40 | 41 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 42 | 43 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 44 | 45 | sci = scienceinstrument.singleModeFibre(config, 0, mask) 46 | 47 | def test_fibreFrame(self): 48 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 49 | 50 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 51 | 52 | sci = scienceinstrument.singleModeFibre(config, 0, mask) 53 | sci.frame(numpy.ones((config.atmos.scrnNo, config.sim.scrnSize, config.sim.scrnSize))) 54 | 55 | 56 | if __name__=="__main__": 57 | unittest.main() 58 | -------------------------------------------------------------------------------- /test/testSimulation.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import soapy 4 | 5 | import numpy 6 | from astropy.io import fits 7 | 8 | import os 9 | import shutil 10 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 11 | 12 | soapy.logger.setLoggingLevel(3) 13 | 14 | RESULTS = { 15 | "8x8": 0.5, 16 | "8x8_open": 0.5, 17 | "8x8_offAxis": 0.22, 18 | "8x8_zernike": 0.36, 19 | "8x8_lgs" : 0.45, 20 | "8x8_phys": 0.50, 21 | "8x8_lgsuplink":0.35, 22 | } 23 | 24 | 25 | class TestSimpleSCAO(unittest.TestCase): 26 | 27 | def testOnAxis(self): 28 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 29 | sim.config.sim.simName = None 30 | sim.config.sim.logfile = None 31 | sim.config.sim.nIters = 100 32 | sim.config.wfss[0].GSPosition=(0,0) 33 | sim.config.atmos.randomSeed = 0 34 | 35 | sim.aoinit() 36 | 37 | sim.makeIMat(forceNew=True) 38 | 39 | sim.aoloop() 40 | 41 | #Check results are ok 42 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8"], atol=0.2) 43 | 44 | 45 | def testPhysProp(self): 46 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 47 | sim.config.sim.simName = None 48 | sim.config.sim.logfile = None 49 | sim.config.sim.nIters = 100 50 | sim.config.wfss[0].GSPosition=(0,0) 51 | sim.config.wfss[0].propagationMode="Physical" 52 | sim.config.atmos.randomSeed = 0 53 | 54 | sim.aoinit() 55 | 56 | sim.makeIMat(forceNew=True) 57 | 58 | sim.aoloop() 59 | 60 | #Check results are ok 61 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8_phys"], atol=0.2) 62 | 63 | 64 | def testOffAxis(self): 65 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 66 | sim.config.sim.simName = None 67 | sim.config.sim.logfile = None 68 | sim.config.sim.nIters = 100 69 | sim.config.wfss[0].GSPosition = (20,0) 70 | sim.config.atmos.randomSeed = 0 71 | 72 | sim.aoinit() 73 | 74 | sim.makeIMat(forceNew=True) 75 | 76 | sim.aoloop() 77 | 78 | #Check results are ok 79 | assert numpy.allclose( 80 | sim.longStrehl[0,-1], RESULTS["8x8_offAxis"], atol=0.2) 81 | 82 | 83 | def testZernikeDM(self): 84 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 85 | sim.config.sim.simName = None 86 | sim.config.sim.logfile = None 87 | sim.config.sim.nIters = 100 88 | sim.config.wfss[0].GSPosition = (0,0) 89 | sim.config.atmos.randomSeed = 0 90 | 91 | sim.config.sim.nDM = 1 92 | sim.config.dms[0].type = "Zernike" 93 | sim.config.dms[0].nxActuators = 45 94 | sim.config.dms[0].svdConditioning = 0.01 95 | sim.config.dms[0].iMatValue = 100 96 | 97 | sim.aoinit() 98 | 99 | sim.makeIMat(forceNew=True) 100 | 101 | sim.aoloop() 102 | 103 | #Check results are ok 104 | assert numpy.allclose( 105 | sim.longStrehl[0,-1], RESULTS["8x8_zernike"], atol=0.2) 106 | 107 | 108 | def testCone(self): 109 | 110 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8_lgs.yaml")) 111 | sim.config.sim.simName= None 112 | sim.config.sim.logfile = None 113 | sim.config.sim.nIters = 100 114 | sim.config.atmos.randomSeed = 0 115 | 116 | sim.aoinit() 117 | 118 | sim.makeIMat(forceNew=True) 119 | 120 | sim.aoloop() 121 | 122 | #Check results are ok 123 | assert numpy.allclose( 124 | sim.longStrehl[0,-1], RESULTS["8x8_lgs"], atol=0.2) 125 | 126 | 127 | def testOpenLoop(self): 128 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8_openloop.yaml")) 129 | sim.config.sim.simName = None 130 | sim.config.sim.logfile = None 131 | sim.config.sim.nIters = 100 132 | sim.config.wfss[0].GSPosition=(0,0) 133 | sim.config.atmos.randomSeed = 0 134 | 135 | for i in range(sim.config.sim.nDM): 136 | sim.config.dms[i].closed = False 137 | 138 | sim.aoinit() 139 | 140 | sim.makeIMat(forceNew=True) 141 | 142 | sim.aoloop() 143 | 144 | #Check results are ok 145 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8_open"], atol=0.2) 146 | 147 | 148 | def testLgsUplink_phys(self): 149 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 150 | sim.config.sim.simName = None 151 | sim.config.sim.logfile = None 152 | sim.config.sim.nIters = 100 153 | sim.config.wfss[0].GSPosition = (0, 0) 154 | sim.config.wfss[1].GSPosition = (0, 0) 155 | sim.config.wfss[1].lgs.propagationMode = "Physical" 156 | sim.config.atmos.randomSeed = 0 157 | sim.aoinit() 158 | 159 | sim.makeIMat(forceNew=True) 160 | 161 | sim.aoloop() 162 | 163 | #Check results are ok 164 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8_lgsuplink"], atol=0.2) 165 | 166 | 167 | def testLgsUplink_geo(self): 168 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 169 | sim.config.sim.simName = None 170 | sim.config.sim.logfile = None 171 | sim.config.sim.nIters = 100 172 | sim.config.wfss[0].GSPosition = (0, 0) 173 | sim.config.wfss[1].GSPosition = (0, 0) 174 | sim.config.wfss[1].lgs.propagationMode = "Geometric" 175 | sim.config.atmos.randomSeed = 0 176 | sim.aoinit() 177 | 178 | sim.makeIMat(forceNew=True) 179 | 180 | sim.aoloop() 181 | 182 | #Check results are ok 183 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8_lgsuplink"], atol=0.2) 184 | 185 | 186 | def testSaveData(self): 187 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 188 | sim.config.sim.simName = 'test_sh8x8' 189 | sim.config.sim.logfile = False 190 | 191 | sim.config.sim.saveSlopes = True 192 | sim.config.sim.saveDmCommands = True 193 | sim.config.sim.saveLgsPsf = True 194 | sim.config.sim.saveWfe = True 195 | sim.config.sim.saveStrehl = True 196 | sim.config.sim.saveSciPsf = True 197 | sim.config.sim.saveInstPsf = True 198 | sim.config.sim.saveCalib = True 199 | sim.config.sim.saveWfsFrames = True 200 | 201 | sim.config.sim.saveSciRes = False 202 | sim.config.sim.saveInstScieField = False 203 | 204 | sim.config.sim.nIters = 2 205 | wdir ='./' 206 | sim.aoinit() 207 | sim.makeIMat(forceNew=True) 208 | sim.aoloop() 209 | 210 | try: 211 | assert os.path.isfile(wdir + sim.path + '/slopes.fits') &\ 212 | os.path.isfile(wdir + sim.path + '/dmCommands.fits') &\ 213 | os.path.isfile(wdir + sim.path + '/lgsPsf.fits') &\ 214 | os.path.isfile(wdir + sim.path + '/WFE.fits') &\ 215 | os.path.isfile(wdir + sim.path + '/instStrehl.fits') &\ 216 | os.path.isfile(wdir + sim.path + '/longStrehl.fits') &\ 217 | os.path.isfile(wdir + sim.path + '/sciPsf_00.fits') &\ 218 | os.path.isfile(wdir + sim.path + '/sciPsfInst_00.fits') &\ 219 | os.path.isfile(wdir + sim.path + '/iMat.fits') &\ 220 | os.path.isfile(wdir + sim.path + '/cMat.fits') &\ 221 | os.path.isfile(wdir + sim.path + '/wfsFPFrames/wfs-0_frame-0.fits') 222 | # os.path.isfile(wdir + sim.path + '/sciResidual_00.fits') &\ 223 | # os.path.isfile(wdir + sim.path + '/scieFieldInst_00_real.fits') &\ 224 | # os.path.isfile(wdir + sim.path + '/scieFieldInst_00_imag.fits') &\ 225 | except: 226 | raise 227 | finally: 228 | dd = os.path.dirname(sim.path) 229 | shutil.rmtree(wdir + dd) 230 | 231 | 232 | def testMaskLoad(): 233 | 234 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 235 | sim.config.sim.simName = None 236 | sim.config.sim.logfile = None 237 | 238 | sim.aoinit() 239 | 240 | mask = numpy.ones((sim.config.sim.pupilSize, sim.config.sim.pupilSize)) 241 | 242 | # save mask 243 | if os.path.isfile('testmask.fits'): 244 | os.remove('testmask.fits') 245 | 246 | hdu = fits.PrimaryHDU(mask) 247 | hdulist = fits.HDUList([hdu]) 248 | hdulist.writeto('testmask.fits') 249 | hdulist.close() 250 | 251 | try: 252 | # attempt to load it 253 | sim.config.tel.mask = 'testmask.fits' 254 | sim.aoinit() 255 | 256 | # check its good 257 | p = sim.config.sim.simPad 258 | pad_mask = numpy.pad(mask, mode="constant", pad_width=((p,p),(p,p))) 259 | assert numpy.array_equal(sim.mask, pad_mask) 260 | except: 261 | raise 262 | finally: 263 | os.remove('testmask.fits') 264 | 265 | 266 | 267 | if __name__ == '__main__': 268 | unittest.main() 269 | -------------------------------------------------------------------------------- /test/testSimulationlegacy.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import soapy 4 | 5 | import numpy 6 | from astropy.io import fits 7 | 8 | import os 9 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 10 | 11 | soapy.logger.setLoggingLevel(3) 12 | 13 | RESULTS = { 14 | "8x8": 0.5, 15 | "8x8_open": 0.5, 16 | "8x8_offAxis": 0.22, 17 | "8x8_zernike": 0.36, 18 | "8x8_lgs" : 0.4, 19 | "8x8_phys": 0.53, 20 | "8x8_lgsuplink":0.35, 21 | } 22 | 23 | 24 | class TestSimpleSCAO(unittest.TestCase): 25 | 26 | def testOnAxis(self): 27 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 28 | sim.config.wfss[0].type = "ShackHartmannLegacy" 29 | sim.config.sim.simName = None 30 | sim.config.sim.logfile = None 31 | sim.config.sim.nIters = 100 32 | sim.config.wfss[0].GSPosition=(0,0) 33 | 34 | sim.aoinit() 35 | 36 | sim.makeIMat(forceNew=True) 37 | 38 | sim.aoloop() 39 | 40 | #Check results are ok 41 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8"], atol=0.2) 42 | 43 | def testPhysProp(self): 44 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 45 | sim.config.wfss[0].type = "ShackHartmannLegacy" 46 | 47 | sim.config.sim.simName = None 48 | sim.config.sim.logfile = None 49 | sim.config.sim.nIters = 100 50 | sim.config.wfss[0].GSPosition=(0,0) 51 | sim.config.wfss[0].propagationMode="Physical" 52 | 53 | sim.aoinit() 54 | 55 | sim.makeIMat(forceNew=True) 56 | 57 | sim.aoloop() 58 | 59 | #Check results are ok 60 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8_phys"], atol=0.2) 61 | 62 | def testOffAxis(self): 63 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 64 | sim.config.wfss[0].type = "ShackHartmannLegacy" 65 | 66 | sim.config.sim.simName = None 67 | sim.config.sim.logfile = None 68 | sim.config.sim.nIters = 100 69 | sim.config.wfss[0].GSPosition = (20,0) 70 | 71 | sim.aoinit() 72 | 73 | sim.makeIMat(forceNew=True) 74 | 75 | sim.aoloop() 76 | 77 | #Check results are ok 78 | assert numpy.allclose( 79 | sim.longStrehl[0,-1], RESULTS["8x8_offAxis"], atol=0.2) 80 | 81 | 82 | def testZernikeDM(self): 83 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 84 | sim.config.wfss[0].type = "ShackHartmannLegacy" 85 | 86 | sim.config.sim.simName = None 87 | sim.config.sim.logfile = None 88 | sim.config.sim.nIters = 100 89 | sim.config.wfss[0].GSPosition = (0,0) 90 | 91 | sim.config.sim.nDM = 1 92 | sim.config.dms[0].type = "Zernike" 93 | sim.config.dms[0].nxActuators = 45 94 | sim.config.dms[0].svdConditioning = 0.01 95 | sim.config.dms[0].iMatValue = 100 96 | 97 | sim.aoinit() 98 | 99 | sim.makeIMat(forceNew=True) 100 | 101 | sim.aoloop() 102 | 103 | #Check results are ok 104 | assert numpy.allclose( 105 | sim.longStrehl[0,-1], RESULTS["8x8_zernike"], atol=0.2) 106 | 107 | 108 | def testCone(self): 109 | 110 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8_lgs.yaml")) 111 | sim.config.wfss[0].type = "ShackHartmannLegacy" 112 | sim.config.wfss[1].type = "ShackHartmannLegacy" 113 | 114 | sim.config.sim.simName= None 115 | sim.config.sim.logfile = None 116 | sim.config.sim.nIters = 100 117 | 118 | sim.aoinit() 119 | print("WFS TYPE: {}".format(sim.wfss[0])) 120 | sim.makeIMat(forceNew=True) 121 | 122 | sim.aoloop() 123 | 124 | #Check results are ok 125 | assert numpy.allclose( 126 | sim.longStrehl[0,-1], RESULTS["8x8_lgs"], atol=0.2) 127 | 128 | def testOpenLoop(self): 129 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8_openloop.yaml")) 130 | sim.config.wfss[0].type = "ShackHartmannLegacy" 131 | 132 | sim.config.sim.simName = None 133 | sim.config.sim.logfile = None 134 | sim.config.sim.nIters = 100 135 | sim.config.wfss[0].GSPosition=(0,0) 136 | 137 | for i in range(sim.config.sim.nDM): 138 | sim.config.dms[i].closed = False 139 | 140 | sim.aoinit() 141 | 142 | sim.makeIMat(forceNew=True) 143 | 144 | sim.aoloop() 145 | 146 | #Check results are ok 147 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8_open"], atol=0.2) 148 | 149 | def testLgsUplink_phys(self): 150 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 151 | sim.config.wfss[0].type = "ShackHartmannLegacy" 152 | sim.config.wfss[1].type = "ShackHartmannLegacy" 153 | 154 | sim.config.sim.simName = None 155 | sim.config.sim.logfile = None 156 | sim.config.sim.nIters = 100 157 | sim.config.wfss[0].GSPosition = (0, 0) 158 | sim.config.wfss[1].GSPosition = (0, 0) 159 | sim.config.wfss[1].lgs.propagationMode = "Physical" 160 | sim.aoinit() 161 | 162 | sim.makeIMat(forceNew=True) 163 | 164 | sim.aoloop() 165 | 166 | #Check results are ok 167 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8_lgsuplink"], atol=0.2) 168 | 169 | def testLgsUplink_geo(self): 170 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8_lgs-uplink.yaml")) 171 | sim.config.wfss[0].type = "ShackHartmannLegacy" 172 | sim.config.wfss[1].type = "ShackHartmannLegacy" 173 | 174 | sim.config.sim.simName = None 175 | sim.config.sim.logfile = None 176 | sim.config.sim.nIters = 100 177 | sim.config.wfss[0].GSPosition = (0, 0) 178 | sim.config.wfss[1].GSPosition = (0, 0) 179 | sim.config.wfss[1].lgs.propagationMode = "Geometric" 180 | sim.aoinit() 181 | 182 | sim.makeIMat(forceNew=True) 183 | 184 | sim.aoloop() 185 | 186 | #Check results are ok 187 | assert numpy.allclose(sim.longStrehl[0,-1], RESULTS["8x8_lgsuplink"], atol=0.2) 188 | 189 | if __name__ == '__main__': 190 | unittest.main() 191 | -------------------------------------------------------------------------------- /test/testWfs.py: -------------------------------------------------------------------------------- 1 | from soapy import confParse, WFS 2 | import aotools 3 | import unittest 4 | import numpy 5 | import os 6 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 7 | 8 | class TestWfs(unittest.TestCase): 9 | 10 | def testa_initWfs(self): 11 | 12 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 13 | 14 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 15 | 16 | wfs = WFS.WFS(config, mask=mask) 17 | 18 | 19 | def testb_wfsFrame(self): 20 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 21 | 22 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 23 | 24 | wfs = WFS.WFS(config, mask=mask) 25 | 26 | wfs.frame(numpy.zeros((config.sim.simSize, config.sim.simSize))) 27 | 28 | 29 | def testc_initSHWfs(self): 30 | 31 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 32 | 33 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 34 | 35 | wfs = WFS.ShackHartmann(config, mask=mask) 36 | 37 | def testd_SHWfsFrame(self): 38 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 39 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 40 | 41 | wfs = WFS.ShackHartmann(config, mask=mask) 42 | 43 | wfs.frame(numpy.zeros((config.sim.simSize, config.sim.simSize))) 44 | 45 | def testc_initLegacySHWfs(self): 46 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 47 | 48 | mask = aotools.circle(config.sim.pupilSize / 2., config.sim.simSize) 49 | 50 | wfs = WFS.ShackHartmannLegacy(config, mask=mask) 51 | 52 | def testd_LegacySHWfsFrame(self): 53 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 54 | mask = aotools.circle(config.sim.pupilSize / 2., config.sim.simSize) 55 | 56 | wfs = WFS.ShackHartmannLegacy(config, mask=mask) 57 | 58 | wfs.frame(numpy.zeros((config.atmos.scrnNo, config.sim.simSize, config.sim.simSize))) 59 | 60 | def test_PhysWfs(self): 61 | 62 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 63 | config.wfss[0].propagationMode = "Physical" 64 | 65 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 66 | 67 | wfs = WFS.WFS(config, mask=mask) 68 | 69 | wfs.frame(numpy.zeros((config.atmos.scrnNo, config.sim.scrnSize, config.sim.scrnSize))) 70 | 71 | def testc_initGradWfs(self): 72 | 73 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 74 | config.sim.pupilSize = 10*config.wfss[0].nxSubaps 75 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 76 | 77 | wfs = WFS.Gradient(config, mask=mask) 78 | 79 | def testd_GradWfsFrame(self): 80 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 81 | config.sim.pupilSize = 10*config.wfss[0].nxSubaps 82 | 83 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 84 | 85 | wfs = WFS.Gradient(config, mask=mask) 86 | 87 | wfs.frame(numpy.zeros((config.sim.simSize, config.sim.simSize))) 88 | 89 | def teste_initPyrWfs(self): 90 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "pwfs.yaml")) 91 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 92 | 93 | wfs = WFS.Pyramid(config, mask=mask) 94 | 95 | def testf_PyrWfsFrame(self): 96 | config = confParse.loadSoapyConfig(os.path.join(CONFIG_PATH, "pwfs.yaml")) 97 | mask = aotools.circle(config.sim.pupilSize/2., config.sim.simSize) 98 | 99 | wfs = WFS.Pyramid(config, mask=mask) 100 | 101 | wfs.frame(numpy.zeros((config.sim.simSize, config.sim.simSize))) 102 | 103 | 104 | if __name__=="__main__": 105 | unittest.main() 106 | -------------------------------------------------------------------------------- /test/test_infinitephasescreen.py: -------------------------------------------------------------------------------- 1 | import time 2 | from matplotlib import pyplot 3 | 4 | # import tqdm 5 | from soapy import atmosphere 6 | 7 | 8 | 9 | if __name__ == "__main__": 10 | 11 | nx_size = 400 12 | pixel_scale = 8./128 13 | r0 = 0.16 14 | L0 = 100. 15 | wind_speed = pixel_scale/2 16 | time_step = 1. 17 | wind_direction = 0 18 | 19 | N_iters = 1000 20 | 21 | print("initialising phase screen...") 22 | phase_screen = atmosphere.InfinitePhaseScreen(nx_size, pixel_scale, r0, L0, wind_speed, time_step, wind_direction) 23 | print("Done!") 24 | 25 | fig = pyplot.figure() 26 | 27 | t1 = time.time() 28 | for i in range(N_iters): 29 | screen = phase_screen.move_screen() 30 | t2 = time.time() 31 | 32 | elapsed = t2 - t1 33 | itspersec = N_iters/elapsed 34 | 35 | print("Iterations per second: {} it/s".format(itspersec)) -------------------------------------------------------------------------------- /test/test_numbalib.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import scipy.ndimage 3 | import aotools 4 | 5 | from soapy import numbalib 6 | 7 | 8 | def test_zoom(): 9 | 10 | NX_DATA_SHAPE = 10 11 | ZOOM_FACTOR = 5 12 | 13 | input_data = numpy.arange(NX_DATA_SHAPE**2) 14 | input_data.resize(NX_DATA_SHAPE, NX_DATA_SHAPE) 15 | input_data = input_data.astype("float32") 16 | 17 | # reference zoom using scipy 18 | zoom_data = scipy.ndimage.zoom( 19 | input_data, (ZOOM_FACTOR, ZOOM_FACTOR), order=1) 20 | 21 | # numbalib zoom to test 22 | zoom_data2 = numpy.zeros((ZOOM_FACTOR * NX_DATA_SHAPE, ) * 2).astype(input_data.dtype) 23 | numbalib.zoom(input_data, zoom_data2) 24 | 25 | assert numpy.allclose(zoom_data, zoom_data2) 26 | 27 | 28 | def test_zoomtoefield(): 29 | """ 30 | Checks that when zooming to efield, the same result is found as when zooming 31 | then using numpy.exp to get efield. 32 | """ 33 | input_data = numpy.arange(100).reshape(10,10).astype("float32") 34 | 35 | output_data = numpy.zeros((100, 100), dtype="float32") 36 | output_efield2 = numpy.zeros((100, 100), dtype="complex64") 37 | 38 | numbalib.zoom(input_data, output_data) 39 | 40 | output_efield1 = numpy.exp(1j * output_data) 41 | 42 | numbalib.wfslib.zoomtoefield(input_data, output_efield2) 43 | 44 | assert numpy.allclose(output_efield1, output_efield2) 45 | 46 | 47 | def test_chop_subaps_mask(): 48 | """ 49 | Tests that the numba routing chops phase into sub-apertures in the same way 50 | as using numpy indices 51 | """ 52 | nx_phase = 12 53 | nx_subap_size = 3 54 | nx_subaps = nx_phase // nx_subap_size 55 | 56 | phase = (numpy.random.random((nx_phase, nx_phase)) 57 | + 1j * numpy.random.random((nx_phase, nx_phase)) 58 | ).astype("complex64") 59 | subap_array = numpy.zeros((nx_subaps * nx_subaps, nx_subap_size, nx_subap_size)).astype("complex64") 60 | numpy_subap_array = subap_array.copy() 61 | 62 | mask = aotools.circle(nx_phase/2., nx_phase) 63 | 64 | x_coords, y_coords = numpy.meshgrid( 65 | numpy.arange(0, nx_phase, nx_subap_size), 66 | numpy.arange(0, nx_phase, nx_subap_size)) 67 | subap_coords = numpy.array([x_coords.flatten(), y_coords.flatten()]).T 68 | 69 | numpy_chop(phase, subap_coords, nx_subap_size, numpy_subap_array, mask) 70 | numbalib.wfslib.chop_subaps_mask( 71 | phase, subap_coords, nx_subap_size, subap_array, mask) 72 | assert numpy.array_equal(numpy_subap_array, subap_array) 73 | 74 | 75 | def numpy_chop(phase, subap_coords, nx_subap_size, subap_array, mask): 76 | """ 77 | Numpy vesion of chop subaps tests 78 | """ 79 | mask_phase = mask * phase 80 | for n, (x, y) in enumerate(subap_coords): 81 | subap_array[n] = mask_phase[ 82 | x: x + nx_subap_size, 83 | y: y + nx_subap_size 84 | ] 85 | return subap_array 86 | 87 | 88 | def test_abs_squared(): 89 | """ 90 | Tests that the numba vectorised and parallelised abs squared gives the same result as numpy 91 | """ 92 | data = (numpy.random.random((100, 20, 20)) 93 | + 1j * numpy.random.random((100, 20, 20))).astype("complex64") 94 | 95 | output_data = numpy.zeros((100, 20, 20), dtype="float32") 96 | 97 | numbalib.abs_squared(data, out=output_data) 98 | 99 | assert numpy.array_equal(output_data, numpy.abs(data)**2) 100 | 101 | 102 | def test_place_subaps_detector(): 103 | 104 | nx_subaps = 4 105 | pxls_per_subap = 4 106 | tot_pxls_per_subap = 2 * pxls_per_subap # More for total FOV 107 | tot_subaps = nx_subaps * nx_subaps 108 | nx_pxls = nx_subaps * pxls_per_subap 109 | 110 | detector = numpy.zeros((nx_pxls, nx_pxls)) 111 | detector_numpy = detector.copy() 112 | 113 | subaps = numpy.random.random((tot_subaps, tot_pxls_per_subap, tot_pxls_per_subap)) 114 | # Find the coordinates of the vertices of the subap on teh detector 115 | detector_coords = [] 116 | subap_coords = [] 117 | for ix in range(nx_subaps): 118 | x1 = ix * pxls_per_subap - pxls_per_subap/2 119 | x2 = (ix + 1) * pxls_per_subap + pxls_per_subap/2 120 | sx1 = 0 121 | sx2 = tot_pxls_per_subap 122 | for iy in range(nx_subaps): 123 | y1 = iy * pxls_per_subap - pxls_per_subap/2 124 | y2 = (iy + 1) * pxls_per_subap + pxls_per_subap/2 125 | 126 | sy1 = 0 127 | sy2 = tot_pxls_per_subap 128 | # Check for edge subaps that would be out of bounds 129 | if x1 < 0: 130 | x1 = 0 131 | sx1 = tot_pxls_per_subap / 4 132 | if x2 > nx_pxls: 133 | x2 = nx_pxls 134 | sx2 = 3 * tot_pxls_per_subap / 4 135 | 136 | if y1 < 0: 137 | y1 = 0 138 | sy1 = tot_pxls_per_subap / 4 139 | if y2 > nx_pxls: 140 | y2 = nx_pxls 141 | sy2 = 3 * tot_pxls_per_subap / 4 142 | 143 | detector_coords.append(numpy.array([x1, x2, y1, y2])) 144 | subap_coords.append(numpy.array([sx1, sx2, sy1, sy2])) 145 | 146 | detector_coords = numpy.array(detector_coords).astype("int") 147 | subap_coords = numpy.array(subap_coords).astype("int") 148 | 149 | numbalib.wfslib.place_subaps_on_detector( 150 | subaps, detector, detector_coords, subap_coords) 151 | 152 | numpy_place_subaps(subaps, detector_numpy, detector_coords, subap_coords) 153 | 154 | assert numpy.array_equal(detector, detector_numpy) 155 | 156 | def numpy_place_subaps( subap_arrays, detector, detector_subap_coords, valid_subap_coords): 157 | 158 | for n, (x1, x2, y1, y2) in enumerate(detector_subap_coords): 159 | sx1, sx2, sy1, sy2 = valid_subap_coords[n] 160 | subap = subap_arrays[n] 161 | detector[x1: x2, y1: y2] += subap[sx1: sx2, sy1: sy2] 162 | 163 | return detector 164 | 165 | 166 | def test_fftshift_2d(): 167 | 168 | data = numpy.arange(100).reshape(10,10) 169 | 170 | npy_shift = numpy.fft.fftshift(data) 171 | 172 | data_shift = numbalib.fftshift_2d_inplace(data.copy()) 173 | 174 | assert numpy.array_equal(data_shift, npy_shift) 175 | 176 | 177 | 178 | 179 | 180 | if __name__ == "__main__": 181 | test_zoomtoefield() -------------------------------------------------------------------------------- /test/test_reconstructors.py: -------------------------------------------------------------------------------- 1 | import soapy 2 | import numpy 3 | import os 4 | from astropy.io import fits 5 | CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../conf/") 6 | 7 | import shutil 8 | 9 | def test_save_imat(): 10 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 11 | sim.config.sim.simName = "test_sim" 12 | sim.config.sim.logfile = None 13 | 14 | # 1 scrn for fast init 15 | sim.config.atmos.scrnNo = 1 16 | try: 17 | sim.aoinit() 18 | 19 | recon = sim.recon 20 | 21 | recon.makeIMat() 22 | recon.save_interaction_matrix() 23 | 24 | imat_filename = "test_sim/iMat.fits" 25 | 26 | imat = fits.getdata(imat_filename) 27 | 28 | assert (numpy.array_equal(imat, sim.recon.interaction_matrix)) 29 | 30 | finally: 31 | shutil.rmtree("test_sim") 32 | 33 | 34 | 35 | 36 | def test_load_imat(): 37 | sim = soapy.Sim(os.path.join(CONFIG_PATH, "sh_8x8.yaml")) 38 | sim.config.sim.simName = "test_sim" 39 | sim.config.sim.logfile = None 40 | 41 | # 1 scrn for fast init 42 | sim.config.atmos.scrnNo = 1 43 | 44 | try: 45 | 46 | sim.aoinit() 47 | 48 | recon = sim.recon 49 | 50 | # Make an imat 51 | recon.makeIMat() 52 | 53 | # Save it for later 54 | recon.save_interaction_matrix() 55 | imat = recon.interaction_matrix.copy() 56 | 57 | # Set the internat soapy imat to 0 58 | recon.interaction_matrix[:] = 0 59 | 60 | # And attempt to load saved one 61 | recon.load_interaction_matrix() 62 | 63 | # Ensure its been loaded as expected 64 | assert (numpy.array_equal(imat, recon.interaction_matrix)) 65 | 66 | finally: 67 | shutil.rmtree("test_sim") --------------------------------------------------------------------------------