From 4d34574b912ddb987f6d8cbb0afdf764bfcf4f78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20Le=20Guyader?= <loic.le.guyader@xfel.eu>
Date: Tue, 17 Dec 2019 11:18:49 +0100
Subject: [PATCH] Upload New File

---
 TZPGcalc.py | 409 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 409 insertions(+)
 create mode 100644 TZPGcalc.py

diff --git a/TZPGcalc.py b/TZPGcalc.py
new file mode 100644
index 0000000..5ce4ff6
--- /dev/null
+++ b/TZPGcalc.py
@@ -0,0 +1,409 @@
+# -*- coding: utf-8 -*-
+""" TZPG simple calculator for SCS.
+
+    Interactive widget to calculate beam sizes and position at the sample and detector planes for the SCS instrument.
+
+    Copyright (2019) SCS Team.
+"""
+
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib.patches import Rectangle
+from matplotlib.colors import hsv_to_rgb
+
+import ipywidgets as widgets
+from ipywidgets import HBox, VBox, Layout
+from IPython.display import display
+
+# zone plate focal length
+F = 250*1e-3 # [m]
+
+# Z position of the zone plate optic from the first interaction point (sample Z stage at 0 mm)
+Z0 = 230*1e-3 # [m]
+
+# zone plate nominal focal sorthened by the KBS focusing
+d = 3.3 - Z0 # distance between HFM and TZPG
+f1 = 7.3 # HFM focus 2 m behind second interaction point
+F = F*(d-f1)/(d-f1-F)
+
+class TZPGcalc():
+    def __init__(self):
+        self.initFig()
+        self.initWidgets()
+        self.UpdateFig()
+        display(self.control)
+        
+    def initFig(self):
+        """ Creates a figure for the sample plane and detector plane images with all necessary drawings.
+        """
+        
+        plt.close('TZPGcalc')
+        fig, (self.ax_sam, self.ax_det) = plt.subplots(1, 2, num='TZPGcalc', figsize=(6,3))
+
+        self.ax_sam.set_title('Sample plane')
+        self.ax_det.set_title('Detector plane')
+
+        self.ax_sam.set_aspect('equal')
+        self.ax_det.set_aspect('equal')
+        self.ax_sam.set_xlim([-2, 2])
+        self.ax_sam.set_ylim([-2, 2])
+        self.ax_det.set_xlim([-35, 35])
+        self.ax_det.set_ylim([-20, 50])
+
+        # red and blue shifted color of the beams
+        c_rr = hsv_to_rgb([0/360, 50/100, 100/100])
+        c_rb = hsv_to_rgb([40/360, 50/100, 100/100])
+        c_gr = hsv_to_rgb([95/360, 60/100, 100/100])
+        c_gb = hsv_to_rgb([145/360, 60/100, 100/100])
+
+        self.samBeamsL = {'F0G0': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F0G1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F0G-1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F1G0': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_rr, alpha=0.7, lw=None)),
+                'F1G1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gr, alpha=0.7, lw=None)),
+                'F1G-1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gr, alpha=0.7, lw=None))
+               }
+
+        self.detBeamsL = {'F0G0': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F0G1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F0G-1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F1G0': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_rr, alpha=0.7, lw=None)),
+                'F1G1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gr, alpha=0.7, lw=None)),
+                'F1G-1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gr, alpha=0.7, lw=None))
+               }
+
+        self.samBeamsH = {'F0G0': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F0G1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F0G-1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F1G0': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_rb, alpha=0.7, lw=None)),
+                'F1G1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gb, alpha=0.7, lw=None)),
+                'F1G-1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gb, alpha=0.7, lw=None))
+               }
+
+        self.detBeamsH = {'F0G0': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F0G1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F0G-1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)),
+                'F1G0': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_rb, alpha=0.7, lw=None)),
+                'F1G1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gb, alpha=0.7, lw=None)),
+                'F1G-1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gb, alpha=0.7, lw=None))
+               }
+        
+        self.detLines = {'module': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, fill=False, facecolor='k')),
+                'Vfilter': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="blue", alpha=0.4)),
+                'Hfilter': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="blue", alpha=0.4)),
+               }
+
+        # 5x5 membranes
+        self.sampleLines = {}
+        self.etchLines = {}
+        for k in range(25):
+            self.sampleLines[k] = self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, fill=False, facecolor='k'))
+            self.etchLines[k] = self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, fill=False, facecolor='k', alpha=0.4, ls='--'))
+
+    def RectUpdate(self, rect, xLeft, yBottom, xRight, yTop):
+        """ Updates the position and size of the given Rectangle.
+        
+            rect: Rectangle to update
+            xLeft: x position of the left corner
+            yBottom: y position of the bottom corner
+            xRight: x position of the right corner
+            yTop: y position of the top corner
+        """
+        scale = 1e3 # displayed distances in [mm]
+        
+        xw = np.abs(xLeft - xRight)
+        yw = np.abs(yTop - yBottom)
+        
+    
+        rect.set_xy((scale*xLeft, scale*yBottom))
+        rect.set_height(scale*yw)
+        rect.set_width(scale*xw)
+    
+    def UpdateBeams(self, Beams, Z, conf):
+        """ Update the position and size of the beams.
+        
+            Beams: dictionary of f'F{f}G{g}' Rectangles for f = 0 and 1 zone plate order and
+                g = +1, 0 and -1 grating order
+            Z: distance Z between the zone plate and the current imaging plane
+            conf: dictionnary of distance for calculation {'F', 'TZPGwH', 'TZPGwV', 'TZPGo', 'theta_grating'}
+        """
+        F = conf['F']
+        wH = conf['TZPGwH']
+        wV = conf['TZPGwV']
+        o = conf['TZPGo']
+        offaxis = wV/2 + o
+
+        # side view X
+        Fx = F*np.arctan(conf['theta_grating'])
+
+        Xdg0 = np.abs(Z - F)/F*wH/2
+        Xdg1L = Z/F*(Fx - wH/2) + wH/2
+        Xdg1 = Z*np.tan(conf['theta_grating'])
+        Xdg1H = Z/F*(Fx + wH/2) - wH/2
+
+        # top view Y
+        YdfL = (o*(Z - F)/F + offaxis)
+        Ydf = ((o + wV/2)*(Z - F)/F + offaxis)
+        YdfH = ((o + wV)*(Z - F)/F + offaxis)
+        
+        if Z < F: # before zone plate focus, low and high beam edges are swapped
+            Xdg1L, Xdg1H = (Xdg1H, Xdg1L)
+            YdfL, YdfH = (YdfH, YdfL)
+
+        self.RectUpdate(Beams['F0G0'],
+                   -wH/2, -wV/2, wH/2, wV/2)
+        self.RectUpdate(Beams['F0G1'],
+                   Xdg1-wH/2, -wV/2, Xdg1+wH/2, wV/2)
+        self.RectUpdate(Beams['F0G-1'],
+                   -Xdg1-wH/2, -wV/2, -Xdg1+wH/2, wV/2)
+
+        self.RectUpdate(Beams['F1G0'],
+                   -Xdg0, YdfL, Xdg0, YdfH)
+        self.RectUpdate(Beams['F1G1'],
+                   Xdg1L, YdfL, Xdg1H, YdfH)
+        self.RectUpdate(Beams['F1G-1'],
+                   -Xdg1H, YdfL, -Xdg1L, YdfH)
+
+    def DetectorUpdate(self, Xoff, Yoff):
+        """ Draw DSSC detector module with filter mask.
+        
+            Xoff: x offset
+            Yoff: y offset
+        """
+        # x module axis is vertical, y module axis is horizontal
+        # the module 15 is +0.91 mm vertical from the beam and 4.233 mm horizontal from the beam
+        offset_h = 4.233e-3 #[mm]
+        offset_v = 0.91e-3 #[mm]
+
+        moduleHw = 256*0.236e-3 #[mm]
+        moduleVw = 128*0.204e-3 #[mm]
+
+        filterW = 7e-3 #[mm]
+        filterL = 160e-3 #[mm]
+
+        self.RectUpdate(self.detLines['module'],
+                   -moduleHw - offset_h + Xoff, offset_v + Yoff, -offset_h + Xoff, moduleVw + offset_v + Yoff)
+        self.RectUpdate(self.detLines['Vfilter'],
+                   -filterW/2 + Xoff, -filterL/2 + Yoff,  filterW/2 + Xoff, filterL/2 + Yoff)
+        self.RectUpdate(self.detLines['Hfilter'],
+                   -filterL/2 + Xoff, -filterW/2 + Yoff,  filterL/2 + Xoff, filterW/2 + Yoff)
+
+    def SampleUpdate(self, w, p, Xoff, Yoff, thickness=0.525):
+        """ Draw the sample.
+        
+            w: membrane width
+            p: membrane pitch
+            Xoff: sample x offset
+            Yoff: sample y offset
+            thickness: sample thickness used to calculate the etched facets
+        """
+        # Si etching angle
+        wp = w +2*thickness/np.tan(np.deg2rad(54.74))
+
+        j = 0
+        for k in range(-2, 3):
+            for l in range(-2, 3):
+                self.RectUpdate(self.sampleLines[j], k*p - w/2 + Xoff, l*p - w/2 - Yoff, k*p + w/2 + Xoff, l*p + w/2 - Yoff)
+                self.RectUpdate(self.etchLines[j], k*p - wp/2 + Xoff, l*p - wp/2 - Yoff, k*p + wp/2 + Xoff, l*p + wp/2 - Yoff)
+                j+=1
+
+    def UpdateFig(self):
+        """ Update the figure with the current slider values.
+        
+        """
+        
+        # we calculate the optics for the central wavelength
+        nrjL, nrjH = self.nrj_slider.value # [eV]
+        wlL = 1240/nrjL*1e-9
+        wlH = 1240/nrjH*1e-9
+        wl = 0.5*(wlL + wlH)
+
+        theta_grating = self.grating_slider.value*1e-3 # [rad]
+        sampleZ = self.samz_slider.value*1e-3 # [m]
+        detectorZ = self.det_slider.value*1e-3 # [m]
+        TZPGwH = self.TZPGwH_slider.value*1e-3 #[m]
+        TZPGwV = self.TZPGwV_slider.value*1e-3 #[m]
+        TZPGo = self.TZPGoffaxis_slider.value*1e-3 - TZPGwV/2 #[m]
+
+        d_nominal = wl/np.sin(theta_grating)
+        self.d_label.value = f'Grating Pitch:{int(np.round(d_nominal*1e9))} nm'
+
+        rn = TZPGwV + TZPGo
+        dr_nominal = wl * F / (2*rn)
+        self.dr_label.value = f'Outer Zone Plate width dr:{int(np.round(dr_nominal*1e9))} nm'
+
+        # configuration for the low energy and high energy photon
+        confL = {'F':(2*rn)*dr_nominal/wlL,
+                 'theta_grating':np.arcsin(wlL/d_nominal),
+                 'TZPGwH':TZPGwH, 'TZPGwV':TZPGwV,  'TZPGo':TZPGo}
+        confH = {'F':(2*rn)*dr_nominal/wlH,
+                 'theta_grating':np.arcsin(wlH/d_nominal),
+                 'TZPGwH':TZPGwH, 'TZPGwV':TZPGwV,  'TZPGo':TZPGo}
+
+        # update the beams
+        self.UpdateBeams(self.samBeamsL, Z0 + sampleZ, confL)
+        self.UpdateBeams(self.detBeamsL, Z0 + detectorZ, confL)
+        self.UpdateBeams(self.samBeamsH, Z0 + sampleZ, confH)
+        self.UpdateBeams(self.detBeamsH, Z0 + detectorZ, confH)
+
+        # update the detector
+        detXoff = self.detX_slider.value*1e-3 #[m]
+        detYoff = self.detY_slider.value*1e-3 #[m]
+        self.DetectorUpdate(detXoff, detYoff)
+
+        # update the sample
+        samw = self.samw_slider.value*1e-3 #[m]
+        samp = self.samp_slider.value*1e-3 #[m]
+        samXoff = self.samX_slider.value*1e-3 #[m]
+        samYoff = self.samY_slider.value*1e-3 #[m]
+        samthickness = self.samthickness_slider.value*1e-6 #[m]
+        self.SampleUpdate(samw, samp, samXoff, samYoff, samthickness)
+
+    def initWidgets(self):
+        """ Creates the necessary interactive widget controls.
+        """
+        self.button = widgets.Button(
+            description='Update',
+        )
+
+        @self.button.on_click
+        def plot_on_click(b):
+            self.UpdateFig()
+
+        # TZPG part
+        self.nrj_slider = widgets.FloatRangeSlider(
+            value=[840., 880.],
+            min=450.,
+            max=3000.0,
+            step=1,
+            readout_format='.2f',
+        )
+        self.TZPGwH_slider = widgets.FloatSlider(
+            value=1.0,
+            min=.1,
+            max=3.0,
+            step=0.05,
+            readout_format='.2f',
+        )
+        self.TZPGwV_slider = widgets.FloatSlider(
+            value=1.0,
+            min=.1,
+            max=3.0,
+            step=0.05,
+            readout_format='.2f',
+        )
+        self.TZPGoffaxis_slider = widgets.FloatSlider(
+            value=0.75,
+            min=.0,
+            max=2.0,
+            step=0.05,
+            readout_format='.2f',
+        )
+        self.grating_slider = widgets.FloatSlider(
+            value=3.8,
+            min=1.,
+            max=10.0,
+            step=0.05,
+            readout_format='.2f',
+        )
+        self.dr_label = widgets.Label(value='dr')
+        self.d_label = widgets.Label(value='dr')
+        TZPGTab = VBox(children=[HBox([widgets.Label(value='Energy (eV):'), self.nrj_slider]),
+                                 HBox([widgets.Label(value=r'Grating $\theta$ (mrad):'), self.grating_slider]),
+                                 self.d_label, self.dr_label,
+                                 HBox([widgets.Label(value='TZPG horiz. width (mm):'), self.TZPGwH_slider]),
+                                 HBox(children=[HBox([widgets.Label(value='TZPG vert. width (mm):'), self.TZPGwV_slider]),
+                                                HBox([widgets.Label(value='TZPG off axis (mm):'), self.TZPGoffaxis_slider])
+                                               ])])
+
+        # sample part
+        self.samz_slider = widgets.FloatSlider(
+            value=30.,
+            min=-10.,
+            max=180.0,
+            step=1,
+            readout_format='.2f',
+        )
+        self.samw_slider = widgets.FloatSlider(
+            value=.5,
+            min=0.01,
+            max=2.0,
+            step=.01,
+            readout_format='.2f',
+        )
+        self.samp_slider = widgets.FloatSlider(
+            value=1.0,
+            min=0.01,
+            max=2.0,
+            step=.01,
+            readout_format='.2f',
+        )
+        self.samX_slider = widgets.FloatSlider(
+            value=0.,
+            min=-10,
+            max=10,
+            step=0.01,
+            readout_format='.2f',
+        )
+        self.samY_slider = widgets.FloatSlider(
+            value=0.,
+            min=-10,
+            max=10,
+            step=0.01,
+            readout_format='.2f',
+        )
+        self.samthickness_slider = widgets.FloatSlider(
+            value=381,
+            min=1,
+            max=1000,
+            step=1,
+            readout_format='.0f',
+        )
+        samTab = VBox(children=[HBox([widgets.Label(value='Sample Z (mm):'), self.samz_slider]),
+                                HBox(children=[HBox([widgets.Label(value='Membrane width (mm):'), self.samw_slider]),
+                                               HBox([widgets.Label(value='Membrane pitch (mm):'), self.samp_slider])]),
+                                HBox(children=[HBox([widgets.Label(value='Sample X-Offset (mm):'), self.samX_slider]),
+                                               HBox([widgets.Label(value='Sample Y-Offset (mm):'), self.samY_slider])]),
+                                HBox([widgets.Label(value='Substrate thickness (um):'), self.samthickness_slider])
+                                ])
+
+        #detector tab
+        self.det_slider = widgets.FloatSlider(
+            value=2000.,
+            min=1000,
+            max=5800,
+            step=1,
+            description='',
+            readout_format='.2f',
+        )
+        self.detX_slider = widgets.FloatSlider(
+            value=20.,
+            min=-50,
+            max=50,
+            step=0.5,
+            readout_format='.2f',
+        )
+        self.detY_slider = widgets.FloatSlider(
+            value=0.,
+            min=-50,
+            max=50,
+            step=0.5,
+            readout_format='.2f',
+        )
+        detTab = VBox(children=[HBox([widgets.Label(value='Detector Z (m):'), self.det_slider]),
+                                HBox(children=[HBox([widgets.Label(value='Detector X-Offset (mm):'), self.detX_slider]),
+                                               HBox([widgets.Label(value='Detector Y-Offset (mm):'), self.detY_slider])])])
+
+        tab1 = widgets.Accordion(children=[TZPGTab])
+        tab1.set_title(0, 'TZPG')
+        tab1.selected_index = None
+
+        tab2 = widgets.Accordion(children=[samTab])
+        tab2.set_title(0, 'sample')
+        tab2.selected_index = None
+
+        tab3 = widgets.Accordion(children=[detTab])
+        tab3.set_title(0, 'detector')
+        tab3.selected_index = None
+
+        self.control = VBox(children=[tab1, tab2, tab3, self.button])
\ No newline at end of file
-- 
GitLab