#Copyright Durham University and Andrew Reeves
#2014
# This file is part of soapy.
# soapy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# soapy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with soapy. If not, see <http://www.gnu.org/licenses/>.
import numpy
import aotools
from . import AOFFT, logger, lineofsight, interp
# from .aotools import circle, interp
#xrange now just "range" in python3.
#Following code means fastest implementation used in 2 and 3
try:
xrange
except NameError:
xrange = range
RAD2ASEC = 206265.
ASEC2RAD = 1./RAD2ASEC
[docs]class LGS(object):
'''
A class to simulate the propogation of a laser up through turbulence.
Given a set of phase screens, this will return the PSF which would be present on-sky.
Parameters:
simConfig: The Soapy simulation config
wfsConfig: The relavent Soapy WFS configuration
atmosConfig: The relavent Soapy atmosphere configuration
nOutPxls (int): Number of pixels required in output LGS
outPxlScale (float): The pixel scale of the output LGS PSF in arcsecs per pixel
'''
def __init__(
self, wfsConfig, soapyConfig, nOutPxls=None, outPxlScale=None):
# The LGS WFS config
self.wfsConfig = wfsConfig
self.config = wfsConfig.lgs
self.soapyConfig = soapyConfig
self.simConfig = soapyConfig.sim
self.atmosConfig = soapyConfig.atmos
self.outPxlScale = outPxlScale
self.nOutPxls = nOutPxls
# get this no of pixels from the LOS if not other wise told
# Change in ``calcInitParams`` if you want more or less
self.losNOutPxls = self.nOutPxls
self.losOutPxlScale = self.outPxlScale
self.config.position = self.wfsConfig.GSPosition
self.losMask = None
self.calcInitParams()
self.initLos()
self.initFFTs()
[docs] def calcInitParams(self):
pass
[docs] def initFFTs(self):
"""
Virtual Method as many LGS implentations will require extra FFTs
"""
pass
[docs] def initLos(self):
"""
Initialises the ``LineOfSight`` object, which gets the phase or EField in a given direction through turbulence.
"""
# Init the line of sight object for light propation through turbulence
self.los = lineofsight.LineOfSight(
self.config, self.soapyConfig,
propagation_direction="up", nx_out_pixels=self.losNOutPxls,
mask=self.losMask, out_pixel_scale=self.losOutPxlScale,
)
# Find central position of the LGS pupil at each altitude.
self.los.metaPupilPos = {}
for i in xrange(self.atmosConfig.scrnNo):
self.los.metaPupilPos[i] = lgsOALaunchMetaPupilPos(
self.config.position,
numpy.array(self.config.launchPosition)*self.los.telescope_diameter/2.,
self.config.height, self.atmosConfig.scrnHeights[i]
)
# Check position not too far from centre. May need more phase!
maxPos = numpy.array(
list(self.los.metaPupilPos.values())).max()
maxPos*=self.simConfig.pxlScale
if 2*(maxPos+self.simConfig.pupilSize/2.) > self.simConfig.simSize:
logger.warning(
"LGS far off-axis - likely need to make simOversize bigger")
[docs] def getLgsPsf(self, scrns):
logger.debug("Get LGS PSF")
self.los.frame(scrns)
self.EField = self.los.EField
self.phase = self.los.phase
[docs]class LGS_Geometric(LGS):
'''
A class to simulate the propogation of a laser up through turbulence using a geometric algorithm.
Given a set of phase screens, this will return the PSF which would be present on-sky.
Parameters:
simConfig: The Soapy simulation config
wfsConfig: The relavent Soapy WFS configuration
atmosConfig: The relavent Soapy atmosphere configuration
nOutPxls (int): Number of pixels required in output LGS
outPxlScale (float): The pixel scale of the output LGS PSF in arcsecs per pixel
'''
[docs] def calcInitParams(self):
"""
Calculate some useful paramters to be used later
"""
self.lgsPupilPxls = int(
round(self.config.pupilDiam * self.simConfig.pxlScale))
if self.outPxlScale is None:
self.outPxlScale_m = 1./self.simConfig.pxlScale
else:
# The pixel scale in metres per pixel at the LGS altitude
self.outPxlScale_m = (self.outPxlScale/3600.)*(numpy.pi/180.) * self.config.height
# Get the angular scale in radians of the output array
self.outPxlScale_rad = self.outPxlScale_m/self.config.height
# The number of pixels required across the LGS image
if self.nOutPxls is None:
self.nOutPxls = self.simConfig.simSize
# Field of fov of the requested LGS PSF image
self.fov = (self.nOutPxls * self.outPxlScale_rad) * RAD2ASEC
# The number of points required to get the correct FOV after the FFT
fov_rad = self.fov / RAD2ASEC
self.nFovPxls = int(round(fov_rad * self.config.pupilDiam
/ self.config.wavelength))
# The mask to apply before geometric FFTing
self.mask = aotools.circle(self.nFovPxls/2., self.nFovPxls)
self.losNOutPxls = self.lgsPupilPxls
self.losOutPxlScale = self.config.pupilDiam/self.lgsPupilPxls
[docs] def initFFTs(self):
# FFT for geometric propagation
self.FFT = AOFFT.FFT(
(self.nOutPxls, self.nOutPxls),
axes=(0,1), mode="pyfftw",
dtype = "complex64",direction="FORWARD",
THREADS=self.config.fftwThreads,
fftw_FLAGS=(self.config.fftwFlag,"FFTW_DESTROY_INPUT")
)
[docs] def getLgsPsf(self, scrns):
super(LGS_Geometric, self).getLgsPsf(scrns)
# Pick out lgs Pupil sized chunk of field in middle
# coord = (self.los.EField.shape[0]-self.lgsPupilPxls)/2.
# lgsEField = self.los.EField[coord: -coord, coord: -coord]
# Scale to the desired size for LGS FOV
lgsEField = interp.zoom(self.EField, self.nFovPxls)*self.mask
self.FFT.inputData[:self.nFovPxls, :self.nFovPxls] = lgsEField
self.psf = abs(AOFFT.ftShift2d(self.FFT())**2)
return self.psf
[docs]class LGS_Physical(LGS):
'''
A class to simulate the propogation of a laser up through turbulence using a geometric algorithm.
Given a set of phase screens, this will return the PSF which would be present on-sky.
Parameters:
simConfig: The Soapy simulation config
wfsConfig: The relavent Soapy WFS configuration
atmosConfig: The relavent Soapy atmosphere configuration
nOutPxls (int): Number of pixels required in output LGS
outPxlScale (float): The pixel scale of the output LGS PSF in arcsecs per pixel
'''
[docs] def calcInitParams(self):
"""
Calculate some useful paramters to be used later
"""
self.mask = aotools.circle(
0.5*self.config.pupilDiam*self.simConfig.pxlScale,
self.simConfig.simSize)
if self.outPxlScale is None:
self.outPxlScale_m = 1./self.simConfig.pxlScale
else:
# The pixel scale in metres per pixel at the LGS altitude
self.outPxlScale_m = (self.outPxlScale / RAD2ASEC) * self.config.height
# Get the angular scale in radians of the output array
self.outPxlScale_rad = self.outPxlScale_m/self.config.height
# The number of pixels required across the LGS image
if self.nOutPxls is None:
self.nOutPxls = self.simConfig.simSize
# Field of fov of the requested LGS PSF image
self.fov = (self.nOutPxls * self.outPxlScale_rad) * RAD2ASEC
# The number of points required to get the correct FOV after the FFT
fov_rad = self.fov / RAD2ASEC
self.nFovPxls = (fov_rad * self.config.pupilDiam
/ self.config.wavelength)
# The mask to apply before physical propagation
self.lgsPupilPxls = int(round(self.config.pupilDiam/self.outPxlScale_m))
self.mask = aotools.circle(self.lgsPupilPxls/2., 3*self.nOutPxls)
self.losMask = self.mask
self.losOutPxlScale = self.outPxlScale_m
self.losNOutPxls = 3*self.nOutPxls
[docs] def getLgsPsf(self, scrns=None):
"""
Return the LGS PSF to be used in WFS calculation
"""
super(LGS_Physical, self).getLgsPsf(scrns)
# Pick out middle of oversized fov
self.psf = abs(self.EField[
self.nOutPxls: -self.nOutPxls,
self.nOutPxls: -self.nOutPxls])**2
return self.psf