#! /usr/bin/env python
#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/>.
'''
The main Soapy Simulation module
This module contains the ``Sim`` class, which can be used to run an end-to-end simulation. Initally, a configuration file is read, the system is initialised, interaction and command matrices calculated and finally a loop run. The simulation outputs some information to the console during the simulation.
The ``Sim`` class holds all configuration information and data from the simulation.
Examples:
To initialise the class::
import soapy
sim = soapy.Sim("sh_8x8_4.2m.py")
Configuration information has now been loaded, and can be accessed through the ``config`` attribute of the ``sim`` class. In fact, each sub-module of the system has a configuration object accessed through this config attribute::
print(sim.config.sim.pupilSize)
sim.config.wfss[0].pxlsPerSubap = 10
Next, the system is initialised, this entails calculating various parameters in the system sub-modules, so must be done after changing some simulation parameters::
sim.aoinit()
DM Interation and command matrices are calculated now. If ``sim.config.sim.simName`` is not ``None``, then these matrices will be saved in ``data/simName`` (data will be saved here also in a time-stamped directory)::
sim.makeIMat()
Finally, the loop is run with the command::
sim.aoloop()
Some output will be printed to the console. After the loop has finished, data specified to be saved in the config file will be saved to ``data/simName`` (if it is not set to ``None``). Data can also be accessed from the simulation class, e.g. ``sim.allSlopes``, ``sim.longStrehl``
:Author:
Andrew Reeves
'''
# standard python imports
import datetime
import os
import time
import traceback
from multiprocessing import Process, Queue
from argparse import ArgumentParser
import shutil
import numpy
#Use pyfits or astropy for fits file handling
try:
from astropy.io import fits
except ImportError:
try:
import pyfits as fits
except ImportError:
raise ImportError("soapy requires either pyfits or astropy")
import aotools
#sim imports
from . import atmosphere, logger, wfs, DM, reconstruction, SCI, confParse, interp
#xrange now just "range" in python3.
#Following code means fastest implementation used in 2 and 3
try:
xrange
except NameError:
xrange = range
[docs]class Sim(object):
"""
The soapy Simulation class.
This class holds all configuration information, data and control
methods of the simulation. It contains high level methods dealing with
initialising all component objects, making reconstructor control
matrices, running the loop and saving data after the loop has run.
Can be sub-classed and the 'aoloop' method overwritten for different loops
to be used
Args:
configFile (string): The filename of the AO configuration file
"""
def __init__(self, configFile=None):
if not configFile:
configFile = "conf/testConf.py"
self.readParams(configFile)
#logger.info("Loaded config file successfully!")
self.guiQueue = None
self.go = False
[docs] def readParams(self, configFile=None):
"""
Reads configuration file parameters
Calls the radParams function in confParse to read, parse and if required
set reasonable defaults to AO parameters
"""
if configFile:
self.configFile = configFile
self.config = confParse.loadSoapyConfig(self.configFile)
logger.statusMessage(
1, 1,"Loaded configuration file successfully!" )
[docs] def setLoggingLevel(self, level):
"""
sets which messages are printed from logger.
if logging level is set to 0, nothing is printed. if set to 1, only
warnings are printed. if set to 2, warnings and info is printed. if set
to 3 detailed debugging info is printed.
parameters:
level (int): the desired logging level
"""
logger.setLoggingLevel(level)
[docs] def aoinit(self):
'''
Initialises all simulation objects.
Initialises and passes relevant data to sim objects.
This does important pre-run tasks, such as creating or
loading phase screens, determining WFS geometry,
setting propagation modes and pre-allocating data arrays
used later in the simulation.
'''
# Read params if they haven't been read before
try:
self.config.sim.pupilSize
except:
self.readParams()
logger.setLoggingLevel(self.config.sim.verbosity)
logger.setLoggingFile(self.config.sim.logfile)
logger.info("Starting Sim: {}".format(self.getTimeStamp()))
# Calculate some params from read ones
self.config.calcParams()
# Init Pupil Mask
logger.info("Creating mask...")
self.mask = make_mask(self.config)
self.atmos = atmosphere.atmos(self.config)
# Find if WFSs should each have own process
if self.config.sim.wfsMP:
logger.info("Setting fftwThreads to 1 as WFS MP")
for nwfs in xrange(self.config.sim.nGS):
self.config.wfss[nwfs].fftwThreads = 1
self.runWfs = self.runWfs_MP
else:
self.runWfs = self.runWfs_noMP
# Init WFSs
logger.info("Initialising WFSs....")
self.wfss = {}
self.config.sim.totalWfsData = 0
self.wfsFrameNo = numpy.zeros(self.config.sim.nGS)
for nwfs in xrange(self.config.sim.nGS):
try:
wfsClass = getattr(wfs, self.config.wfss[nwfs].type)
except AttributeError:
raise confParse.ConfigurationError(
"No WFS of type {} found.".format(
self.config.wfss[nwfs].type))
self.wfss[nwfs] = wfsClass(
self.config, n_wfs=nwfs, mask=self.mask)
self.config.wfss[nwfs].dataStart = self.config.sim.totalWfsData
self.config.sim.totalWfsData += self.wfss[nwfs].n_measurements
logger.info("WFS {0}: {1} measurements".format(nwfs,
self.wfss[nwfs].n_measurements))
# Init DMs
logger.info("Initialising {0} DMs...".format(self.config.sim.nDM))
self.dms = {}
self.dmActCommands = {}
self.config.sim.totalActs = 0
self.dmShape = numpy.zeros([self.config.sim.simSize]*2)
self.dmAct1 = []
for dm in xrange(self.config.sim.nDM):
self.dmAct1.append(self.config.sim.totalActs)
try:
dmObj = getattr(DM, self.config.dms[dm].type)
except AttributeError:
raise confParse.ConfigurationError("No DM of type {} found".format(self.config.dms[dm].type))
self.dms[dm] = dmObj(
self.config, n_dm=dm, wfss=self.wfss,
mask=self.mask
)
self.dmActCommands[dm] = numpy.empty(
(self.config.sim.nIters, self.dms[dm].n_acts))
self.dmAct1.append(self.config.sim.totalActs)
self.config.sim.totalActs += self.dms[dm].n_acts
logger.info("DM %d: %d active actuators"%(dm,self.dms[dm].n_acts))
logger.info("%d total DM Actuators"%self.config.sim.totalActs)
# Init Reconstructor
logger.info("Initialising Reconstructor...")
try:
reconObj = getattr(reconstruction, self.config.recon.type)
except AttributeError:
raise confParse.ConfigurationError("No reconstructor of type {} found.".format(self.config.recon.type))
self.recon = reconObj(
self.config, self.dms, self.wfss, self.atmos,
self.runWfs
)
# Init Science Cameras
logger.info("Initialising {0} Science Cams...".format(self.config.sim.nSci))
self.sciCams = {}
self.sciImgs = {}
self.sciImgNo=0
for nSci in xrange(self.config.sim.nSci):
try:
sciObj = getattr(SCI, self.config.scis[nSci].type)
except AttributeError:
raise confParse.ConfigurationError("No science camera of type {} found".format(self.config.scis[nSci].type))
self.sciCams[nSci] = sciObj(
self.config, nSci=nSci, mask=self.mask
)
self.sciImgs[nSci] = numpy.zeros( [self.config.scis[nSci].pxls]*2 )
# Init data storage
logger.info("Initialise Data Storage...")
self.initSaveData()
# Init simulation
#Circular buffers to hold loop iteration correction data
self.slopes = numpy.zeros((self.config.sim.totalWfsData))
self.closed_correction = numpy.zeros((
self.config.sim.nDM, self.config.sim.scrnSize, self.config.sim.scrnSize
))
self.open_correction = self.closed_correction.copy()
self.dmCommands = numpy.zeros(self.config.sim.totalActs)
self.buffer = DelayBuffer()
self.iters = 0
# Init performance tracking
self.Twfs = 0
self.Tlgs = 0
self.Tdm = 0
self.Tsci = 0
self.Trecon = 0
self.Timat = 0
self.Tatmos = 0
logger.info("Initialisation Complete!")
[docs] def makeIMat(self,forceNew=False, progressCallback=None):
"""
Creates interaction and control matrices for simulation reconstruction
Makes and inverts Interaction matrices for each DM in turn to
create a DM control Matrix for each DM.
Each DM's control Matrix is independent of the others,
so care must be taken so DM correction modes do not "overlap".
Some reconstruction modes may require WFS frames to be taken for the
creation of a control matrix. Depending on set parameters,
can load previous control and interaction matrices.
Args:
forceNew (bool): if true, will force making of new iMats and cMats, otherwise will attempt to load previously made matrices from same simName
progressCallback (func): function called to report progress of interaction matrix construction
"""
t = time.time()
logger.info("Making interaction Matrices...")
if forceNew:
loadIMat=False
loadCMat=False
else:
if self.config.sim.simName==None:
loadIMat=False
loadCMat=False
else:
loadIMat=True
loadCMat=True
self.recon.makeCMat(loadIMat=loadIMat,loadCMat=loadCMat,
callback=self.addToGuiQueue, progressCallback=progressCallback)
# Now know valid actuators for each DM, can get the index of the each DM in the command vector
self.dmAct1 = []
self.config.sim.totalActs = 0
for dm in self.dms.values():
self.dmAct1.append(self.config.sim.totalActs)
self.config.sim.totalActs += dm.n_valid_actuators
self.dmCommands = numpy.zeros(self.config.sim.totalActs)
self.Timat+= time.time() - t
[docs] def runWfs_noMP(self, scrns = None, dmShape=None, wfsList=None,
loopIter=None):
"""
Runs all WFSs
Runs a single frame for each WFS in wfsList, passing the given phase screens and optional dmShape (if WFS in closed loop). The WFSs are only read out if the wfs frame time co-incides with the WFS frame rate, else old slopes are provided. If iter is not given, then all WFSs are run and read out. If LGSs are present it will also deals with LGS propagation. Finally, the slopes from all WFSs are returned.
Args:
scrns (list): List of phase screens passing over telescope
dmShape (ndarray, optional): 2-dim array of the total corrector shape
wfsList (list, optional): A list of the WFSs to be run
loopIter (int, optional): The loop iteration number
Returns:
ndarray: The slope data return from the WFS frame (may not be actual slopes if WFS other than SH used)
"""
t_wfs = time.time()
if scrns != None:
self.scrns=scrns
if wfsList==None:
wfsList=range(self.config.sim.nGS)
slopesSize = 0
for nwfs in wfsList:
slopesSize+=self.wfss[nwfs].n_measurements
slopes = numpy.zeros( (slopesSize) )
s = 0
for nwfs in wfsList:
#check if due to read out WFS
if loopIter:
read=False
if (int(float(self.config.sim.loopTime*loopIter)
/self.config.wfss[nwfs].exposureTime)
!= self.wfsFrameNo[nwfs]):
self.wfsFrameNo[nwfs]+=1
read=True
else:
read = True
slopes[s:s+self.wfss[nwfs].n_measurements] = \
self.wfss[nwfs].frame(self.scrns, dmShape, read=read)
s += self.wfss[nwfs].n_measurements
self.Twfs+=time.time()-t_wfs
return slopes
[docs] def runWfs_MP(self, scrns=None, dmShape=None, wfsList=None, loopIter=None):
"""
Runs all WFSs using multiprocessing
Runs a single frame for each WFS in wfsList, passing the given phase
screens and optional dmShape (if WFS in closed loop). If LGSs are
present it will also deals with LGS propagation. Finally, the slopes
from all WFSs are returned. Each WFS is allocated a separate process
to complete the frame, giving a significant increase in speed,
especially for computationally heavy WFSs.
Args:
scrns (list): List of phase screens passing over telescope
dmShape (ndarray, optional): 2-dimensional array of the total corrector shape
wfsList (list, optional): A list of the WFSs to be run, if not set, runs all WFSs
loopIter (int, optional): The loop iteration number
Returns:
ndarray: The slope data return from the WFS frame (may not be actual slopes if WFS other than SH used)
"""
t_wfs = time.time()
if scrns != None:
self.scrns=scrns
if wfsList==None:
wfsList=range(self.config.sim.nGS)
slopesSize = 0
for nwfs in wfsList:
slopesSize+=self.wfss[nwfs].n_measurements
slopes = numpy.zeros( (slopesSize) )
wfsProcs = []
wfsQueues = []
s = 0
for proc in xrange(len(wfsList)):
nwfs = wfsList[proc]
# check if due to read out WFS
if loopIter:
read=False
if (int(float(self.config.sim.loopTime*loopIter)
/self.config.wfss[nwfs].exposureTime)
!= self.wfsFrameNo[nwfs]):
self.wfsFrameNo[nwfs]+=1
read = True
else:
read = True
wfsQueues.append(Queue())
wfsProcs.append(Process(target=multiWfs,
args=[ self.scrns, self.wfss[nwfs], dmShape, read,
wfsQueues[proc]])
)
wfsProcs[proc].daemon = True
wfsProcs[proc].start()
for proc in xrange(len(wfsList)):
nwfs = wfsList[proc]
(slopes[s:s+self.wfss[nwfs].n_measurements],
self.wfss[nwfs].wfsDetectorPlane,
self.wfss[nwfs].uncorrectedPhase,
lgsPsf) = wfsQueues[proc].get()
if numpy.any(lgsPsf)!=None:
self.wfss[nwfs].LGS.psf1 = lgsPsf
wfsProcs[proc].join()
s += self.wfss[nwfs].n_measurements
self.Twfs+=time.time()-t_wfs
return slopes
[docs] def runDM(self, dmCommands, closed=True):
"""
Runs a single frame of the deformable mirrors
Calculates the total combined shape of all deformable mirrors (DMs), given an array of DM commands. DM commands correspond to shapes generated during the making of interaction matrices, the final DM shape for each DM is a combination of these. The DM commands will have already been calculated by the systems reconstructor.
Args:
dmCommands (ndarray): an array of dm commands corresponding to dm shapes
closed (bool): if True, indicates to DM that slopes are residual errors from previous frame, if False, slopes correspond to total phase error over pupil.
Returns:
ndArray: the combined DM shape
"""
t = time.time()
self.dmShapes = []
if closed:
correction_buffer = self.closed_correction
else:
correction_buffer = self.open_correction
for dm in xrange(self.config.sim.nDM):
if self.config.dms[dm].closed == closed:
correction_buffer[dm] = self.dms[dm].dmFrame(
dmCommands[ self.dmAct1[dm]:
self.dmAct1[dm]+self.dms[dm].n_valid_actuators])
self.Tdm += time.time() - t
return correction_buffer
[docs] def runSciCams(self, dmShape=None):
"""
Runs a single frame of the science Cameras
Calculates the image recorded by all science cameras in the system for the current phase over the telescope one frame. If a dmShape is present (which it usually will be in AO!) this correction is applied to the science phase before the image is calculated.
Args:
correction (list or ndarray, optional): An array of the combined system DM shape to correct the science path. If not given science cameras are in open loop.
"""
t = time.time()
self.sciImgNo +=1
for sci in xrange(self.config.sim.nSci):
self.sciImgs[sci] += self.sciCams[sci].frame(self.scrns, dmShape)
# Normalise long exposure psf
#self.sciImgs[sci] /= self.sciImgs[sci].sum()
self.sciCams[sci].longExpStrehl = (
self.sciImgs[sci].max()/
self.sciImgs[sci].sum()/
self.sciCams[sci].psfMax)
self.Tsci +=time.time()-t
[docs] def loopFrame(self):
"""
Runs a single from of the entire AO system.
Moves the atmosphere, runs the WFSs, finds the corrective DM shape and finally runs the science cameras. This can be called over and over to form the "loop"
"""
# Get next phase screens
t = time.time()
self.scrns = self.atmos.moveScrns()
self.Tatmos = time.time()-t
# Run Loop...
########################################
# Get dmCommands from reconstructor
if self.config.sim.nDM:
self.dmCommands[:] = self.recon.reconstruct(self.slopes)
# Delay the dmCommands if loopDelay is configured
self.dmCommands = self.buffer.delay(self.dmCommands, self.config.sim.loopDelay)
# Get dmShape from closed loop DMs
self.closed_correction = self.runDM(
self.dmCommands, closed=True)
# Run WFS, with closed loop DM shape applied
self.slopes = self.runWfs(dmShape=self.closed_correction,
loopIter=self.iters)
# Get DM shape for open loop DMs
self.open_correction = self.runDM(self.dmCommands,
closed=False)
# Pass whole combined DM shapes to science target
self.combinedCorrection = self.open_correction + self.closed_correction
self.runSciCams(self.combinedCorrection)
# Save Data
self.storeData(self.iters)
self.printOutput(self.iters, strehl=True)
self.addToGuiQueue()
self.iters += 1
[docs] def aoloop(self):
"""
Main AO Loop
Runs a WFS iteration, reconstructs the phase, runs DMs and finally the science cameras. Also makes some nice output to the console and can add data to the Queue for the GUI if it has been requested. Repeats for nIters.
"""
self.go = True
try:
while self.iters < self.config.sim.nIters:
if self.go:
self.loopFrame()
else:
break
except KeyboardInterrupt:
self.go = False
logger.info("\nSim exited by user\n")
#Finally save data after loop is over.
self.saveData()
self.finishUp()
[docs] def reset_loop(self):
"""
Resets parameters in the system to zero, to restart an AO run wihtout reinitialising
"""
self.iters = 0
self.slopes[:] = 0
self.dmCommands[:] = 0
self.longStrehl[:] = 0
self.recon.reset()
for sci in self.sciImgs.values(): sci[:] = 0
for dm in self.dms.values(): dm.reset()
[docs] def finishUp(self):
"""
Prints a message to the console giving timing data. Used on sim end.
"""
print('\n')
if hasattr(self, "longStrehl") and (self.longStrehl is not None):
for sci_n in range(self.config.sim.nSci):
print("Science Camera {}: Long Exposure Strehl Ratio: {:0.2f}".format(sci_n, self.longStrehl[sci_n][self.iters-1]))
print("\n\nTime moving atmosphere: %0.2f"%self.Tatmos)
print("Time making IMats and CMats: %0.2f"%self.Timat)
print("Time in WFS: %0.2f"%self.Twfs)
print ("\t of which time spent in : %0.2f"%self.Tlgs)
print("Time in Reconstruction: %0.2f"%self.recon.Trecon)
print("Time in DM: %0.2f"%self.Tdm)
print("Time making science image: %0.2f"%self.Tsci)
print("\n")
[docs] def initSaveData(self):
'''
Initialise data structures used for data saving.
Initialise the data structures which will be used to store data which will be saved or analysed once the simulation has ended. If the ``simName = None``, no data is saved, other wise a directory called ``simName`` is created, and data from simulation runs are saved in a time-stamped directory inside this.
'''
# Initialise the FITS header to use. Store in `config.sim`
self.config.sim.saveHeader = self.makeSaveHeader()
if self.config.sim.simName!=None:
self.path = self.config.sim.simName +"/"+self.timeStamp
# make sure a different directory used by sleeping
time.sleep(1)
try:
os.mkdir(self.path)
except OSError:
os.mkdir(self.config.sim.simName)
os.mkdir(self.path)
#Init WFS FP Saving
if self.config.sim.saveWfsFrames:
os.mkdir(self.path+"/wfsFPFrames/")
shutil.copyfile(self.configFile, self.path+"/conf.py" )
# Init Strehl Saving
if self.config.sim.nSci>0:
self.instStrehl = numpy.zeros(
(self.config.sim.nSci, self.config.sim.nIters) )
self.longStrehl = numpy.zeros(
(self.config.sim.nSci, self.config.sim.nIters) )
# Init science WFE saving
self.WFE = numpy.zeros(
(self.config.sim.nSci, self.config.sim.nIters)
)
#Init science residual phase saving
self.sciPhase = []
if self.config.sim.saveSciRes and self.config.sim.nSci>0:
for sci in xrange(self.config.sim.nSci):
self.sciPhase.append(
numpy.empty(
(self.config.sim.nIters, self.config.sim.simSize,
self.config.sim.simSize)))
#Init WFS slopes data saving
if self.config.sim.saveSlopes:
self.allSlopes = numpy.empty(
(self.config.sim.nIters, self.config.sim.totalWfsData) )
else:
self.allSlopes = None
#Init DM Command Data saving
if self.config.sim.saveDmCommands:
ttActs = 0
self.allDmCommands = numpy.empty( (self.config.sim.nIters, ttActs+self.config.sim.totalActs))
else:
self.allDmCommands = None
#Init LGS PSF Saving
if self.config.sim.saveLgsPsf:
self.lgsPsfs = []
for lgs in xrange(self.config.sim.nGS):
if self.config.wfss[lgs].lgs and self.config.wfss[lgs].lgs.uplink:
self.lgsPsfs.append(
numpy.empty((self.config.sim.nIters,
self.wfss[lgs].lgs.nOutPxls,
self.wfss[lgs].lgs.nOutPxls))
)
self.lgsPsfs = numpy.array(self.lgsPsfs)
else:
self.lgsPsfs = None
#Init Instantaneous PSF saving
if self.config.sim.nSci>0 and self.config.sim.saveInstPsf==True:
self.sciImgsInst = {}
for sci in xrange(self.config.sim.nSci):
self.sciImgsInst[sci] = numpy.zeros([self.config.sim.nIters,self.config.scis[sci].pxls,self.config.scis[sci].pxls])
#Init Instantaneous electric field
if self.config.sim.nSci>0 and self.config.sim.saveInstScieField==True:
self.scieFieldInst = {}
for sci in xrange(self.config.sim.nSci):
self.scieFieldInst[sci] = numpy.zeros(([self.config.sim.nIters,self.config.scis[sci].pxls,self.config.scis[sci].pxls]), dtype=complex )
[docs] def storeData(self, i):
"""
Stores data from each frame in an appropriate data structure.
Called on each frame to store the simulation data into various data structures corresponding to different data sources in the system.
Args:
i (int): The system iteration number
"""
if self.config.sim.saveSlopes:
self.allSlopes[i] = self.slopes
if self.config.sim.saveDmCommands:
act=0
self.allDmCommands[i,act:] = self.dmCommands
#Quick bodge to save lgs psfs as images
if self.config.sim.saveLgsPsf:
lgs=0
for nwfs in xrange(self.config.sim.nGS):
if self.config.wfss[nwfs].lgs and self.config.wfss[nwfs].lgs.uplink:
self.lgsPsfs[lgs, i] = self.wfss[nwfs].lgs.psf
lgs+=1
if self.config.sim.nSci>0:
for sci in xrange(self.config.sim.nSci):
self.instStrehl[sci,i] = self.sciCams[sci].instStrehl
self.longStrehl[sci,i] = self.sciCams[sci].longExpStrehl
# # Record WFE residual
# res = self.sciCams[sci].los.residual
# # Remove piston first
# res -= res.sum()/self.mask.sum()
# res *= self.mask
# self.WFE[sci,i] = numpy.sqrt(numpy.mean(numpy.square(res)))
self.WFE[sci, i] = self.sciCams[sci].calc_wavefronterror()
if self.config.sim.saveSciRes:
for sci in xrange(self.config.sim.nSci):
self.sciPhase[sci][i] = self.sciCams[sci].residual
if self.config.sim.simName!=None:
if self.config.sim.saveWfsFrames:
for nwfs in xrange(self.config.sim.nGS):
fits.writeto(
self.path+"/wfsFPFrames/wfs-%d_frame-%d.fits"%(nwfs,i),
self.wfss[nwfs].wfsDetectorPlane,
header=self.config.sim.saveHeader)
#Save Instantaneous PSF
if self.config.sim.nSci>0 and self.config.sim.saveInstPsf==True:
for sci in xrange(self.config.sim.nSci):
self.sciImgsInst[sci][i,:,:] = self.sciCams[sci].detector
#Save Instantaneous electric field
if self.config.sim.nSci>0 and self.config.sim.saveInstScieField==True:
for sci in xrange(self.config.sim.nSci):
self.scieFieldInst[sci][self.iters,:,:] = self.sciCams[sci].focalPlane_efield
[docs] def saveData(self):
"""
Saves all recorded data to disk
Called once simulation has ended to save the data recorded during the simulation to disk in the directories created during initialisation.
"""
if self.config.sim.simName!=None:
if self.config.sim.saveSlopes:
fits.writeto(
self.path+"/slopes.fits", self.allSlopes,
header=self.config.sim.saveHeader, clobber=True)
if self.config.sim.saveDmCommands:
fits.writeto(
self.path+"/dmCommands.fits",
self.allDmCommands, header=self.config.sim.saveHeader,
clobber=True)
if self.config.sim.saveLgsPsf:
fits.writeto(
self.path+"/lgsPsf.fits", self.lgsPsfs,
header=self.config.sim.saveHeader, clobber=True)
if self.config.sim.saveWfe:
fits.writeto(
self.path+"/WFE.fits", self.WFE,
header=self.config.sim.saveHeader, clobber=True)
if self.config.sim.saveStrehl:
fits.writeto(
self.path+"/instStrehl.fits", self.instStrehl,
header=self.config.sim.saveHeader, clobber=True)
fits.writeto(
self.path+"/longStrehl.fits", self.longStrehl,
header=self.config.sim.saveHeader, clobber=True)
if self.config.sim.saveSciRes:
for i in xrange(self.config.sim.nSci):
fits.writeto(self.path+"/sciResidual_%02d.fits"%i,
self.sciPhase[i],
header=self.config.sim.saveHeader,
clobber=True)
if self.config.sim.saveSciPsf:
for i in xrange(self.config.sim.nSci):
fits.writeto(self.path+"/sciPsf_%02d.fits"%i,
self.sciImgs[i],
header=self.config.sim.saveHeader,
clobber=True )
if self.config.sim.saveInstPsf:
for i in xrange(self.config.sim.nSci):
fits.writeto(self.path+"/sciPsfInst_%02d.fits"%i,
self.sciImgsInst[i],
header=self.config.sim.saveHeader,
clobber=True )
if self.config.sim.saveInstScieField:
for i in xrange(self.config.sim.nSci):
fits.writeto(self.path+"/scieFieldInst_%02d_real.fits"%i,
self.scieFieldInst[i].real,
header=self.config.sim.saveHeader,
clobber=True )
if self.config.sim.saveInstScieField:
for i in xrange(self.config.sim.nSci):
fits.writeto(self.path+"/scieFieldInst_%02d_imag.fits"%i,
self.scieFieldInst[i].imag,
header=self.config.sim.saveHeader,
clobber=True )
[docs] def getTimeStamp(self):
"""
Returns a formatted timestamp
Returns:
string: nicely formatted timestamp of current time.
"""
self.time = datetime.datetime.now()
return self.time.strftime("%Y-%m-%d-%H-%M-%S")
[docs] def printOutput(self, iter, strehl=False):
"""
Prints simulation information to the console
Called on each iteration to print information about the current simulation, such as current strehl ratio, to the console. Still under development
Args:
label(str): Simulation Name
iter(int): simulation frame number
strehl(float, optional): current strehl ration if science cameras are present to record it.
"""
if self.config.sim.simName:
string = self.config.sim.simName.split("/")[-1]
else:
string = self.config.filename.split("/")[-1].split(".")[0]
if strehl:
string += " Strehl -- "
for sci in xrange(self.config.sim.nSci):
string += "sci_{0}: inst {1:.2f}, long {2:.2f} ".format(
sci, self.sciCams[sci].instStrehl,
self.sciCams[sci].longExpStrehl)
logger.statusMessage(iter+1, self.config.sim.nIters, string )
[docs] def addToGuiQueue(self):
"""
Adds data to a Queue object provided by the soapy GUI.
The soapy GUI doesn't need to plot every frame from the simulation. When it wants a frame, it will request if by setting ``waitingPlot = True``. As this function is called on every iteration, data is passed to the GUI only if ``waitingPlot = True``. This allows efficient and abstracted interaction between the GUI and the simulation
"""
if self.guiQueue != None:
if self.waitingPlot:
guiPut = []
wfsFocalPlane = {}
wfsPhase = {}
lgsPsf = {}
for i in xrange(self.config.sim.nGS):
wfsFocalPlane[i] = self.wfss[i].wfsDetectorPlane.copy().astype("float32")
try:
wfsPhase[i] = self.wfss[i].uncorrectedPhase
except AttributeError:
wfsPhase[i] = None
pass
try:
lgsPsf[i] = self.wfss[i].lgs.psf.copy()
except AttributeError:
lgsPsf[i] = None
pass
dmShape = {}
for i in xrange(self.config.sim.nDM):
try:
dmShape[i] = self.dms[i].dm_shape.copy()#*self.mask
except AttributeError:
dmShape[i] = None
sciImg = {}
residual = {}
instSciImg = {}
for i in xrange(self.config.sim.nSci):
try:
sciImg[i] = self.sciImgs[i].copy()
except AttributeError:
sciImg[i] = None
try:
instSciImg[i] = self.sciCams[i].detector.copy()
except AttributeError:
instSciImg[i] = None
try:
residual[i] = self.sciCams[i].los.residual.copy()*self.mask
except AttributeError:
residual[i] = None
guiPut = { "wfsFocalPlane":wfsFocalPlane,
"wfsPhase": wfsPhase,
"lgsPsf": lgsPsf,
"dmShape": dmShape,
"sciImg": sciImg,
"instSciImg": instSciImg,
"residual": residual }
self.guiLock.lock()
try:
self.guiQueue.put_nowait(guiPut)
except:
self.guiLock.unlock()
traceback.print_exc()
self.guiLock.unlock()
self.waitingPlot = False
[docs]def make_mask(config):
"""
Generates a Soapy pupil mask
Parameters:
config (SoapyConfig): Config object describing Soapy simulation
Returns:
ndarray: 2-d pupil mask
"""
if config.tel.mask == "circle":
mask = aotools.circle(config.sim.pupilSize / 2.,
config.sim.simSize)
if config.tel.obsDiam != None:
mask -= aotools.circle(
config.tel.obsDiam * config.sim.pxlScale / 2.,
config.sim.simSize
)
elif isinstance(config.tel.mask, str):
maskHDUList = fits.open(config.tel.mask)
mask = maskHDUList[0].data.copy()
maskHDUList.close()
logger.info('load mask "{}", of size: {}'.format(config.tel.mask, mask.shape))
if not numpy.array_equal(mask.shape, (config.sim.pupilSize,) * 2):
# interpolate mask to pupilSize if not that size already
mask = numpy.round(interp.zoom(mask, config.sim.pupilSize))
else:
mask = config.tel.mask.copy()
# Check its size is compatible. If its the pupil size, pad to sim size
if (not numpy.array_equal(mask.shape, (config.sim.pupilSize,)*2)
and not numpy.array_equal(mask.shape, (config.sim.simSize,)*2) ):
raise ValueError("Mask Shape {} not compatible. Should be either `pupilSize` or `simSize`".format(mask.shape))
if mask.shape != (config.sim.simSize, )*2:
mask = numpy.pad(
mask, config.sim.simPad, mode="constant")
return mask
# Functions used by MP stuff
[docs]def multiWfs(scrns, wfsObj, dmShape, read, queue):
"""
Function to run the WFS in multiprocessing mode.
Function is called by each of the new WFS processes spawned to run each WFS. Does the same job as the sim runWfs_noMP method of running LGS, then getting slopes from each WFS.
Args:
scrns (list): list of the phase screens over the WFS
wfsObj (WFS object): the WFS object being run
dmShape (ndArray): shape of system DMs for WFS phase correction
queue (Queue object): a multiprocessing Queue object used to pass data back to host process.
"""
slopes = wfsObj.frame(scrns, dmShape, read=read)
if wfsObj.LGS:
lgsPsf = wfsObj.LGS.psf1
else:
lgsPsf = None
res = [slopes, wfsObj.wfsDetectorPlane, wfsObj.uncorrectedPhase, lgsPsf]
queue.put(res)
#######################
#Control Functions
######################
[docs]class DelayBuffer(list):
'''
A delay buffer.
Each time delay() is called on the buffer, the input value is stored.
If the buffer is larger than count, the oldest value is removed and returned.
If the buffer is not yet full, a zero of similar shape as the last input
is returned.
'''
[docs] def delay(self, value, count):
self.append(value)
if len(self) <= count:
result = value*0.0
else:
for _ in range(len(self)-count):
result = self.pop(0)
return result
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("configFile",nargs="?",action="store")
args = parser.parse_args()
if args.configFile != None:
confFile = args.configFile
else:
confFile = "conf/testConf.py"
sim = Sim(confFile)
print("AOInit...")
sim.aoinit()
sim.makeIMat()
sim.aoloop()
from ._version import get_versions
__version__ = get_versions()['version']
del get_versions