From 4122183a540dfd235dc115536b46c704159bb298 Mon Sep 17 00:00:00 2001
From: rfetick <r.fetick@gmail.com>
Date: Mon, 27 May 2019 18:30:27 +0200
Subject: [PATCH] Add functions in instrument.py

---
 paompy/__pycache__/instrument.cpython-36.pyc | Bin 208 -> 4625 bytes
 paompy/__pycache__/utils.cpython-36.pyc      | Bin 5661 -> 5565 bytes
 paompy/instrument.py                         | 127 +++++++++++++++++++
 3 files changed, 127 insertions(+)

diff --git a/paompy/__pycache__/instrument.cpython-36.pyc b/paompy/__pycache__/instrument.cpython-36.pyc
index 60647b04140b2e9681e454842f07a3fa53851a19..915fdb330ce7eaeb4bbba25aedb467e8b5beddc1 100644
GIT binary patch
literal 4625
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
Ko9{Q3X6-*Q`$vob

delta 100
zcmbQJa)Hsnn3tDpGt--xJO&1a$3P4Rj6jwH5EpX*i4=w?h7`tN22G}kODskGG#PKP
m$H%ASC&$OHWGG?+Dkx%_9Kq};Xp@_tQks)$#}3j6#0&tuv=fp5

diff --git a/paompy/__pycache__/utils.cpython-36.pyc b/paompy/__pycache__/utils.cpython-36.pyc
index cf5630f41dc860396ee1340f3c0391cf5401bb68..b49526342a64130321eef33d19fbb9dedd266e10 100644
GIT binary patch
delta 1301
zcmZ8g&2Jk;6yKTMwbyHV?Gl@|v=LS%+H7h<^Kq(5K9sbfs7foh64LS!ZFi>jhW*l8
zr;SfOkPG5~(3S&-N*uW4>=OrW`~jSY0|>4h;L3@4<0v?t(fsDk%zMB6y|**3x<B0<
zuh#4KhvmOM`Swjs`%{}~hO{^3$Uy?rJdF`XBjQmx>yaKCo<VTWh|IX;m6T1RvR7s%
zrw7&*jg?xOSJ~BAnOVmgv$*|*?p4_wt1$bRcr{jKwc{(AS7$Beu!h`nSd(2i*1QIz
zk2Sab%^#a>BIv<$mx$W_qu%|!C;QtEA3W?$o|Er&ViXt6E-{N8^POwD)F{kg7{=Vy
zg^}~<8JM_d_|VT!G-wxJnLETSeldT&SCR*NZV50$s3BAkstD7de2~6?Hm?5%SszVQ
z!XyA5^JI9Z_^0&y@{~%Sp{k#cQt>L($UhIW-3hfgDV5R5D&h*PT|g7%PdOTBDNRTu
z^e`C;nq<O+^e|yljdn3--)x;%2A8D!qoQYrV;d<{GG{!XXfZdqh_tnQ=numn=EFhC
z;4<#Ng@7b8DW8c~+HjR)Dpt)PGo3nfwQ8-Rd;+RXZdY}E9t-1^g%O4MQ0Q@(Kohwg
zg!#gAZd^LAUzCFo0w17XeCNDPR*N5<AIM4ZS>tN4MX!&Kx}(nfog`a|`w+5^Z{1$L
z)kXN!?#Q;jzTRQ}(3dU??=HKGi$|TsQ)_h2VgG5ENGFL&nDiGgrn}-Q7X?62pVDr^
zytIXx5B!W%xLUkytgXDR`IP8O>6v4Gnr)M&?2hVP0vnTm8!ss-6<@z`eXJ^=SZNl*
zs-k}};K1eW_Cpx$4|tSukT>%^JTr1X&V-%@fiTmU_kBS?m|?>B(ab-Z-YpD&C4;6a
zE?dN+O*w2i918d4NnlLY=AMv`FuA@QvNbnhQBJ3;zc*Y8RIY`dAI7c;yV8bx2zL=a
zP)ES?*FFy0^5Ey<w^n<M)(PF)a|t|<rkpl$YQln?gqa-1*=SnUdLD$?XerNl5c-k&
ztXI$cR5~mLY4R-WPv3-km|GZa_qKLcwtCx-?{14y`t*PY!|98`I>H9R4(6%k%t$=D
i!@{}~r|dA|>-hTemIQ-Zq$ay%I!3c+U$a{FjsF0TcL@0a

delta 1393
zcmZ8h&2Jk;6yMnod%anI#CDpbPFpJ#R92%1LMy0*AR$evK<YwT4qYLoo1Jws>F(OR
zSyKfLIgk?q1X{R6LgK)sLL4BsN<DCeBe&@V35f$jf<J-x)+J5r(fr<<_vZcP&D%GA
zc=4OL{g=vR=M(Gi_r5=+X@6)ZekSURIOPKbu6Y_~T#uNiqckETHa#=8JS#4E1x7wI
zD#o^F=eiY@V#jlGy%3GXuIJ`@F)GIuuafI}RQ0MnVBC&s@whks*jV@K`9Te@!AqgO
z+iYpvX=>iYHI0vP_dw%rc<N)ro8*(c%qs`Xo8nboJJ7soKE><2ftJ&}$tTd#;`&Xk
zGyUb^;w?kjy<RGJL@!k)Y75E|Bsxs#yB{FBn{RKdb#HAh-&nt~fot=g4++Y06#6pV
zl<yCH2qhXy5ezp2KiCO>{O4PPIoTKH92?6Xn$OJ^uu{qjQW1w8Lzyy+dN4*d(}!(&
zUxQX=T36Y8wq`wAb<xr76u?0Vb%H9vIKe1L?)2|Li`GwI2A8TNk^sUuOwy%nxA6Om
zBP!zrRpaTXJU$OK<y|pYKl-Q`vO<|QRkA;AxHL&Br9a}BAT7^{h?F6cRO!h;S<oLQ
ze3WA?`?PcszYgv^riZh*d^h{GByQKpkzeMR2Xb1v8=OH<u1x(@1aX+|^f{a(dz*kr
zPF(p3JC*8m_L*~zIY&P@CF2ZTyi-(WBxI_LSR{a5tU;6DeD+)U!Yfbbr;34qzz2wM
zhorsQgw0&`|7@b(WV1S4$rdW#y*%@VNZNkV7O}q_wzu}$e!J%fX&>5m?%n}Lr{oz}
za2|DK$sK<X!fbX}y|nP`!CN9pM6&%kZj@9TO?OxgM<pL~3|4-seWf!u`?~&oHNQp9
zsSgAu^Gm&DUB(zM`>Q@P@eH-;3)nl%T8(MeJ-Xbu&Gsp&Z8}5KYK!m!ipQ&eU+?H(
z;aVAT7<VjKM;+cGc$46I?!rDlQGRM0SVQ<3rzC34XzDgw)z9ipmV55&-EN1$JLp2O
zx=(Tj=20js8ODRXygptj*XQxsfsQ^f2<UG{m1Q6Xd-HM-2Evc>488Ke?_=nE&`)||
zJ8w>9)1$;HN+?oTWfayQ;ENL<hF}w6Rm1aDsW(jUOZ6iOCEBV(sBG@1Aw5pGMkZ%t
z>FUDL#`4XTWmV{J?S?@*>i#Q4vqI1%pu3-_$8kF<`_;J5hf(+%B_pY8O{`0kO}W@c
SG^nj=R>iDsIJ36vT=)-v9x5yV

diff --git a/paompy/instrument.py b/paompy/instrument.py
index 18f721c..d5f2807 100644
--- a/paompy/instrument.py
+++ b/paompy/instrument.py
@@ -6,3 +6,130 @@ Created on Mon May 27 17:30:51 2019
 @author: rfetick
 """
 
+from paompy.utils import circarr, airy, RAD2ARCSEC
+from paompy.config import _DEFAULT_RES
+
+#%% DETECTOR CLASS AND ITS SUBCLASSES
+
+class Detector(object):
+    """Represents a detector
+    
+    Attributes
+    ----------
+    resolution : float
+        Pixel scale [meter]
+    Npix : tuple, list, numpy.ndarray
+        Number of pixels on X and Y axis
+    gainADU : float
+        Detector gain [electron/ADU]
+    RON : float
+        Read-Out-Noise [electron]
+        
+    """
+    
+    def __init__(self,Npix,resolution=_DEFAULT_RES,gainADU=1.,RON=0.):
+        self.resolution_pixel = resolution
+        self.Npix = Npix
+        self.gainADU = gainADU
+        self.RON = RON
+        self.binning = 1
+    
+    def __repr__(self):
+        s  = "PAOMPY Detector\n"
+        s += "---------------\n"
+        s += "Pixels    : (%u,%u)\n" % (self.Npix[0],self.Npix[1])
+        s += "Resolution: %u um\n" % round(self.resolution*1e6)
+        s += "Binning   : %u\n" % self.binning
+        s += "Gain ADU  : %.2f e-/ADU\n" % self.gainADU
+        s += "RON       : %.2f e-" % self.RON
+        return s
+    
+    @property
+    def resolution(self):
+        return self.resolution_pixel * self.binning
+
+
+ZIMPOL_DETECTOR = Detector((1024,1024),30*1e-6,gainADU=10.5,RON=20.)
+
+# Equivalent detector resolution after image reduction pipeline
+MUSE_DETECTOR = Detector((200,200),237.14745*1e-6,gainADU=5.,RON=15.)
+
+
+
+
+#%% INSTRUMENT CLASS AND ITS SUBCLASSES
+
+class Instrument(object):
+    """Represents an optical system
+    
+    Attributes
+    ----------
+    D : float
+        Entrance pupil diameter [meter]
+    detector : Detector
+        Camera at the focal plane
+    filters : dict
+        Dictionary of available filters as tuples (central wvl, width) [meter]
+    AO_Nact : int
+        Linear number of actuators
+    """
+
+    def __init__(self,D,detector=None,occ=0.):
+        self.D = D
+        self.occ = occ # occultation ratio
+        self.detector = detector
+        self.filters = {}
+        self.AO_Nact = 0
+        self.focal_length = None
+        self.name = ""
+        
+    def __repr__(self):
+        s  = self.name+" OpticalSystem\n"
+        s += "-------------------------\n" 
+        s += "Diameter: %.2g m (occ=%u%%)\n" % (self.D,self.occ*100)
+        s += "AO_Nact : %u\n" % self.AO_Nact
+        s += "Focal   : %s m\n" % str(self.focal_length)
+        s += "Filters : %u" % len(self.filters)
+        return s
+    
+    @property
+    def resolution_mas(self):
+        if self.focal_length is None:
+            raise ValueError("Cannot compute `resolution_mas` if `focal_length` is not set")
+        return self.detector.resolution/self.focal_length * RAD2ARCSEC * 1e3
+    
+    def pupil(self,Npix,wvl=None,samp=None):
+        """Returns the 2D array of the pupil transmission function"""
+        Dpix = min(Npix)/2
+        pup = circarr(Npix)
+        return (pup < Dpix) * (pup >= Dpix*self.occ)
+    
+    def samp(self,wvl):
+        """Returns sampling value for the given wavelength"""
+        if self.detector is None:
+            raise ValueError("Cannot compute sampling if `detector` is not defined")
+        if self.focal_length is None:
+            raise ValueError("Cannot compute sampling if `focal_length` is not defined")
+        return wvl*self.focal_length/(self.detector.resolution*self.D)
+    
+    def wvl(self,samp):
+        """Returns wavelength for the given sampling"""
+        if self.detector is None:
+            raise ValueError("Cannot compute wavelength if `detector` is not defined")
+        if self.focal_length is None:
+            raise ValueError("Cannot compute wavelength if `focal_length` is not defined")
+        return samp*(self.detector.resolution*self.D)/self.focal_length
+    
+    
+    def PSFdl(self,Npix,wvl):
+        """Returns the diffraction limited PSF
+        
+        Parameters
+        ----------
+        Npix : tuple, list of 2 elements
+            Size of the output 2D array
+        wvl : float
+            Observation wavelength
+        """
+        return airy(Npix,self.samp(wvl),self.occ)
+
-- 
GitLab