tomoAO 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tomoao-0.0.1/LICENSE +21 -0
- tomoao-0.0.1/PKG-INFO +55 -0
- tomoao-0.0.1/README.md +32 -0
- tomoao-0.0.1/setup.cfg +4 -0
- tomoao-0.0.1/setup.py +45 -0
- tomoao-0.0.1/tomoAO/IO/__init__.py +1 -0
- tomoao-0.0.1/tomoAO/IO/io_functions.py +47 -0
- tomoao-0.0.1/tomoAO/Reconstruction/__init__.py +1 -0
- tomoao-0.0.1/tomoAO/Reconstruction/reconClassType.py +297 -0
- tomoao-0.0.1/tomoAO/Reconstruction/recontructors.py +114 -0
- tomoao-0.0.1/tomoAO/Simulation/__init__.py +1 -0
- tomoao-0.0.1/tomoAO/Simulation/aoSystem.py +170 -0
- tomoao-0.0.1/tomoAO/__init__.py +6 -0
- tomoao-0.0.1/tomoAO/tools/__init__.py +0 -0
- tomoao-0.0.1/tomoAO/tools/tomography_tools.py +294 -0
- tomoao-0.0.1/tomoAO/version.py +1 -0
- tomoao-0.0.1/tomoAO.egg-info/PKG-INFO +55 -0
- tomoao-0.0.1/tomoAO.egg-info/SOURCES.txt +19 -0
- tomoao-0.0.1/tomoAO.egg-info/dependency_links.txt +1 -0
- tomoao-0.0.1/tomoAO.egg-info/requires.txt +3 -0
- tomoao-0.0.1/tomoAO.egg-info/top_level.txt +1 -0
tomoao-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 cmcorreia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tomoao-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tomoAO
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python package with atmospheric tomography generic functions and tools
|
|
5
|
+
Home-page: https://github.com/cmcorreia/PyTomo
|
|
6
|
+
Author: SpaceODT
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: aotools
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Requires-Dist: astropy
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# tomoAO
|
|
29
|
+
Python package with atmospheric tomography generic functions and tools
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
1. Install OOPAO
|
|
34
|
+
```sh
|
|
35
|
+
pip install git+https://github.com/cheritier/OOPAO
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. Install cupy according to your cuda version (Optional)
|
|
39
|
+
```sh
|
|
40
|
+
pip install cupy-cuda11x
|
|
41
|
+
```
|
|
42
|
+
```sh
|
|
43
|
+
pip install cupy-cuda12x
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. Install tomoAO via pip
|
|
47
|
+
```sh
|
|
48
|
+
pip install tomoAO
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or clone the repository
|
|
52
|
+
```sh
|
|
53
|
+
git clone https://github.com/cmcorreia/PyTomo
|
|
54
|
+
pip install -e 'path to tomoAO'
|
|
55
|
+
```
|
tomoao-0.0.1/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# tomoAO
|
|
2
|
+
Python package with atmospheric tomography generic functions and tools
|
|
3
|
+
|
|
4
|
+
## Installation
|
|
5
|
+
|
|
6
|
+
1. Install OOPAO
|
|
7
|
+
```sh
|
|
8
|
+
pip install git+https://github.com/cheritier/OOPAO
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
2. Install cupy according to your cuda version (Optional)
|
|
12
|
+
```sh
|
|
13
|
+
pip install cupy-cuda11x
|
|
14
|
+
```
|
|
15
|
+
```sh
|
|
16
|
+
pip install cupy-cuda12x
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
3. Install tomoAO via pip
|
|
20
|
+
```sh
|
|
21
|
+
pip install tomoAO
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or clone the repository
|
|
25
|
+
```sh
|
|
26
|
+
git clone https://github.com/cmcorreia/PyTomo
|
|
27
|
+
pip install -e 'path to tomoAO'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
tomoao-0.0.1/setup.cfg
ADDED
tomoao-0.0.1/setup.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import os
|
|
3
|
+
from setuptools import setup, find_packages
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def read(*paths, **kwargs):
|
|
7
|
+
"""Read the contents of a text file safely.
|
|
8
|
+
# >>> read("project_name", "VERSION")
|
|
9
|
+
'0.1.0'
|
|
10
|
+
# >>> read("README.md")
|
|
11
|
+
# ...
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
content = ""
|
|
15
|
+
with io.open(
|
|
16
|
+
os.path.join(os.path.dirname(__file__), *paths),
|
|
17
|
+
encoding=kwargs.get("encoding", "utf8"),
|
|
18
|
+
) as open_file:
|
|
19
|
+
content = open_file.read().strip()
|
|
20
|
+
return content
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
setup(
|
|
24
|
+
name = 'tomoAO',
|
|
25
|
+
version=open("tomoAO/version.py").read().split('=')[1].strip().strip('"'),
|
|
26
|
+
license = 'MIT',
|
|
27
|
+
author = 'SpaceODT',
|
|
28
|
+
description='Python package with atmospheric tomography generic functions and tools',
|
|
29
|
+
long_description=read("README.md"),
|
|
30
|
+
long_description_content_type='text/markdown',
|
|
31
|
+
packages = find_packages(),
|
|
32
|
+
url='https://github.com/cmcorreia/PyTomo',
|
|
33
|
+
install_requires = [
|
|
34
|
+
'aotools',
|
|
35
|
+
'numpy',
|
|
36
|
+
'astropy'
|
|
37
|
+
],
|
|
38
|
+
classifiers=[
|
|
39
|
+
'Programming Language :: Python :: 3',
|
|
40
|
+
'License :: OSI Approved :: MIT License',
|
|
41
|
+
'Operating System :: OS Independent',
|
|
42
|
+
'Intended Audience :: Science/Research',
|
|
43
|
+
'Natural Language :: English',
|
|
44
|
+
]
|
|
45
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .io_functions import *
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import os
|
|
3
|
+
import numpy as np
|
|
4
|
+
from astropy.io import fits
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_from_ini(config_file, ao_mode=None, config_dir=os.path.dirname(__file__)):
|
|
8
|
+
# Instantiate configparser
|
|
9
|
+
config = configparser.ConfigParser()
|
|
10
|
+
config.optionxform = str
|
|
11
|
+
# Path of the configuration file is given as the "configdir" argument
|
|
12
|
+
# Read the configuration file
|
|
13
|
+
config.read(config_dir + config_file)
|
|
14
|
+
|
|
15
|
+
# Get the section of the config file containing the reconstructor
|
|
16
|
+
# parameters for the chosen AO_MODE
|
|
17
|
+
if ao_mode is None:
|
|
18
|
+
ao_mode = 'default'
|
|
19
|
+
|
|
20
|
+
parm = dict(config[ao_mode].items())
|
|
21
|
+
# breakpoint()
|
|
22
|
+
#print(parm)
|
|
23
|
+
# for key, value in parm.items():
|
|
24
|
+
# print(key, value)
|
|
25
|
+
# print(eval(value))
|
|
26
|
+
# print("----------------")
|
|
27
|
+
# breakpoint()
|
|
28
|
+
return {key: eval(value) for key, value in
|
|
29
|
+
parm.items()} # returns a dictionary with entries in specified data types
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def open_influence_matrix(config, baseline_folder=None, filename=None):
|
|
33
|
+
if baseline_folder is None:
|
|
34
|
+
baseline_folder = config["path2imx"]
|
|
35
|
+
|
|
36
|
+
if filename is None:
|
|
37
|
+
filename = config["influence_matrix"]
|
|
38
|
+
|
|
39
|
+
#with fits.open(baseline_folder + "influence_matrix_5Mar.fits") as hdul:
|
|
40
|
+
with fits.open(baseline_folder+filename) as hdul:
|
|
41
|
+
hdul.verify('fix')
|
|
42
|
+
IM = hdul[0].data
|
|
43
|
+
|
|
44
|
+
# For now, concatenating n_channels times the same IM
|
|
45
|
+
n_channels = config["n_lgs"]
|
|
46
|
+
|
|
47
|
+
return np.array([IM] * n_channels)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .recontructors import *
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on Tue Apr 18 15:25:00 2023
|
|
3
|
+
|
|
4
|
+
@author: ccorreia@spaceodt.net
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import tomoAO.tools.tomography_tools as tools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
cuda_available = False
|
|
13
|
+
try:
|
|
14
|
+
import cupy as cp
|
|
15
|
+
if cp.cuda.is_available():
|
|
16
|
+
cuda_available = True
|
|
17
|
+
except:
|
|
18
|
+
cuda_available = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
import matplotlib.pyplot as plt
|
|
22
|
+
from scipy.sparse import block_diag
|
|
23
|
+
|
|
24
|
+
from time import time
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class tomoReconstructor:
|
|
32
|
+
def __init__(self, aoSys, weight_vector=None, alpha=None,
|
|
33
|
+
model='zonal', noise_covariance=None, lag=0,
|
|
34
|
+
weightOptimDir=-1, os=2, zernikeMode=None):
|
|
35
|
+
"""Initialization function.
|
|
36
|
+
|
|
37
|
+
Instantiate a LinearMMSE reconstructor
|
|
38
|
+
|
|
39
|
+
:param tel: Telescope object [Telescope]
|
|
40
|
+
:param atmModel: atmosphere object [Atmosphere]
|
|
41
|
+
:param guideStar: a list of guide star objects [list of Source objects]
|
|
42
|
+
:param mmseStar: a list of target star objects [list of Source objects]
|
|
43
|
+
:param dm: deformable mirror object [Deformable Mirror]
|
|
44
|
+
:param outputRecGrid: a mask where the phase is to be reconstructed
|
|
45
|
+
:param validSubapMask: a multi-dim mask with the valid subapertures per WFS channel [bool]
|
|
46
|
+
:param model: whether 'zonal' (default) or modal [str]
|
|
47
|
+
:param noiseCovariance: the noise covariance matrix as a scalar [float] or a matrix [ndarray]
|
|
48
|
+
:param lag: the AO system lag that can be compensated through tomography [float]
|
|
49
|
+
:param weightOptimDir: a vector with the relative weights for each optimization direction [float]
|
|
50
|
+
:param os: the over-sampling factor [1, 2, 4] (default=2) to apply to the reconstructed phase w.r.t the inpup slopes-maps [int]
|
|
51
|
+
:param zernikeMode: zernike modes used for modal removal sampled with the required os factor [ndarray]
|
|
52
|
+
|
|
53
|
+
:return: This class instantiates the direct matrices and generates an MMSE reconstructor
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
self.tel = aoSys.tel
|
|
57
|
+
self.atmModel = aoSys.atm
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if isinstance(aoSys.lgsAst, list):
|
|
61
|
+
# It's already a list, no need to convert
|
|
62
|
+
self.guideStar = aoSys.lgsAst
|
|
63
|
+
else:
|
|
64
|
+
# Convert to a list
|
|
65
|
+
self.guideStar = [aoSys.lgsAst]
|
|
66
|
+
self.nGuideStar = len(self.guideStar)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if isinstance(aoSys.mmseStar, list):
|
|
70
|
+
# It's already a list, no need to convert
|
|
71
|
+
self.mmseStar = aoSys.mmseStar
|
|
72
|
+
else:
|
|
73
|
+
# Convert to a list
|
|
74
|
+
self.mmseStar = [aoSys.mmseStar]
|
|
75
|
+
|
|
76
|
+
self.nMmseStar = len(self.mmseStar) if self.mmseStar is not None else 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
self.dm = aoSys.dm
|
|
80
|
+
|
|
81
|
+
self._alpha = alpha
|
|
82
|
+
self._lag = lag
|
|
83
|
+
self.weightOptimDir = weightOptimDir
|
|
84
|
+
self.os = os
|
|
85
|
+
self.model = model
|
|
86
|
+
self.zernikeMode = zernikeMode
|
|
87
|
+
|
|
88
|
+
self.outputRecGrid = aoSys.outputReconstructiongrid
|
|
89
|
+
|
|
90
|
+
#All valid subap mask
|
|
91
|
+
self.unfiltered_subap_mask = aoSys.unfiltered_subap_mask
|
|
92
|
+
|
|
93
|
+
#Valid subap mask
|
|
94
|
+
self._filtered_subap_mask = aoSys.filtered_subap_mask
|
|
95
|
+
|
|
96
|
+
if weight_vector is None:
|
|
97
|
+
self.weight_vector = self.computeDefaultWeightVector()
|
|
98
|
+
else:
|
|
99
|
+
self.weight_vector = weight_vector
|
|
100
|
+
|
|
101
|
+
if noise_covariance is None:
|
|
102
|
+
self.noise_covariance = self.computeDefaultNoiseCovariance()
|
|
103
|
+
else:
|
|
104
|
+
self.noise_covariance = noise_covariance
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# self._weight_vector = weight_vector
|
|
112
|
+
if isinstance(self.weightOptimDir, str):
|
|
113
|
+
if self.weightOptimDir.lower() == 'avr' or self.weightOptimDir.lower() == 'average':
|
|
114
|
+
self.weightOptimDir = 1 / self.nMmseStar * np.ones(self.nMmseStar)
|
|
115
|
+
else:
|
|
116
|
+
raise ValueError('Keyword for optimization weights not recognized')
|
|
117
|
+
elif self.weightOptimDir != -1: # Default optimization in individual directions
|
|
118
|
+
if len(self.weightOptimDir) != self.nMmseStar:
|
|
119
|
+
raise ValueError('The weights are not the same size as the number of optimization directions')
|
|
120
|
+
else:
|
|
121
|
+
self.weightOptimDir = self.weightOptimDir / np.sum(self.weightOptimDir)
|
|
122
|
+
|
|
123
|
+
# %% scale r0 in case of different wavelengths
|
|
124
|
+
wvlScale = self.guideStar[0].wavelength / self.atmModel.wavelength
|
|
125
|
+
self.atmModel.r0 = self.atmModel.r0 * wvlScale ** 1.2
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Gamma, gridMask = tools.sparseGradientMatrixAmplitudeWeighted(self.unfiltered_subap_mask, amplMask=None,
|
|
129
|
+
# os=self.os)
|
|
130
|
+
#
|
|
131
|
+
# Gamma = [Gamma] * self.nGuideStar # Replicate Gamma nMmseStar times
|
|
132
|
+
# gridMask = [gridMask] * self.nGuideStar
|
|
133
|
+
# Gamma = [np.asarray(mat) for mat in Gamma]
|
|
134
|
+
# self.Gamma = block_diag(Gamma)
|
|
135
|
+
# self.gridMask = gridMask
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# %% FITTING MATRIX
|
|
139
|
+
if self.dm is not None:
|
|
140
|
+
iFittingMatrix = 2*self.dm.modes[self.outputRecGrid.flatten("F"),]
|
|
141
|
+
|
|
142
|
+
if cuda_available:
|
|
143
|
+
self.fittingMatrix = cp.linalg.pinv(cp.asarray(iFittingMatrix), rcond=1e-3).get()
|
|
144
|
+
else:
|
|
145
|
+
self.fittingMatrix = np.linalg.pinv(iFittingMatrix, rcond=1e-3)
|
|
146
|
+
else:
|
|
147
|
+
self.fittingMatrix = None
|
|
148
|
+
|
|
149
|
+
# %% RECONSTRUCTOR
|
|
150
|
+
self.buildReconstructor()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def noise_covariance(self):
|
|
157
|
+
return self._noise_covariance
|
|
158
|
+
|
|
159
|
+
@noise_covariance.setter
|
|
160
|
+
def noise_covariance(self, val):
|
|
161
|
+
if val is not None:
|
|
162
|
+
if self.model == 'modal' and isinstance(val, (int, float)):
|
|
163
|
+
n_mode = len(self.zernikeMode)
|
|
164
|
+
val = [[val] * n_mode] * n_mode
|
|
165
|
+
val = [[val[i][j] if i == j else 0 for j in range(n_mode)] for i in range(n_mode)]
|
|
166
|
+
elif isinstance(val, (int, float)):
|
|
167
|
+
val = val * np.eye(self.Gamma.shape[0])
|
|
168
|
+
else:
|
|
169
|
+
#Default noise covariance matrix TODO: Evaluate if this should be the default setting
|
|
170
|
+
val = 1e-3 * self.alpha * np.diag(1 / (self.weight_vector.flatten(order='F') + 1e-8))
|
|
171
|
+
|
|
172
|
+
self._noise_covariance = val
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def computeDefaultNoiseCovariance(self):
|
|
176
|
+
self.noise_covariance = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def weight_vector(self):
|
|
181
|
+
return self._weight_vector
|
|
182
|
+
|
|
183
|
+
@weight_vector.setter
|
|
184
|
+
def weight_vector(self, val):
|
|
185
|
+
|
|
186
|
+
if val is not None:
|
|
187
|
+
self._weight_vector = val
|
|
188
|
+
|
|
189
|
+
else:
|
|
190
|
+
# #Default weight vector TODO: Evaluate if this should be the default setting
|
|
191
|
+
weight_vector = np.ones([2 * np.count_nonzero(self.unfiltered_subap_mask), self.nGuideStar])
|
|
192
|
+
filteredAllValidMask = self.filtered_subap_mask.T[self.unfiltered_subap_mask]
|
|
193
|
+
filteredAllValidMask = np.tile(filteredAllValidMask, 2)
|
|
194
|
+
weight_vector[~filteredAllValidMask] = 0
|
|
195
|
+
self._weight_vector = weight_vector
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def computeDefaultWeightVector(self):
|
|
199
|
+
self.weight_vector = None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def filtered_subap_mask(self):
|
|
204
|
+
return self._filtered_subap_mask
|
|
205
|
+
|
|
206
|
+
@filtered_subap_mask.setter
|
|
207
|
+
def filtered_subap_mask(self, val):
|
|
208
|
+
self._filtered_subap_mask = val
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def alpha(self):
|
|
213
|
+
return self._alpha
|
|
214
|
+
|
|
215
|
+
@alpha.setter
|
|
216
|
+
def alpha(self, val):
|
|
217
|
+
self._alpha = val
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def R_unfiltered(self):
|
|
222
|
+
return self._R_unfiltered
|
|
223
|
+
|
|
224
|
+
def buildCovarianceMatrices(self):
|
|
225
|
+
print("Building the covariance matrices")
|
|
226
|
+
self.Cox = []
|
|
227
|
+
for i in range(len(self.mmseStar)):
|
|
228
|
+
res = tools.spatioAngularCovarianceMatrix(self.tel, self.atmModel, [self.mmseStar[i]], self.guideStar,
|
|
229
|
+
self.unfiltered_subap_mask, self.os)
|
|
230
|
+
self.Cox.append(res)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
self.Cxx = tools.spatioAngularCovarianceMatrix(self.tel, self.atmModel, self.guideStar, self.guideStar,
|
|
234
|
+
self.unfiltered_subap_mask, self.os)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def buildGamma(self):
|
|
238
|
+
print("Building Gamma")
|
|
239
|
+
Gamma, gridMask = tools.sparseGradientMatrixAmplitudeWeighted(self.unfiltered_subap_mask, amplMask=None,
|
|
240
|
+
os=self.os)
|
|
241
|
+
|
|
242
|
+
Gamma = [Gamma] * self.nGuideStar # Replicate Gamma nMmseStar times
|
|
243
|
+
gridMask = [gridMask] * self.nGuideStar
|
|
244
|
+
Gamma = [np.asarray(mat) for mat in Gamma]
|
|
245
|
+
self.Gamma = block_diag(Gamma).todense()
|
|
246
|
+
self.gridMask = gridMask
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def buildReconstructor(self):
|
|
250
|
+
print("Using GPU" if cuda_available else "GPU not available, using CPU")
|
|
251
|
+
|
|
252
|
+
start = time()
|
|
253
|
+
self.buildGamma()
|
|
254
|
+
print(f"Took {time()-start} seconds to build Gamma")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
start = time()
|
|
258
|
+
self.buildCovarianceMatrices()
|
|
259
|
+
print(f"Took {time()-start} seconds to build the covariance matrices")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
print("Building the reconstructor")
|
|
263
|
+
start = time()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if cuda_available:
|
|
267
|
+
self.Cox = cp.asarray(self.Cox)
|
|
268
|
+
self.Cxx = cp.asarray(self.Cxx)
|
|
269
|
+
self.noise_covariance = cp.asarray(self.noise_covariance)
|
|
270
|
+
self.Gamma = cp.asarray(self.Gamma)
|
|
271
|
+
|
|
272
|
+
if self.weightOptimDir == -1:
|
|
273
|
+
self.Reconstructor = [None] * self.nMmseStar
|
|
274
|
+
|
|
275
|
+
for k in range(self.nMmseStar):
|
|
276
|
+
|
|
277
|
+
if cuda_available:
|
|
278
|
+
self.Reconstructor[k] = (
|
|
279
|
+
self.Cox[k] @ self.Gamma.T @ cp.linalg.pinv(self.Gamma @ self.Cxx @ self.Gamma.T + self.noise_covariance)).get()
|
|
280
|
+
else:
|
|
281
|
+
self.Reconstructor[k] = self.Cox[k] @ self.Gamma.T @ np.linalg.pinv(self.Gamma @ self.Cxx @ self.Gamma.T + self.noise_covariance)
|
|
282
|
+
|
|
283
|
+
else: # weighted sum over all the optimisation directions
|
|
284
|
+
# This code uses the ss stars to compute a weighted average tomographic
|
|
285
|
+
# reconstructor over all of them
|
|
286
|
+
self.Reconstructor = [None]
|
|
287
|
+
CoxWAvr = np.sum([ self.Cox[k] * self.weightOptimDir[k] for k in range(self.nMmseStar)], axis=0)
|
|
288
|
+
self.Reconstructor[0] = CoxWAvr @ self.Gamma.T @ np.linalg.pinv(self.Gamma @ self.Cxx @ self.Gamma.T + self.noise_covariance)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
self._R_unfiltered = self.fittingMatrix @ self.Reconstructor[0]
|
|
292
|
+
print(f"Took {time()-start} seconds to build the reconstructor")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import tomoAO.tools.tomography_tools as tools
|
|
2
|
+
from tomoAO.Reconstruction.reconClassType import tomoReconstructor
|
|
3
|
+
from tomoAO.IO import load_from_ini
|
|
4
|
+
from tomoAO.Simulation import AOSystem
|
|
5
|
+
|
|
6
|
+
from scipy.linalg import block_diag
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
cuda_available = False
|
|
10
|
+
try:
|
|
11
|
+
import cupy as cp
|
|
12
|
+
if cp.cuda.is_available():
|
|
13
|
+
cuda_available = True
|
|
14
|
+
except:
|
|
15
|
+
cuda_available = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def tomographic_reconstructor_phase_space(config_file, config_dir, alpha, weight_vector=None, noise_covariance=None, debug=False):
|
|
19
|
+
|
|
20
|
+
config_vars = load_from_ini(config_file, ao_mode="MLAO", config_dir=config_dir)
|
|
21
|
+
|
|
22
|
+
aoSys = AOSystem(config_vars)
|
|
23
|
+
|
|
24
|
+
rec = tomoReconstructor(aoSys, alpha=alpha, weight_vector=weight_vector, noise_covariance=noise_covariance, os=2)
|
|
25
|
+
|
|
26
|
+
if debug:
|
|
27
|
+
return rec
|
|
28
|
+
|
|
29
|
+
return rec._R_unfiltered
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def tomographic_reconstructor_dm_space(aoSys, IM, weight, alpha, Cn=None):
|
|
34
|
+
|
|
35
|
+
# %% LGS-to-LGS cross-covariance matrix
|
|
36
|
+
Cxx = tools.spatioAngularCovarianceMatrix(aoSys.tel, aoSys.atm, aoSys.lgsAst, aoSys.lgsAst,
|
|
37
|
+
aoSys.act_mask, os=1, dm_space=True)
|
|
38
|
+
# %% LGS-to-science cross-covariance matrix
|
|
39
|
+
Cox = []
|
|
40
|
+
for i in range(len([aoSys.mmseStar])):
|
|
41
|
+
res = tools.spatioAngularCovarianceMatrix(aoSys.tel, aoSys.atm, [[aoSys.mmseStar][i]],
|
|
42
|
+
aoSys.lgsAst,
|
|
43
|
+
aoSys.act_mask, os=1, dm_space=True)
|
|
44
|
+
Cox.append(res)
|
|
45
|
+
|
|
46
|
+
Cox = Cox[0]
|
|
47
|
+
|
|
48
|
+
# TODO this scaling was chosen roughly to match the original invcov matrix read from file
|
|
49
|
+
Cxx = Cxx / 36
|
|
50
|
+
Cox = Cox / 36
|
|
51
|
+
|
|
52
|
+
if Cn is None:
|
|
53
|
+
Cn = 1e-3 * alpha * np.diag(1 / (weight.flatten(order='F') + 1e-8))
|
|
54
|
+
|
|
55
|
+
IM = block_diag(*IM)
|
|
56
|
+
|
|
57
|
+
if cuda_available:
|
|
58
|
+
Cox = cp.asarray(Cox)
|
|
59
|
+
IM = cp.asarray(IM)
|
|
60
|
+
Cxx = cp.asarray(Cxx)
|
|
61
|
+
Cn = cp.asarray(Cn)
|
|
62
|
+
|
|
63
|
+
return (Cox @ IM.T @ np.linalg.pinv(IM @ Cxx @ IM.T + Cn)).get()
|
|
64
|
+
|
|
65
|
+
return Cox @ IM.T @ np.linalg.pinv(IM @ Cxx @ IM.T + Cn)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def averaging_bayesian_reconstructor_dm_space(IM, weight_vector, inv_cov_mat, alpha):
|
|
71
|
+
"""
|
|
72
|
+
This function computes the Bayesian reconstructor as:
|
|
73
|
+
|
|
74
|
+
:math:`R = {(H^{T} W^{T} + A)}^{-1} H^{T} W^{T}`
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
IM:
|
|
78
|
+
weight_vector:
|
|
79
|
+
inv_cov_mat:
|
|
80
|
+
median_intensity:
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
n_lgs = weight_vector.shape[1]
|
|
87
|
+
|
|
88
|
+
IMw = IM.copy()
|
|
89
|
+
n_act = IM.shape[-1]
|
|
90
|
+
|
|
91
|
+
for k in range(n_lgs):
|
|
92
|
+
W = np.tile(np.reshape(weight_vector[:, k], (-1, 1)), (1, n_act))
|
|
93
|
+
IMw[k, :, :] = IMw[k, :, :] * W
|
|
94
|
+
|
|
95
|
+
IMw = block_diag(*IMw)
|
|
96
|
+
IM = block_diag(*IM)
|
|
97
|
+
|
|
98
|
+
# perform the regularized inversion
|
|
99
|
+
return 1 / n_lgs * np.hstack([np.eye(n_act)] * n_lgs) @ np.linalg.inv(
|
|
100
|
+
IMw.T @ IM + alpha * 1e-3 * block_diag(*[inv_cov_mat]*n_lgs) + 1.) @ (
|
|
101
|
+
IMw.T) # the one is there to penalize piston
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .aoSystem import AOSystem
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on Tue Apr 18 15:25:00 2023
|
|
3
|
+
|
|
4
|
+
@author: ccorreia@spaceodt.net
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import tomoAO.tools.tomography_tools as tools
|
|
8
|
+
|
|
9
|
+
# %% USE OOPAO, define a geometry and compute the cross-covariance matrix for all the layers
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from OOPAO.Atmosphere import Atmosphere
|
|
13
|
+
from OOPAO.DeformableMirror import DeformableMirror
|
|
14
|
+
from OOPAO.MisRegistration import MisRegistration
|
|
15
|
+
from OOPAO.Source import Source
|
|
16
|
+
from OOPAO.Telescope import Telescope
|
|
17
|
+
from OOPAO.ShackHartmann import ShackHartmann
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AOSystem:
|
|
23
|
+
def __init__(self, param):
|
|
24
|
+
# %% ----------------------- TELESCOPE ----------------------------------
|
|
25
|
+
|
|
26
|
+
# create the Telescope object (not Keck for now)
|
|
27
|
+
tel = Telescope(resolution=param['resolution'],
|
|
28
|
+
diameter=param['diameter'],
|
|
29
|
+
samplingTime=param['samplingTime'],
|
|
30
|
+
centralObstruction=param['centralObstruction'])
|
|
31
|
+
|
|
32
|
+
thickness_spider = 0.05 # size in m
|
|
33
|
+
angle = [45, 135, 225, 315] # in degrees
|
|
34
|
+
offset_X = [-0.4, 0.4, 0.4, -0.4] # shift offset of the spider
|
|
35
|
+
offset_Y = None
|
|
36
|
+
|
|
37
|
+
tel.apply_spiders(angle, thickness_spider, offset_X=offset_X, offset_Y=offset_Y)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# data = loadmat(f'{param["path2matrices"]}tel_pupil.mat')
|
|
41
|
+
# pupil = data['pup'] # Extract the array
|
|
42
|
+
#
|
|
43
|
+
# tel.pupil = pupil
|
|
44
|
+
# %% ----------------------- NGS ----------------------------------
|
|
45
|
+
# create the Source object
|
|
46
|
+
ngs = Source(optBand=param['opticalBand'],
|
|
47
|
+
magnitude=param['magnitude'],
|
|
48
|
+
altitude=param['srcAltitude'])
|
|
49
|
+
|
|
50
|
+
# combine the NGS to the telescope using '*' operator:
|
|
51
|
+
ngs * tel
|
|
52
|
+
# %% LGS objects
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
lgsAst = [Source(optBand=param['opticalBand'],
|
|
56
|
+
magnitude=param['lgs_magnitude'],
|
|
57
|
+
altitude=param['lgs_altitude'],
|
|
58
|
+
coordinates=[param['lgs_zenith'][kLgs], param['lgs_azimuth'][kLgs]])
|
|
59
|
+
for kLgs in range(param["n_lgs"])]
|
|
60
|
+
|
|
61
|
+
# %% science targets
|
|
62
|
+
sciSrc = Source(optBand='K',
|
|
63
|
+
magnitude=0,
|
|
64
|
+
altitude=np.inf,
|
|
65
|
+
coordinates=[0, 0])
|
|
66
|
+
|
|
67
|
+
# %% science targets
|
|
68
|
+
|
|
69
|
+
recCalSrc = Source(optBand='Na',
|
|
70
|
+
magnitude=0,
|
|
71
|
+
altitude=np.inf,
|
|
72
|
+
coordinates=[0, 0])
|
|
73
|
+
# %% ----------------------- ATMOSPHERE ----------------------------------
|
|
74
|
+
|
|
75
|
+
# create the Atmosphere object
|
|
76
|
+
|
|
77
|
+
atm = Atmosphere(telescope=tel,
|
|
78
|
+
r0=param['r0'],
|
|
79
|
+
L0=param['L0'],
|
|
80
|
+
windSpeed=param['windSpeed'],
|
|
81
|
+
fractionalR0=param['fractionnalR0'],
|
|
82
|
+
windDirection=param['windDirection'],
|
|
83
|
+
altitude=np.array(param['altitude']),
|
|
84
|
+
param=param)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# %% ----------------------- DEFORMABLE MIRROR ----------------------------------
|
|
88
|
+
# mis-registrations object
|
|
89
|
+
misReg = MisRegistration(param)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# set coordinate vector to match the Keck actuator location
|
|
93
|
+
act_mask = np.loadtxt(param["actuator_mask"], dtype=bool, delimiter=",")
|
|
94
|
+
if act_mask.shape[0] != param['nActuator']:
|
|
95
|
+
act_mask = np.pad(act_mask, pad_width=int(param['nSubapExtra']/2), mode='constant', constant_values=0)
|
|
96
|
+
|
|
97
|
+
X, Y = tools.meshgrid(param['nActuator'], tel.D, offset_x=0.0, offset_y=0.0, stretch_x=1, stretch_y=1)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
coordinates = np.array([X[act_mask], Y[act_mask]]).T
|
|
101
|
+
|
|
102
|
+
self.dm_coordinates = coordinates
|
|
103
|
+
# if no coordinates specified, create a cartesian dm
|
|
104
|
+
resolution = tel.resolution
|
|
105
|
+
|
|
106
|
+
# TODO this cannot be set by default, since the wavefront resolution is set only during the MMSE reconstructor. THere is a loophole here!
|
|
107
|
+
#tel.resolution = 2 * param['nSubaperture'] + 1
|
|
108
|
+
tel.resolution = param['dm_resolution']# this is to compute a low-resolution DM IF, where low-resolution is the wavefront reconstruction resolution
|
|
109
|
+
dm = DeformableMirror(telescope=tel,
|
|
110
|
+
nSubap=param['nSubaperture'],
|
|
111
|
+
mechCoupling=param['mechanicalCoupling'],
|
|
112
|
+
misReg=misReg,
|
|
113
|
+
coordinates=coordinates,
|
|
114
|
+
pitch=tel.D / (param['nActuator'] - 1))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
dm.act_mask = act_mask
|
|
118
|
+
tel.resolution = resolution
|
|
119
|
+
# %% ----------------------- Wave Front Sensor ----------------------------------
|
|
120
|
+
wfs = ShackHartmann(telescope=tel,
|
|
121
|
+
nSubap=param['nSubaperture'],
|
|
122
|
+
lightRatio=0.5)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
unfiltered_subap_mask = np.loadtxt(param["unfiltered_subap_mask"],
|
|
126
|
+
dtype=bool, delimiter=",")
|
|
127
|
+
|
|
128
|
+
if unfiltered_subap_mask.shape[0] != param['nSubaperture']:
|
|
129
|
+
unfiltered_subap_mask = np.pad(unfiltered_subap_mask,
|
|
130
|
+
pad_width=int(param['nSubapExtra']/2),
|
|
131
|
+
mode='constant',
|
|
132
|
+
constant_values=0)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
filtered_subap_mask = np.loadtxt(param["filtered_subap_mask"],
|
|
136
|
+
dtype=bool, delimiter=",")
|
|
137
|
+
|
|
138
|
+
if filtered_subap_mask.shape[0] != param['nSubaperture']:
|
|
139
|
+
filtered_subap_mask = np.pad(filtered_subap_mask,
|
|
140
|
+
pad_width=int(param['nSubapExtra']/2),
|
|
141
|
+
mode='constant',
|
|
142
|
+
constant_values=0)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
wfs.valid_subapertures = unfiltered_subap_mask
|
|
146
|
+
wfs.subap_mask = unfiltered_subap_mask #Force existence of subap_mask variable for backwards compatibility
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# %% ----------------------- Wave Front Reconstruction ----------------------------------
|
|
151
|
+
|
|
152
|
+
outputReconstructiongrid = tools.reconstructionGrid(unfiltered_subap_mask, param['os'], dm_space=False)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# %% ----------------------- Self Allocation ----------------------------------
|
|
156
|
+
|
|
157
|
+
self.param = param
|
|
158
|
+
self.atm = atm
|
|
159
|
+
self.tel = tel
|
|
160
|
+
self.dm = dm
|
|
161
|
+
self.wfs = wfs
|
|
162
|
+
self.lgsAst = lgsAst
|
|
163
|
+
self.mmseStar = ngs
|
|
164
|
+
self.misreg = misReg
|
|
165
|
+
self.outputReconstructiongrid = outputReconstructiongrid
|
|
166
|
+
self.sciSrc = sciSrc
|
|
167
|
+
self.act_mask = act_mask
|
|
168
|
+
self.unfiltered_subap_mask = unfiltered_subap_mask
|
|
169
|
+
self.filtered_subap_mask = filtered_subap_mask
|
|
170
|
+
|
|
File without changes
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on Tue Apr 18 15:25:00 2023
|
|
5
|
+
|
|
6
|
+
@author: ccorreia@spaceodt.net
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
cuda_available = False
|
|
11
|
+
try:
|
|
12
|
+
import cupy as cp
|
|
13
|
+
if cp.cuda.is_available():
|
|
14
|
+
cuda_available = True
|
|
15
|
+
except:
|
|
16
|
+
cuda_available = False
|
|
17
|
+
|
|
18
|
+
from scipy.sparse import csr_matrix
|
|
19
|
+
from scipy.special import gamma, kv
|
|
20
|
+
|
|
21
|
+
import math
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def bessel_i(n, x, terms=10):
|
|
26
|
+
x = cp.asarray(x, dtype=cp.float64)
|
|
27
|
+
sum_result = cp.zeros_like(x, dtype=cp.float64)
|
|
28
|
+
|
|
29
|
+
for m in range(terms):
|
|
30
|
+
term = (1 / (math.factorial(m) * math.gamma(m + n + 1))) * (x / 2) ** (2 * m + n)
|
|
31
|
+
sum_result += term
|
|
32
|
+
|
|
33
|
+
return sum_result
|
|
34
|
+
|
|
35
|
+
def bessel_k(n, x, terms=10):
|
|
36
|
+
x = cp.asarray(x, dtype=cp.float64)
|
|
37
|
+
|
|
38
|
+
if cp.any(x <= 0):
|
|
39
|
+
raise ValueError("x must be positive for K_n(x)")
|
|
40
|
+
|
|
41
|
+
i_n = bessel_i(n, x, terms)
|
|
42
|
+
i_neg_n = bessel_i(-n, x, terms)
|
|
43
|
+
|
|
44
|
+
return (np.pi / 2) * (i_neg_n - i_n) / np.sin(n * cp.pi)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def covariance_matrix(rho, r0, L0):
|
|
48
|
+
L0r0ratio = (L0 / r0) ** (5 / 3)
|
|
49
|
+
cst = (24 * gamma(6 / 5) / 5) ** (5 / 6) * \
|
|
50
|
+
(gamma(11 / 6) / (2 ** (5 / 6) * np.pi ** (8 / 3))) * L0r0ratio
|
|
51
|
+
out = np.ones(rho.shape, dtype=rho.dtype) * (24 * gamma(6 / 5) / 5) ** (5 / 6) * \
|
|
52
|
+
(gamma(11 / 6) * gamma(5 / 6) /
|
|
53
|
+
(2 * np.pi ** (8 / 3))) * L0r0ratio
|
|
54
|
+
index = rho != 0
|
|
55
|
+
u = 2 * np.pi * rho[index] / L0
|
|
56
|
+
if cuda_available:
|
|
57
|
+
out[index] = cst * u ** (5 / 6) * bessel_k(5 / 6, cp.asarray(u), terms=10).get()
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
out[index] = cst * u ** (5 / 6) * kv(5 / 6, u)
|
|
61
|
+
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def dists(rho1, rho2):
|
|
67
|
+
dist = np.abs(np.subtract.outer(rho1.flatten(), rho2.flatten()))
|
|
68
|
+
return dist
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def meshgrid(nPts, D, offset_x=0, offset_y=0, stretch_x=1, stretch_y=1):
|
|
72
|
+
x = np.linspace(-D / 2, D / 2, nPts)
|
|
73
|
+
X, Y = np.meshgrid(x * stretch_x, x * stretch_y)
|
|
74
|
+
X = X + offset_x
|
|
75
|
+
Y = Y + offset_y
|
|
76
|
+
return X, Y
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def reconstructionGrid(mask, os, dm_space=False):
|
|
80
|
+
# os stands for oversampling. Can be 1 or 2.
|
|
81
|
+
# If os=1, reconstructionGrid pitch = subaperturePitch,
|
|
82
|
+
# if os=2, reconstructionGrid pitch = subaperturePitch/2
|
|
83
|
+
from scipy.signal import convolve2d
|
|
84
|
+
if os == 1 and dm_space:
|
|
85
|
+
val = mask
|
|
86
|
+
elif os == 1 and not dm_space:
|
|
87
|
+
val = convolve2d(np.ones((2, 2)), mask).astype('bool')
|
|
88
|
+
elif os == 2:
|
|
89
|
+
nElements = os * mask.shape[0] + 1 # Linear number of lenslet+actuator
|
|
90
|
+
validLensletActuator = np.zeros((nElements, nElements), dtype=bool)
|
|
91
|
+
index = np.arange(1, nElements, 2) # Lenslet index
|
|
92
|
+
validLensletActuator[np.ix_(index, index)] = mask
|
|
93
|
+
kernel = np.ones((3, 3))
|
|
94
|
+
output = convolve2d(validLensletActuator, kernel, mode='same')
|
|
95
|
+
val = output.astype(bool)
|
|
96
|
+
return val
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def spatioAngularCovarianceMatrix(tel, atm, src1, src2, mask, os, dm_space=0):
|
|
100
|
+
# Compute the discrete reconstruction mask from the subap_mask passed on as input
|
|
101
|
+
|
|
102
|
+
recIdx = reconstructionGrid(mask, os, dm_space)
|
|
103
|
+
if src1 == src2: # auto-covariance matrix
|
|
104
|
+
|
|
105
|
+
arcsec2radian = np.pi / 180 / 3600
|
|
106
|
+
nPts = recIdx.shape[0]
|
|
107
|
+
crossCovCell = np.empty((len(src1), len(src2)), dtype=object)
|
|
108
|
+
for i in range(0, len(src1)):
|
|
109
|
+
for j in range(0, len(src2)):
|
|
110
|
+
if j >= i:
|
|
111
|
+
|
|
112
|
+
phaseCovElem = np.zeros((nPts ** 2, nPts ** 2, len(atm.altitude)))
|
|
113
|
+
for l in range(0, len(atm.altitude)):
|
|
114
|
+
# CROSS COVARIANCE BETWEEN TWO STARS
|
|
115
|
+
# STAR 1
|
|
116
|
+
coneCompressionFactor = 1 - \
|
|
117
|
+
atm.altitude[l] / src1[i].altitude
|
|
118
|
+
x = src1[i].coordinates[0] * atm.altitude[l] * \
|
|
119
|
+
arcsec2radian * \
|
|
120
|
+
np.cos(src1[i].coordinates[1] * np.pi / 180)
|
|
121
|
+
y = src1[i].coordinates[0] * atm.altitude[l] * \
|
|
122
|
+
arcsec2radian * \
|
|
123
|
+
np.sin(src1[i].coordinates[1] * np.pi / 180)
|
|
124
|
+
X, Y = meshgrid(nPts, tel.D, offset_x=x, offset_y=y,
|
|
125
|
+
stretch_x=coneCompressionFactor, stretch_y=coneCompressionFactor)
|
|
126
|
+
|
|
127
|
+
rho1 = X + 1j * Y
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# STAR 2
|
|
131
|
+
coneCompressionFactor = 1 - \
|
|
132
|
+
atm.altitude[l] / src2[j].altitude
|
|
133
|
+
x = src2[j].coordinates[0] * atm.altitude[l] * \
|
|
134
|
+
arcsec2radian * \
|
|
135
|
+
np.cos(src2[j].coordinates[1] * np.pi / 180)
|
|
136
|
+
y = src2[j].coordinates[0] * atm.altitude[l] * \
|
|
137
|
+
arcsec2radian * \
|
|
138
|
+
np.sin(src2[j].coordinates[1] * np.pi / 180)
|
|
139
|
+
X, Y = meshgrid(nPts, tel.D, offset_x=x, offset_y=y,
|
|
140
|
+
stretch_x=coneCompressionFactor, stretch_y=coneCompressionFactor)
|
|
141
|
+
rho2 = X + 1j * Y
|
|
142
|
+
|
|
143
|
+
dist = dists(rho1.T, rho2.T) # not sure why, using the transpose makes it equal to Matlab's
|
|
144
|
+
|
|
145
|
+
phaseCovElem[:, :, l] = covariance_matrix(
|
|
146
|
+
dist, atm.r0, atm.L0) * atm.fractionalR0[l]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
crossCovCell[i, j] = np.sum(phaseCovElem, axis=2)[
|
|
150
|
+
recIdx.flatten("F"), :][:, recIdx.flatten("F")]
|
|
151
|
+
|
|
152
|
+
# populate the off-diagonal blocks left null in the previous step
|
|
153
|
+
for i in range(0, len(src1)):
|
|
154
|
+
for j in range(0, len(src1)):
|
|
155
|
+
if j < i:
|
|
156
|
+
crossCovCell[i, j] = np.transpose(crossCovCell[j, i])
|
|
157
|
+
|
|
158
|
+
crossCovMat = np.block(
|
|
159
|
+
[[crossCovCell[i, j] for j in range(len(src1))] for i in range(len(src2))])
|
|
160
|
+
crossCovMat = np.vstack(crossCovMat)
|
|
161
|
+
return crossCovMat
|
|
162
|
+
else:
|
|
163
|
+
# breakpoint()
|
|
164
|
+
arcsec2radian = np.pi / 180 / 3600
|
|
165
|
+
nPts = recIdx.shape[0]
|
|
166
|
+
crossCovCell = np.empty((len(src1), len(src2)), dtype=object)
|
|
167
|
+
for i in range(0, len(src1)):
|
|
168
|
+
for j in range(0, len(src2)):
|
|
169
|
+
phaseCovElem = np.zeros((nPts ** 2, nPts ** 2, len(atm.altitude)))
|
|
170
|
+
for l in range(0, len(atm.altitude)):
|
|
171
|
+
# CROSS COVARIANCE BETWEEN TWO STARS
|
|
172
|
+
# STAR 1
|
|
173
|
+
coneCompressionFactor = 1 - \
|
|
174
|
+
atm.altitude[l] / src1[i].altitude
|
|
175
|
+
x = src1[i].coordinates[0] * atm.altitude[l] * \
|
|
176
|
+
arcsec2radian * \
|
|
177
|
+
np.cos(src1[i].coordinates[1] * np.pi / 180)
|
|
178
|
+
y = src1[i].coordinates[0] * atm.altitude[l] * \
|
|
179
|
+
arcsec2radian * \
|
|
180
|
+
np.sin(src1[i].coordinates[1] * np.pi / 180)
|
|
181
|
+
X, Y = meshgrid(nPts, tel.D, offset_x=x, offset_y=y,
|
|
182
|
+
stretch_x=coneCompressionFactor, stretch_y=coneCompressionFactor)
|
|
183
|
+
rho1 = X + 1j * Y
|
|
184
|
+
# STAR 2
|
|
185
|
+
coneCompressionFactor = 1 - \
|
|
186
|
+
atm.altitude[l] / src2[j].altitude
|
|
187
|
+
x = src2[j].coordinates[0] * atm.altitude[l] * \
|
|
188
|
+
arcsec2radian * \
|
|
189
|
+
np.cos(src2[j].coordinates[1] * np.pi / 180)
|
|
190
|
+
y = src2[j].coordinates[0] * atm.altitude[l] * \
|
|
191
|
+
arcsec2radian * \
|
|
192
|
+
np.sin(src2[j].coordinates[1] * np.pi / 180)
|
|
193
|
+
X, Y = meshgrid(nPts, tel.D, offset_x=x, offset_y=y,
|
|
194
|
+
stretch_x=coneCompressionFactor, stretch_y=coneCompressionFactor)
|
|
195
|
+
rho2 = X + 1j * Y
|
|
196
|
+
|
|
197
|
+
dist = dists(rho1.T, rho2.T) # not sure why, using the transpose makes it equal to Matlab's
|
|
198
|
+
phaseCovElem[:, :, l] = covariance_matrix(
|
|
199
|
+
dist, atm.r0, atm.L0) * atm.fractionalR0[l]
|
|
200
|
+
crossCovCell[i, j] = np.sum(phaseCovElem, axis=2)[
|
|
201
|
+
recIdx.flatten("F"), :][:, recIdx.flatten("F")]
|
|
202
|
+
crossCovMat = np.block(
|
|
203
|
+
[[crossCovCell[i, j] for j in range(len(src2))] for i in range(len(src1))])
|
|
204
|
+
if len(src1) > 1:
|
|
205
|
+
crossCovMat = np.vstack(crossCovMat)
|
|
206
|
+
return crossCovMat
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def sparseGradientMatrixAmplitudeWeighted(validLenslet, amplMask, os=2):
|
|
213
|
+
|
|
214
|
+
nLenslet = validLenslet.shape[0]
|
|
215
|
+
|
|
216
|
+
nMap = os * nLenslet + 1
|
|
217
|
+
|
|
218
|
+
if amplMask is None:
|
|
219
|
+
amplMask = np.ones((nMap, nMap))
|
|
220
|
+
|
|
221
|
+
nValidLenslet_ = np.count_nonzero(validLenslet)
|
|
222
|
+
dsa = 1
|
|
223
|
+
|
|
224
|
+
i0x = np.tile([0, 1, 2], 3)
|
|
225
|
+
|
|
226
|
+
j0x = np.repeat([0, 1, 2], 3)
|
|
227
|
+
|
|
228
|
+
i0y = np.tile([0, 1, 2], 3)
|
|
229
|
+
|
|
230
|
+
j0y = np.repeat([0, 1, 2], 3)
|
|
231
|
+
|
|
232
|
+
s0x = np.array([-1 / 4, -1 / 2, -1 / 4, 0, 0, 0, 1 / 4, 1 / 2, 1 / 4]) * (1 / dsa)
|
|
233
|
+
|
|
234
|
+
s0y = -np.array([1 / 4, 0, -1 / 4, 1 / 2, 0, -1 / 2, 1 / 4, 0, -1 / 4]) * (1 / dsa)
|
|
235
|
+
|
|
236
|
+
Gv = np.array([[-2, 2, -1, 1], [-2, 2, -1, 1], [-1, 1, -2, 2], [-1, 1, -2, 2]])
|
|
237
|
+
|
|
238
|
+
i_x = np.zeros(9 * nValidLenslet_)
|
|
239
|
+
j_x = np.zeros(9 * nValidLenslet_)
|
|
240
|
+
s_x = np.zeros(9 * nValidLenslet_)
|
|
241
|
+
i_y = np.zeros(9 * nValidLenslet_)
|
|
242
|
+
j_y = np.zeros(9 * nValidLenslet_)
|
|
243
|
+
s_y = np.zeros(9 * nValidLenslet_)
|
|
244
|
+
|
|
245
|
+
jMap0, iMap0 = np.meshgrid(np.arange(0, 3), np.arange(0, 3))
|
|
246
|
+
|
|
247
|
+
gridMask = np.zeros((nMap, nMap), dtype=bool)
|
|
248
|
+
|
|
249
|
+
u = np.arange(0, 9)
|
|
250
|
+
|
|
251
|
+
for jLenslet in range(0, nLenslet):
|
|
252
|
+
jOffset = os * (jLenslet)
|
|
253
|
+
for iLenslet in range(0, nLenslet):
|
|
254
|
+
if validLenslet[iLenslet, jLenslet]:
|
|
255
|
+
I = (iLenslet) * os + 1
|
|
256
|
+
J = (jLenslet) * os + 1
|
|
257
|
+
|
|
258
|
+
a = amplMask[I - 1:I + os, J - 1:J + os]
|
|
259
|
+
|
|
260
|
+
numIllum = np.sum(a)
|
|
261
|
+
|
|
262
|
+
if numIllum == (os + 1) ** 2:
|
|
263
|
+
iOffset = os * (iLenslet)
|
|
264
|
+
i_x[u] = i0x + iOffset
|
|
265
|
+
j_x[u] = j0x + jOffset
|
|
266
|
+
s_x[u] = s0x
|
|
267
|
+
i_y[u] = i0y + iOffset
|
|
268
|
+
j_y[u] = j0y + jOffset
|
|
269
|
+
s_y[u] = s0y
|
|
270
|
+
u = u + (os + 1) ** 2
|
|
271
|
+
|
|
272
|
+
gridMask[iMap0 + iOffset, jMap0 + jOffset] = True
|
|
273
|
+
|
|
274
|
+
indx = np.ravel_multi_index((i_x.astype(int), j_x.astype(int)), (nMap, nMap), order='F')
|
|
275
|
+
indy = np.ravel_multi_index((i_y.astype(int), j_y.astype(int)), (nMap, nMap), order='F')
|
|
276
|
+
|
|
277
|
+
v = np.tile(np.arange(0, 2 * nValidLenslet_), (u.size, 1)).T
|
|
278
|
+
|
|
279
|
+
Gamma = csr_matrix((np.concatenate((s_x, s_y)), (v.flatten(), np.concatenate((indx, indy)))),
|
|
280
|
+
shape=(2 * nValidLenslet_, nMap ** 2))
|
|
281
|
+
Gamma = Gamma.todense()
|
|
282
|
+
Gamma = Gamma[:, gridMask.flatten("F")]
|
|
283
|
+
|
|
284
|
+
return Gamma, gridMask
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tomoAO
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python package with atmospheric tomography generic functions and tools
|
|
5
|
+
Home-page: https://github.com/cmcorreia/PyTomo
|
|
6
|
+
Author: SpaceODT
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: aotools
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Requires-Dist: astropy
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# tomoAO
|
|
29
|
+
Python package with atmospheric tomography generic functions and tools
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
1. Install OOPAO
|
|
34
|
+
```sh
|
|
35
|
+
pip install git+https://github.com/cheritier/OOPAO
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. Install cupy according to your cuda version (Optional)
|
|
39
|
+
```sh
|
|
40
|
+
pip install cupy-cuda11x
|
|
41
|
+
```
|
|
42
|
+
```sh
|
|
43
|
+
pip install cupy-cuda12x
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. Install tomoAO via pip
|
|
47
|
+
```sh
|
|
48
|
+
pip install tomoAO
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or clone the repository
|
|
52
|
+
```sh
|
|
53
|
+
git clone https://github.com/cmcorreia/PyTomo
|
|
54
|
+
pip install -e 'path to tomoAO'
|
|
55
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
tomoAO/__init__.py
|
|
5
|
+
tomoAO/version.py
|
|
6
|
+
tomoAO.egg-info/PKG-INFO
|
|
7
|
+
tomoAO.egg-info/SOURCES.txt
|
|
8
|
+
tomoAO.egg-info/dependency_links.txt
|
|
9
|
+
tomoAO.egg-info/requires.txt
|
|
10
|
+
tomoAO.egg-info/top_level.txt
|
|
11
|
+
tomoAO/IO/__init__.py
|
|
12
|
+
tomoAO/IO/io_functions.py
|
|
13
|
+
tomoAO/Reconstruction/__init__.py
|
|
14
|
+
tomoAO/Reconstruction/reconClassType.py
|
|
15
|
+
tomoAO/Reconstruction/recontructors.py
|
|
16
|
+
tomoAO/Simulation/__init__.py
|
|
17
|
+
tomoAO/Simulation/aoSystem.py
|
|
18
|
+
tomoAO/tools/__init__.py
|
|
19
|
+
tomoAO/tools/tomography_tools.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tomoAO
|