COMPASS  5.0.0
End-to-end AO simulation tool using GPU acceleration
shesha.init.dm_init Namespace Reference

Initialization of a Dms object. More...

Functions

Dms dm_init (carmaWrap_context context, List[conf.Param_dm] p_dms, conf.Param_tel p_tel, conf.Param_geom p_geom, List[conf.Param_wfs] p_wfss=None, bool keepAllActu=False)
 Create and initialize a Dms object on the gpu. More...
 
def dm_init_standalone (carmaWrap_context context, list p_dms, conf.Param_geom p_geom, diam=1., cobs=0., pupAngle=0., wfs_xpos=[0], wfs_ypos=[0])
 Create and initialize a Dms object on the gpu. More...
 
def make_pzt_dm (conf.Param_dm p_dm, conf.Param_geom p_geom, float cobs, float pupAngle, bool keepAllActu=False)
 Compute the actuators positions and the influence functions for a pzt DM. More...
 
def init_custom_dm (conf.Param_dm p_dm, conf.Param_geom p_geom, float diam)
 Read Fits for influence pzt fonction and form. More...
 
def make_tiptilt_dm (conf.Param_dm p_dm, int patchDiam, conf.Param_geom p_geom, float diam)
 Compute the influence functions for a tip-tilt DM. More...
 
None make_kl_dm (conf.Param_dm p_dm, int patchDiam, conf.Param_geom p_geom, float cobs)
 Compute the influence function for a Karhunen-Loeve DM. More...
 
def comp_dmgeom (conf.Param_dm p_dm, conf.Param_geom p_geom)
 Compute the geometry of a DM : positions of actuators and influence functions. More...
 
def correct_dm (context, Dms dms, list p_dms, conf.Param_controller p_controller, conf.Param_geom p_geom, np.ndarray imat=None, dict dataBase={}, bool use_DB=False)
 Correct the geometry of the DMs using the imat (filter unseen actuators) More...
 
def makePetalDm (p_dm, p_geom, pupAngleDegree)
 makePetalDm(p_dm, p_geom, pupAngleDegree) More...
 
def make_petal_dm_core (pupImage, pupAngleDegree)
 <pupImage> : image of the pupil More...
 
def build_petals (nbSeg, pupAngleDegree, i0, j0, npt)
 

Detailed Description

Initialization of a Dms object.

Author
COMPASS Team https://github.com/ANR-COMPASS
Version
5.0.0
Date
2020/05/18

This file is part of COMPASS https://anr-compass.github.io/compass/

Copyright (C) 2011-2019 COMPASS Team https://github.com/ANR-COMPASS All rights reserved. Distributed under GNU - LGPL

COMPASS is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.

COMPASS: End-to-end AO simulation tool using GPU acceleration The COMPASS platform was designed to meet the need of high-performance for the simulation of AO systems.

The final product includes a software package for simulating all the critical subcomponents of AO, particularly in the context of the ELT and a real-time core based on several control approaches, with performances consistent with its integration into an instrument. Taking advantage of the specific hardware architecture of the GPU, the COMPASS tool allows to achieve adequate execution speeds to conduct large simulation campaigns called to the ELT.

The COMPASS platform can be used to carry a wide variety of simulations to both testspecific components of AO of the E-ELT (such as wavefront analysis device with a pyramid or elongated Laser star), and various systems configurations such as multi-conjugate AO.

COMPASS 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with COMPASS. If not, see https://www.gnu.org/licenses/lgpl-3.0.txt.

Function Documentation

◆ build_petals()

def shesha.init.dm_init.build_petals (   nbSeg,
  pupAngleDegree,
  i0,
  j0,
  npt 
)

Definition at line 993 of file dm_init.py.

993  # building coordinate maps
994  esoOffsetAngle = -np.pi / 6 # -30°, ESO definition.
995  x = np.arange(npt) - i0
996  y = np.arange(npt) - j0
997  X, Y = np.meshgrid(x, y, indexing='ij')
998  theta = (np.arctan2(Y, X) - rot + 2 * np.pi - esoOffsetAngle) % (2 * np.pi)
999 
1000  # Compute separation angle between segments: start and end.
1001  angleStep = 2 * np.pi / nbSeg
1002  startAngle = np.arange(nbSeg) * angleStep
1003  endAngle = np.roll(startAngle, -1)
1004  endAngle[-1] = 2 * np.pi # last angle is 0.00 and must be replaced by 2.pi
1005  petalMap = np.zeros((npt, npt), dtype=int)
1006  for i in range(nbSeg):
1007  nn = np.where(np.logical_and(theta >= startAngle[i], theta < endAngle[i]))
1008  petalMap[nn] = i
1009  return petalMap
Here is the caller graph for this function:

◆ comp_dmgeom()

def shesha.init.dm_init.comp_dmgeom ( conf.Param_dm  p_dm,
conf.Param_geom  p_geom 
)

Compute the geometry of a DM : positions of actuators and influence functions.

:parameters: dm (Param_dm) : dm settings

geom (Param_geom) : geom settings

Definition at line 739 of file dm_init.py.

739  mpup_dim = p_geom._mpupil.shape[0]
740 
741  if (dm_dim < mpup_dim):
742  offs = (mpup_dim - dm_dim) // 2
743  else:
744  offs = 0
745  mpup_dim = dm_dim
746 
747  indgen = np.tile(np.arange(smallsize, dtype=np.int32), (smallsize, 1))
748 
749  tmpx = np.tile(indgen, (nact, 1, 1))
750  tmpy = np.tile(indgen.T, (nact, 1, 1))
751 
752  tmpx += offs + p_dm._i1[:, None, None]
753  tmpy += offs + p_dm._j1[:, None, None]
754 
755  tmp = tmpx + mpup_dim * tmpy
756 
757  # bug in limit of def zone -10 destoe influpos for all actuator
758  tmp[tmpx < 0] = mpup_dim * mpup_dim + 10 # -10
759  tmp[tmpy < 0] = mpup_dim * mpup_dim + 10 # -10
760  tmp[tmpx > dm_dim - 1] = mpup_dim * mpup_dim + 10 # -10
761  tmp[tmpy > dm_dim - 1] = mpup_dim * mpup_dim + 10 # -10
762  itmps = np.argsort(tmp.flatten()).astype(np.int32)
763  tmps = tmp.flatten()[itmps].astype(np.int32)
764  itmps = itmps[np.where(itmps > -1)]
765 
766  istart = np.zeros((mpup_dim * mpup_dim), dtype=np.int32)
767  npts = np.zeros((mpup_dim * mpup_dim), dtype=np.int32)
768 
769  tmps_unique, cpt = np.unique(tmps, return_counts=True)
770  if (tmps_unique > npts.size - 1).any():
771  tmps_unique = tmps_unique[:-1]
772  cpt = cpt[:-1]
773 
774  for i in range(tmps_unique.size):
775  npts[tmps_unique[i]] = cpt[i]
776  istart[1:] = np.cumsum(npts[:-1])
777 
778  p_dm._influpos = itmps[:np.sum(npts)].astype(np.int32)
779  # infludata = p_dm._influ.flatten()[p_dm._influpos]
780  # p_dm._influ = infludata[:,None,None]
781  # p_dm._influpos = p_dm._influpos / (smallsize * smallsize)
782  p_dm._ninflu = npts.astype(np.int32)
783  p_dm._influstart = istart.astype(np.int32)
784 
785  p_dm._i1 += offs
786  p_dm._j1 += offs
787 
788  # ninflu = np.zeros((istart.size * 2))
789  # ninflu[::2] = istart.astype(np.int32)
790  # ninflu[1::2] = npts.astype(np.int32)
791 
792  # p_dm._ninflu = ninflu
793 
794 
795 def correct_dm(context, dms: Dms, p_dms: list, p_controller: conf.Param_controller,
796  p_geom: conf.Param_geom, imat: np.ndarray = None, dataBase: dict = {},
797  use_DB: bool = False):
798  """Correct the geometry of the DMs using the imat (filter unseen actuators)
Here is the caller graph for this function:

◆ correct_dm()

def shesha.init.dm_init.correct_dm (   context,
Dms  dms,
list  p_dms,
conf.Param_controller  p_controller,
conf.Param_geom  p_geom,
np.ndarray   imat = None,
dict   dataBase = {},
bool   use_DB = False 
)

Correct the geometry of the DMs using the imat (filter unseen actuators)

:parameters: context (carmaWrap_context): context dms (Dms) : Dms object p_dms (list of Param_dm) : dms settings p_controller (Param_controller) : controller settings p_geom (Param_geom) : geom settings imat (np.ndarray) : interaction matrix dataBase (dict): dictionary containing paths to files to load use_DB (bool): dataBase use flag

Definition at line 811 of file dm_init.py.

811  if imat is not None:
812  resp = np.sqrt(np.sum(imat**2, axis=0))
813 
814  ndm = p_controller.ndm.size
815  inds = 0
816 
817  for nmc in range(ndm):
818  nm = p_controller.ndm[nmc]
819  nactu_nm = p_dms[nm]._ntotact
820  # filter actuators only in stackarray mirrors:
821  if (p_dms[nm].type == scons.DmType.PZT):
822  if "dm" in dataBase:
823  influpos, ninflu, influstart, i1, j1, ok = h5u.load_dm_geom_from_dataBase(
824  dataBase, nmc)
825  p_dms[nm].set_ntotact(ok.shape[0])
826  p_dms[nm].set_influ(p_dms[nm]._influ[:, :, ok.tolist()])
827  p_dms[nm].set_xpos(p_dms[nm]._xpos[ok])
828  p_dms[nm].set_ypos(p_dms[nm]._ypos[ok])
829  p_dms[nm]._influpos = influpos
830  p_dms[nm]._ninflu = ninflu
831  p_dms[nm]._influstart = influstart
832  p_dms[nm]._i1 = i1
833  p_dms[nm]._j1 = j1
834  else:
835  tmp = resp[inds:inds + p_dms[nm]._ntotact]
836  ok = np.where(tmp > p_dms[nm].thresh * np.max(tmp))[0]
837  nok = np.where(tmp <= p_dms[nm].thresh * np.max(tmp))[0]
838 
839  p_dms[nm].set_ntotact(ok.shape[0])
840  p_dms[nm].set_influ(p_dms[nm]._influ[:, :, ok.tolist()])
841  p_dms[nm].set_xpos(p_dms[nm]._xpos[ok])
842  p_dms[nm].set_ypos(p_dms[nm]._ypos[ok])
843  p_dms[nm].set_i1(p_dms[nm]._i1[ok])
844  p_dms[nm].set_j1(p_dms[nm]._j1[ok])
845 
846  comp_dmgeom(p_dms[nm], p_geom)
847  if use_DB:
848  h5u.save_dm_geom_in_dataBase(nmc, p_dms[nm]._influpos,
849  p_dms[nm]._ninflu,
850  p_dms[nm]._influstart, p_dms[nm]._i1,
851  p_dms[nm]._j1, ok)
852 
853  dim = max(p_dms[nm]._n2 - p_dms[nm]._n1 + 1, p_geom._mpupil.shape[0])
854  ninflupos = p_dms[nm]._influpos.size
855  n_npts = p_dms[nm]._ninflu.size
856  dms.remove_dm(nm)
857  dms.insert_dm(context, p_dms[nm].type, p_dms[nm].alt, dim,
858  p_dms[nm]._ntotact, p_dms[nm]._influsize, ninflupos, n_npts,
859  p_dms[nm].push4imat, 0, p_dms[nm].dx / p_geom._pixsize,
860  p_dms[nm].dy / p_geom._pixsize, p_dms[nm].theta, p_dms[nm].G,
861  context.active_device, nm)
862  dms.d_dms[nm].pzt_loadarrays(p_dms[nm]._influ, p_dms[nm]._influpos.astype(
863  np.int32), p_dms[nm]._ninflu, p_dms[nm]._influstart, p_dms[nm]._i1,
864  p_dms[nm]._j1)
865 
866  inds += nactu_nm
867  print("Done")
868 
869 
870 def makePetalDm(p_dm, p_geom, pupAngleDegree):
871  '''
872  makePetalDm(p_dm, p_geom, pupAngleDegree)
873 
Here is the call graph for this function:

◆ dm_init()

Dms shesha.init.dm_init.dm_init ( carmaWrap_context  context,
List[conf.Param_dm]  p_dms,
conf.Param_tel  p_tel,
conf.Param_geom  p_geom,
List[conf.Param_wfs]   p_wfss = None,
bool   keepAllActu = False 
)

Create and initialize a Dms object on the gpu.

:parameters: context (carmaWrap_context): context p_dms (list of Param_dms) : dms settings p_tel (Param_tel) : telescope settings p_geom (Param_geom) : geom settings p_wfss (list of Param_wfs) : wfs settings :return: Dms (Dms): Dms object

Definition at line 68 of file dm_init.py.

68  :return:
69  Dms: (Dms): Dms object
70  """
71  max_extent = 0
72  if (p_wfss is not None):
73  xpos_wfs = []
74  ypos_wfs = []
75  for i in range(len(p_wfss)):
76  xpos_wfs.append(p_wfss[i].xpos)
77  ypos_wfs.append(p_wfss[i].ypos)
78  else:
79  xpos_wfs = [0]
80  ypos_wfs = [0]
81  if (len(p_dms) != 0):
82  dms = Dms()
83  types_dm = [p_dm.type for p_dm in p_dms]
84  if scons.DmType.TT in types_dm:
85  first_TT = types_dm.index(scons.DmType.TT)
86  if np.any(np.array(types_dm[first_TT:]) != scons.DmType.TT):
87  raise RuntimeError("TT must be defined at the end of the dms parameters")
88 
89  for i in range(len(p_dms)):
90  max_extent = _dm_init(context, dms, p_dms[i], xpos_wfs, ypos_wfs, p_geom,
91  p_tel.diam, p_tel.cobs, p_tel.pupangle, max_extent,
92  keepAllActu=keepAllActu)
93 
94  return dms
95 
96 
Here is the call graph for this function:

◆ dm_init_standalone()

def shesha.init.dm_init.dm_init_standalone ( carmaWrap_context  context,
list  p_dms,
conf.Param_geom  p_geom,
  diam = 1.,
  cobs = 0.,
  pupAngle = 0.,
  wfs_xpos = [0],
  wfs_ypos = [0] 
)

Create and initialize a Dms object on the gpu.

:parameters: p_dms (list of Param_dms) : dms settings

p_geom (Param_geom) : geom settings

diam (float) : diameter of telescope (default 1.)

cobs (float) : cobs of telescope (default 0.)

pupAngle (float) : pupil rotation angle (degrees, default 0.)

wfs_xpos (array) : guide star x position on sky (in arcsec).

wfs_ypos (array) : guide star y position on sky (in arcsec).

Definition at line 324 of file dm_init.py.

324  dms = Dms()
325  for i in range(len(p_dms)):
326  _dm_init(context, dms, p_dms[i], wfs_xpos, wfs_ypos, p_geom, diam, cobs,
327  pupAngle, max_extent)
328  return dms
329 
330 
331 def make_pzt_dm(p_dm: conf.Param_dm, p_geom: conf.Param_geom, cobs: float,
332  pupAngle: float, keepAllActu: bool = False):
333  """Compute the actuators positions and the influence functions for a pzt DM.
334  NOTE: if the DM is in altitude, central obstruction is forced to 0

◆ init_custom_dm()

def shesha.init.dm_init.init_custom_dm ( conf.Param_dm  p_dm,
conf.Param_geom  p_geom,
float  diam 
)

Read Fits for influence pzt fonction and form.

:parameters: p_dm (Param_dm) : dm settings

p_geom (Param_geom) : geom settings

diam (float) : tel diameter

Conversion. There are sereval coordinate systems. Some are coming from the input fits file, others from compass. Those systems differ by scales and offsets. Coord systems from the input fits file:

  • they all have the same scale: the coordinates are expressed in pixels
  • one system is the single common frame where fits data are described [i]
  • one is local to the minimap [l] Coord systems from compass:
  • they all have the same scale (different from fits one) expressed in pixels
  • one system is attached to ipupil (i=image, largest support) [i]
  • one system is attached to mpupil (m=medium, medium support) [m]
  • one system is local to minimap [l)]

Variables will be named using f, c: define either fits or compass

Definition at line 544 of file dm_init.py.

544  hdul = pfits.open(p_dm.file_influ_hdf5)
545  print("Read influence function from fits file : ", p_dm.file_influ_hdf5)
546 
547  xC = hdul[0].header['XCENTER']
548  yC = hdul[0].header['YCENTER']
549  pitchPix = hdul[0].header['PITCHPX']
550  pitchMeters = hdul[0].header['PITCHM']
551  hi_i1, hi_j1 = hdul[1].data
552  influ = hdul[2].data
553  xpos, ypos = hdul[3].data
554 
555  # facteur de conversion des coordonnees du fichier Fits vers les pixels
556  # de compass
557  # pitchPixCompass = p_dm._pitch # valeur du pitch entre actus en pixels de compass
558  pitchPixCompass = pitchMeters / p_geom._pixsize
559  scaleToCompass = pitchPixCompass / pitchPix
560 
561 
565  offsetXToCompass = p_geom.cent - xC * scaleToCompass
566  offsetYToCompass = p_geom.cent - yC * scaleToCompass
567 
568 
569  iSize, jSize, ntotact = influ.shape
570  if iSize != jSize:
571  raise ('Error')
572 
573  ess1 = np.ceil((hi_i1+iSize) * scaleToCompass + offsetXToCompass) \
574  - np.floor(hi_i1 * scaleToCompass + offsetXToCompass)
575  ess1 = np.max(ess1)
576 
577  ess2 = np.ceil((hi_j1+jSize) * scaleToCompass + offsetYToCompass) \
578  - np.floor(hi_j1 * scaleToCompass + offsetYToCompass)
579  ess2 = np.max(ess2)
580  smallsize = np.maximum(ess1, ess2).astype(int)
581 
582  # Allocate influence function maps and other arrays
583  p_dm._ntotact = ntotact
584  p_dm._influsize = np.int(smallsize)
585  p_dm._i1 = np.zeros(ntotact, dtype=np.int32)
586  p_dm._j1 = np.zeros(ntotact, dtype=np.int32)
587  p_dm._xpos = np.zeros(ntotact, dtype=np.float32)
588  p_dm._ypos = np.zeros(ntotact, dtype=np.float32)
589  p_dm._influ = np.zeros((smallsize, smallsize, ntotact), dtype=np.float32)
590 
591  # loop on actuators
592  for i in range(ntotact):
593  # coordonnes en pixels de la minicarte dans le systeme de depart
594  hi_x = np.arange(iSize) + hi_i1[i]
595  hi_y = np.arange(jSize) + hi_j1[i]
596 
597  # transfert des coord d'origine vers systeme compass
598  ci_x = hi_x * scaleToCompass + offsetXToCompass
599  ci_y = hi_y * scaleToCompass + offsetYToCompass
600 
601  # Creation des coord de destination dans systeme compass
602  ci_i1 = hi_i1[
603  i] * scaleToCompass + offsetXToCompass # itruc = truc dans repere ipup
604  ci_j1 = hi_j1[i] * scaleToCompass + offsetYToCompass
605  ci_i1 = np.floor(ci_i1).astype(np.int32)
606  ci_j1 = np.floor(ci_j1).astype(np.int32)
607  ci_xpix = ci_i1 + np.arange(smallsize)
608  ci_ypix = ci_j1 + np.arange(smallsize)
609  # WARNING: les xpos et ypos sont approximatifs !! Bon pour debug only ...
610  # p_dm._xpos[i] = ci_i1 + smallsize/2.0
611  # p_dm._ypos[i] = ci_j1 + smallsize/2.0
612 
613  f = interpolate.interp2d(ci_y, ci_x, influ[:, :, i], kind='cubic')
614  temp = f(ci_ypix, ci_xpix) * p_dm.unitpervolt
615  # temp[np.where(temp<1e-6)] = 0.
616  p_dm._influ[:, :, i] = temp
617 
618  # ....
619  p_dm._i1[i] = ci_i1
620  p_dm._j1[i] = ci_j1
621 
622  tmp = p_geom.ssize - (np.max(p_dm._i1 + smallsize))
623  margin_i = np.minimum(tmp, np.min(p_dm._i1))
624  tmp = p_geom.ssize - (np.max(p_dm._j1 + smallsize))
625  margin_j = np.minimum(tmp, np.min(p_dm._j1))
626 
627  p_dm._xpos = xpos * scaleToCompass + offsetXToCompass
628  p_dm._ypos = ypos * scaleToCompass + offsetYToCompass
629 
630  p_dm._n1 = int(np.minimum(margin_i, margin_j))
631  p_dm._n2 = p_geom.ssize - 1 - p_dm._n1
632 
633  p_dm._i1 -= p_dm._n1
634  p_dm._j1 -= p_dm._n1
635 
636  comp_dmgeom(p_dm, p_geom)
637 
638 
639 def make_tiptilt_dm(p_dm: conf.Param_dm, patchDiam: int, p_geom: conf.Param_geom,
640  diam: float):
641  """Compute the influence functions for a tip-tilt DM
642 
Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_kl_dm()

None shesha.init.dm_init.make_kl_dm ( conf.Param_dm  p_dm,
int  patchDiam,
conf.Param_geom  p_geom,
float  cobs 
)

Compute the influence function for a Karhunen-Loeve DM.

:parameters: p_dm (Param_dm) : dm settings

patchDiam (int) : patchDiam for dm size

p_geom (Param_geom) : geom settings

cobs (float) : telescope cobs

Definition at line 691 of file dm_init.py.

691  print("KL type: ", p_dm.type_kl)
692 
693  if (p_dm.nkl < 13):
694  nr = np.long(5.0 * np.sqrt(52)) # one point per degree
695  npp = np.long(10.0 * nr)
696  else:
697  nr = np.long(5.0 * np.sqrt(p_dm.nkl))
698  npp = np.long(10.0 * nr)
699 
700  radp = kl_util.make_radii(cobs, nr)
701 
702  kers = kl_util.make_kernels(cobs, nr, radp, p_dm.type_kl, p_dm.outscl)
703 
704  evals, nord, npo, ordd, rabas = kl_util.gkl_fcom(kers, cobs, p_dm.nkl)
705 
706  azbas = kl_util.make_azimuth(nord, npp)
707 
708  ncp, ncmar, px, py, cr, cp, pincx, pincy, pincw, ap = kl_util.set_pctr(
709  patchDiam, nr, npp, p_dm.nkl, cobs, nord)
710 
711  p_dm._ntotact = p_dm.nkl
712  p_dm._nr = nr # number of radial points
713  p_dm._npp = npp # number of elements
714  p_dm._ord = ordd # the radial orders of the basis
715  p_dm._rabas = rabas # the radial array of the basis
716  p_dm._azbas = azbas # the azimuthal array of the basis
717  p_dm._ncp = ncp # dim of grid
718  p_dm._cr = cr # radial coord in cartesien grid
719  p_dm._cp = cp # phi coord in cartesien grid
720  p_dm._i1 = np.zeros((p_dm.nkl), dtype=np.int32) + \
721  (dim - patchDiam) // 2
722  p_dm._j1 = np.zeros((p_dm.nkl), dtype=np.int32) + \
723  (dim - patchDiam) // 2
724  p_dm._ntotact = p_dm.nkl
725  p_dm.ap = ap
726 
727 
728 def comp_dmgeom(p_dm: conf.Param_dm, p_geom: conf.Param_geom):
729  """Compute the geometry of a DM : positions of actuators and influence functions
730 
731  :parameters:
Here is the caller graph for this function:

◆ make_petal_dm_core()

def shesha.init.dm_init.make_petal_dm_core (   pupImage,
  pupAngleDegree 
)

<pupImage> : image of the pupil

La fonction renvoie des fn d'influence en forme de petale d'apres une image de la pupille, qui est supposee etre segmentee.

influ, i1, j1, smallsize, nbSeg = make_petal_dm_core(pupImage, 0.0)

Definition at line 912 of file dm_init.py.

912  # be identified as relevant connex areas
913  from scipy.ndimage.measurements import label
914  from scipy.ndimage.morphology import binary_opening
915  s = np.ones((2, 2), dtype=np.bool)
916  segments, nbSeg = label(binary_opening(pupImage, s))
917 
918  # Faut trouver le plus petit support commun a tous les
919  # petales : on determine <smallsize>
920  smallsize = 0
921  i1t = [] # list of starting indexes of influ functions
922  j1t = []
923  i2t = [] # list of ending indexes of influ functions
924  j2t = []
925  for i in range(nbSeg):
926  petal = segments == (i + 1) # identification (boolean) of a given segment
927  profil = np.sum(petal, axis=1) != 0
928  extent = np.sum(profil).astype(np.int32)
929  i1t.append(np.min(np.where(profil)[0]))
930  i2t.append(np.max(np.where(profil)[0]))
931  if extent > smallsize:
932  smallsize = extent
933 
934  profil = np.sum(petal, axis=0) != 0
935  extent = np.sum(profil).astype(np.int32)
936  j1t.append(np.min(np.where(profil)[0]))
937  j2t.append(np.max(np.where(profil)[0]))
938  if extent > smallsize:
939  smallsize = extent
940 
941  # extension de la zone minimale pour avoir un peu de marge
942  smallsize += 2
943 
944  # Allocate array of influence functions
945  influ = np.zeros((smallsize, smallsize, nbSeg), dtype=np.float32)
946 
947  npt = pupImage.shape[0]
948  i0 = j0 = npt / 2 - 0.5
949  petalMap = build_petals(nbSeg, pupAngleDegree, i0, j0, npt)
950  ii1 = np.zeros(nbSeg)
951  jj1 = np.zeros(nbSeg)
952  for i in range(nbSeg):
953  ip = (smallsize - i2t[i] + i1t[i] - 1) // 2
954  jp = (smallsize - j2t[i] + j1t[i] - 1) // 2
955  i1 = np.maximum(i1t[i] - ip, 0)
956  j1 = np.maximum(j1t[i] - jp, 0)
957  if (j1 + smallsize) > npt:
958  j1 = npt - smallsize
959  if (i1 + smallsize) > npt:
960  i1 = npt - smallsize
961  #petal = segments==(i+1) # determine le segment pupille veritable
962  k = petalMap[i1 + smallsize // 2, j1 + smallsize // 2]
963  petal = (petalMap == k)
964  influ[:, :, k] = petal[i1:i1 + smallsize, j1:j1 + smallsize]
965  ii1[k] = i1
966  jj1[k] = j1
967 
968  return influ, ii1, jj1, int(smallsize), nbSeg
969 
970 
971 def build_petals(nbSeg, pupAngleDegree, i0, j0, npt):
972  """
973  Makes an image npt x npt of <nbSeg> regularly spaced angular segments
974  centred on (i0, j0).
975  Origin of angles is set by <pupAngleDegree>.
976 
977  The segments are oriented as defined in document "Standard Coordinates
978  and Basic Conventions", ESO-193058.
979  This document states that the X axis lies in the middle of a petal, i.e.
980  that the axis Y is along the spider.
981  The separation angle between segments are [-30, 30, 90, 150, -150, -90].
982  For this reason, an <esoOffsetAngle> = -pi/6 is introduced in the code.
983 
984  nbSeg = 6
985  pupAngleDegree = 5.0
986  i0 = j0 = 112.3
987  npt = 222
988  p = build_petals(nbSeg, pupAngleDegree, i0, j0, npt)
989  """
990  # conversion to radians
991  rot = pupAngleDegree * np.pi / 180.0
992 
Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_pzt_dm()

def shesha.init.dm_init.make_pzt_dm ( conf.Param_dm  p_dm,
conf.Param_geom  p_geom,
float  cobs,
float  pupAngle,
bool   keepAllActu = False 
)

Compute the actuators positions and the influence functions for a pzt DM.

Note
if the DM is in altitude, central obstruction is forced to 0

:parameters: p_dm (Param_dm) : dm parameters

p_geom (Param_geom) : geometry parameters

cobs (float) : telescope central obstruction

:return: influ (np.ndarray(dims=3, dtype=np.float64)) : cube of the IF for each actuator

Definition at line 349 of file dm_init.py.

349  coupling = p_dm.coupling
350 
351  # prepare to compute IF on partial (local) support of size <smallsize>
352  pitch = p_dm._pitch
353  smallsize = 0
354 
355  # Petal DM (segmentation of M4)
356  if (p_dm.influ_type == scons.InfluType.PETAL):
357  makePetalDm(p_dm, p_geom, pupAngle)
358  return
359 
360  if (p_dm.influ_type == scons.InfluType.RADIALSCHWARTZ):
361  smallsize = influ_util.makeRadialSchwartz(pitch, coupling)
362  elif (p_dm.influ_type == scons.InfluType.SQUARESCHWARTZ):
363  smallsize = influ_util.makeSquareSchwartz(pitch, coupling)
364  elif (p_dm.influ_type == scons.InfluType.BLACKNUTT):
365  smallsize = influ_util.makeBlacknutt(pitch, coupling)
366  elif (p_dm.influ_type == scons.InfluType.GAUSSIAN):
367  smallsize = influ_util.makeGaussian(pitch, coupling)
368  elif (p_dm.influ_type == scons.InfluType.BESSEL):
369  smallsize = influ_util.makeBessel(pitch, coupling, p_dm.type_pattern)
370  elif (p_dm.influ_type == scons.InfluType.DEFAULT):
371  smallsize = influ_util.makeRigaut(pitch, coupling)
372  else:
373  print("ERROR influtype not recognized ")
374  p_dm._influsize = smallsize
375 
376  # compute location (x,y and i,j) of each actuator:
377  nxact = p_dm.nact
378 
379  if p_dm.type_pattern is None:
380  p_dm.type_pattern = scons.PatternType.SQUARE
381 
382  if p_dm.type_pattern == scons.PatternType.HEXA:
383  print("Pattern type : hexa")
384  cub = dm_util.createHexaPattern(pitch, p_geom.pupdiam * 1.1)
385  keepAllActu = True
386  elif p_dm.type_pattern == scons.PatternType.HEXAM4:
387  print("Pattern type : hexaM4")
388  keepAllActu = True
389  cub = dm_util.createDoubleHexaPattern(pitch, p_geom.pupdiam * 1.1, pupAngle)
390  if p_dm.margin_out is not None:
391  print(f'p_dm.margin_out={p_dm.margin_out} is being '
392  'used for pupil-based actuator filtering')
393  pup_side = p_geom._ipupil.shape[0]
394  cub_off = dm_util.filterActuWithPupil(cub + pup_side // 2 - 0.5,
395  p_geom._ipupil,
396  p_dm.margin_out * p_dm.get_pitch())
397  cub = cub_off - pup_side // 2 + 0.5
398  p_dm.set_ntotact(cub.shape[1])
399  elif p_dm.type_pattern == scons.PatternType.SQUARE:
400  print("Pattern type : square")
401  cub = dm_util.createSquarePattern(pitch, nxact + 4)
402  else:
403  raise ValueError("This pattern does not exist for pzt dm")
404 
405  if keepAllActu:
406  inbigcirc = np.arange(cub.shape[1])
407  else:
408  if (p_dm.alt > 0):
409  cobs = 0
410  inbigcirc = dm_util.select_actuators(cub[0, :], cub[1, :], p_dm.nact,
411  p_dm._pitch, cobs, p_dm.margin_in,
412  p_dm.margin_out, p_dm._ntotact)
413  p_dm._ntotact = inbigcirc.size
414 
415  # print(('inbigcirc',inbigcirc.shape))
416 
417  # converting to array coordinates:
418  cub += p_geom.cent
419 
420  # filtering actuators outside of a disk radius = rad (see above)
421  cubval = cub[:, inbigcirc]
422  ntotact = cubval.shape[1]
423  #pfits.writeto("cubeval.fits", cubval)
424  xpos = cubval[0, :]
425  ypos = cubval[1, :]
426  i1t = (cubval[0, :] - smallsize / 2 - 0.5 - p_dm._n1).astype(np.int32)
427  j1t = (cubval[1, :] - smallsize / 2 - 0.5 - p_dm._n1).astype(np.int32)
428 
429  p_dm._xpos = xpos
430  p_dm._ypos = ypos
431  p_dm._i1 = i1t
432  p_dm._j1 = j1t
433 
434  # Allocate array of influence functions
435 
436  influ = np.zeros((smallsize, smallsize, ntotact), dtype=np.float32)
437  # Computation of influence function for each actuator
438 
439  print("Computing Influence Function type : ", p_dm.influ_type)
440 
441  for i in tqdm(range(ntotact)):
442 
443  i1 = i1t[i]
444  x = np.tile(np.arange(i1, i1 + smallsize, dtype=np.float32),
445  (smallsize, 1)).T # pixel coords in ref frame "dm support"
446  # pixel coords in ref frame "pupil support"
447  x += p_dm._n1
448  # pixel coords in local ref frame
449  x -= xpos[i]
450 
451  j1 = j1t[i]
452  y = np.tile(np.arange(j1, j1 + smallsize, dtype=np.float32),
453  (smallsize, 1)) # idem as X, in Y
454  y += p_dm._n1
455  y -= ypos[i]
456  # print("Computing Influence Function #%d/%d \r" % (i, ntotact), end=' ')
457 
458  if (p_dm.influ_type == scons.InfluType.RADIALSCHWARTZ):
459  influ[:, :, i] = influ_util.makeRadialSchwartz(pitch, coupling, x=x, y=y)
460  elif (p_dm.influ_type == scons.InfluType.SQUARESCHWARTZ):
461  influ[:, :, i] = influ_util.makeSquareSchwartz(pitch, coupling, x=x, y=y)
462  elif (p_dm.influ_type == scons.InfluType.BLACKNUTT):
463  influ[:, :, i] = influ_util.makeBlacknutt(pitch, coupling, x=x, y=y)
464  elif (p_dm.influ_type == scons.InfluType.GAUSSIAN):
465  influ[:, :, i] = influ_util.makeGaussian(pitch, coupling, x=x, y=y)
466  elif (p_dm.influ_type == scons.InfluType.BESSEL):
467  influ[:, :, i] = influ_util.makeBessel(pitch, coupling, x=x, y=y,
468  patternType=p_dm.type_pattern)
469  elif (p_dm.influ_type == scons.InfluType.DEFAULT):
470  influ[:, :, i] = influ_util.makeRigaut(pitch, coupling, x=x, y=y)
471  else:
472  print("ERROR influtype not recognized (defaut or gaussian or bessel)")
473 
474  if (p_dm._puppixoffset is not None):
475  xpos += p_dm._puppixoffset[0]
476  ypos += p_dm._puppixoffset[1]
477 
478  if p_dm.segmented_mirror:
479  # mpupil to ipupil shift
480  # Good centering assumptions
481  s = (p_geom._ipupil.shape[0] - p_geom._mpupil.shape[0]) // 2
482 
483  from skimage.morphology import label
484  k = 0
485  for i in tqdm(range(ntotact)):
486  # Pupil area corresponding to influ data
487  i1, j1 = i1t[i] + s - smallsize // 2, j1t[i] + s - smallsize // 2
488  pupilSnapshot = p_geom._ipupil[i1:i1 + smallsize, j1:j1 + smallsize]
489  if np.all(pupilSnapshot): # We have at least one non-pupil pixel
490  continue
491  labels, num = label(pupilSnapshot, background=0, return_num=True)
492  if num <= 1:
493  continue
494  k += 1
495  maxPerArea = np.array([
496  (influ[:, :, i] * (labels == k).astype(np.float32)).max()
497  for k in range(1, num + 1)
498  ])
499  influ[:, :, i] *= (labels == np.argmax(maxPerArea) + 1).astype(np.float32)
500  print(f'{k} cross-spider influence functions trimmed.')
501 
502  influ = influ * float(p_dm.unitpervolt / np.max(influ))
503 
504  p_dm._influ = influ
505 
506  comp_dmgeom(p_dm, p_geom)
507 
508  dim = max(p_geom._mpupil.shape[0], p_dm._n2 - p_dm._n1 + 1)
509  off = (dim - p_dm._influsize) // 2
510 
511 
512 def init_custom_dm(p_dm: conf.Param_dm, p_geom: conf.Param_geom, diam: float):
513  """Read Fits for influence pzt fonction and form
514 
515  :parameters:
Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_tiptilt_dm()

def shesha.init.dm_init.make_tiptilt_dm ( conf.Param_dm  p_dm,
int  patchDiam,
conf.Param_geom  p_geom,
float  diam 
)

Compute the influence functions for a tip-tilt DM.

:parameters: p_dm (Param_dm) : dm settings

patchDiam (int) : patchDiam for dm size

p_geom (Param_geom) : geom settings

diam (float) : telescope diameter :return: influ (np.ndarray(dims=3,dtype=np.float64)) : cube of the IF

Definition at line 657 of file dm_init.py.

657 
658  nzer = 2
659  influ = dm_util.make_zernike(nzer + 1, dim, patchDiam, p_geom.cent - p_dm._n1 + 1,
660  p_geom.cent - p_dm._n1 + 1, 1)[:, :, 1:]
661 
662  # normalization factor: one unit of tilt gives 1 arcsec:
663  current = influ[dim // 2 - 1, dim // 2 - 1, 0] - \
664  influ[dim // 2 - 2, dim // 2 - 2, 0]
665  fact = p_dm.unitpervolt * diam / p_geom.pupdiam * 4.848 / current
666 
667  influ = influ * fact
668  p_dm._ntotact = influ.shape[2]
669  p_dm._influsize = influ.shape[0]
670  p_dm._influ = influ
671 
672  return influ
673 
674 
675 def make_kl_dm(p_dm: conf.Param_dm, patchDiam: int, p_geom: conf.Param_geom,
676  cobs: float) -> None:
677  """Compute the influence function for a Karhunen-Loeve DM
678 
Here is the caller graph for this function:

◆ makePetalDm()

def shesha.init.dm_init.makePetalDm (   p_dm,
  p_geom,
  pupAngleDegree 
)

makePetalDm(p_dm, p_geom, pupAngleDegree)

The function builds a DM, segmented in petals according to the pupil shape. The petals will be adapted to the EELT case only.

<p_geom> : compass object p_geom. The function requires the object p_geom in order to know what is the pupil mask, and what is the mpupil. <p_dm> : compass petal dm object p_dm to be created. The function will transform/modify in place the attributes of the object p_dm.

Definition at line 887 of file dm_init.py.

887  p_dm._influsize = smallsize
888  p_dm.set_ntotact(nbSeg)
889  p_dm._i1 = i1
890  p_dm._j1 = j1
891  p_dm._xpos = i1 + smallsize / 2 + p_dm._n1
892  p_dm._ypos = j1 + smallsize / 2 + p_dm._n1
893  p_dm._influ = influ
894 
895  # generates the arrays of indexes for the GPUs
896  comp_dmgeom(p_dm, p_geom)
897 
898 
899 def make_petal_dm_core(pupImage, pupAngleDegree):
900  """
901  <pupImage> : image of the pupil
902 
Here is the call graph for this function:
Here is the caller graph for this function: