Skip to content
Snippets Groups Projects
Commit 2916e1f8 authored by Loïc Le Guyader's avatar Loïc Le Guyader
Browse files

Sample with other than normal incidence angle

parent ee8b0558
No related branches found
No related tags found
No related merge requests found
...@@ -10,4 +10,6 @@ The so far implemented features are: ...@@ -10,4 +10,6 @@ The so far implemented features are:
* calculates beam sizes and position for 2 energies * calculates beam sizes and position for 2 energies
* calculates sample position (membrane size and pitch) as well as etched facets * calculates sample position (membrane size and pitch) as well as etched facets
* calculates position of the DSSC module 15 as well as filter bar position * calculates position of the DSSC module 15 as well as filter bar position
* calculates focal length change due to the KBS * calculates focal length change due to the KBS
\ No newline at end of file * calculates beam focusing by KBS for the zone plate 0th order
* calculates beam and sample shape for sample at non-normal incidence angle
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle from matplotlib.patches import Rectangle, Polygon
from matplotlib.colors import hsv_to_rgb from matplotlib.colors import hsv_to_rgb
import ipywidgets as widgets import ipywidgets as widgets
...@@ -26,6 +26,7 @@ Z0 = 230*1e-3 # [m] ...@@ -26,6 +26,7 @@ Z0 = 230*1e-3 # [m]
d = 3.3 - Z0 # distance between HFM and TZPG d = 3.3 - Z0 # distance between HFM and TZPG
f1 = 7.3 # HFM focus 2 m behind second interaction point f1 = 7.3 # HFM focus 2 m behind second interaction point
F = F*(d-f1)/(d-f1-F) F = F*(d-f1)/(d-f1-F)
KBS_F = f1 - d # KBS focus distance from TZPG
# number of membrane to show # number of membrane to show
SampleN = 7 SampleN = 7
...@@ -63,50 +64,86 @@ class TZPGcalc(): ...@@ -63,50 +64,86 @@ class TZPGcalc():
c_gr = hsv_to_rgb([95/360, 60/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]) 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)), self.samBeamsL = {
'F0G1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)), 'F0G0': self.ax_sam.add_patch(
'F0G-1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)), Polygon([(0, 0)], 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)), 'F0G1': self.ax_sam.add_patch(
'F1G1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gr, alpha=0.7, lw=None)), Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
'F1G-1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gr, alpha=0.7, lw=None)) 'F0G-1': self.ax_sam.add_patch(
} Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
'F1G0': self.ax_sam.add_patch(
self.detBeamsL = {'F0G0': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)), Polygon([(0, 0)], facecolor=c_rr, alpha=0.7, lw=None)),
'F0G1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)), 'F1G1': self.ax_sam.add_patch(
'F0G-1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)), Polygon([(0, 0)], facecolor=c_gr, alpha=0.7, lw=None)),
'F1G0': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_rr, alpha=0.7, lw=None)), 'F1G-1': self.ax_sam.add_patch(
'F1G1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gr, alpha=0.7, lw=None)), Polygon([(0, 0)], 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.detBeamsL = {
self.samBeamsH = {'F0G0': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)), 'F0G0': self.ax_det.add_patch(
'F0G1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)), Polygon([(0, 0)], 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)), 'F0G1': self.ax_det.add_patch(
'F1G0': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_rb, alpha=0.7, lw=None)), Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
'F1G1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gb, alpha=0.7, lw=None)), 'F0G-1': self.ax_det.add_patch(
'F1G-1': self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_gb, alpha=0.7, lw=None)) Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
} 'F1G0': self.ax_det.add_patch(
Polygon([(0, 0)], facecolor=c_rr, 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)), 'F1G1': self.ax_det.add_patch(
'F0G1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)), Polygon([(0, 0)], facecolor=c_gr, alpha=0.7, lw=None)),
'F0G-1': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="black", alpha=0.4, lw=None)), 'F1G-1': self.ax_det.add_patch(
'F1G0': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor=c_rb, alpha=0.7, lw=None)), Polygon([(0, 0)], facecolor=c_gr, 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.samBeamsH = {
'F0G0': self.ax_sam.add_patch(
self.detLines = {'module': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, fill=False, facecolor='k')), Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
'Vfilter': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="blue", alpha=0.4)), 'F0G1': self.ax_sam.add_patch(
'Hfilter': self.ax_det.add_patch(Rectangle((0, 0), 1, 1, facecolor="blue", alpha=0.4)), Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
'diamond': self.ax_det.add_patch(Rectangle((-8, -8), 16, 16, facecolor="blue", alpha=0.4, angle=45)) 'F0G-1': self.ax_sam.add_patch(
Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
'F1G0': self.ax_sam.add_patch(
Polygon([(0, 0)], facecolor=c_rb, alpha=0.7, lw=None)),
'F1G1': self.ax_sam.add_patch(
Polygon([(0, 0)], facecolor=c_gb, alpha=0.7, lw=None)),
'F1G-1': self.ax_sam.add_patch(
Polygon([(0, 0)], facecolor=c_gb, alpha=0.7, lw=None))
}
self.detBeamsH = {
'F0G0': self.ax_det.add_patch(
Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
'F0G1': self.ax_det.add_patch(
Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
'F0G-1': self.ax_det.add_patch(
Polygon([(0, 0)], facecolor="black", alpha=0.4, lw=None)),
'F1G0': self.ax_det.add_patch(
Polygon([(0, 0)], facecolor=c_rb, alpha=0.7, lw=None)),
'F1G1': self.ax_det.add_patch(
Polygon([(0, 0)], facecolor=c_gb, alpha=0.7, lw=None)),
'F1G-1': self.ax_det.add_patch(
Polygon([(0, 0)], 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)),
'diamond': self.ax_det.add_patch(
Rectangle((-8, -8), 16, 16, facecolor="blue", alpha=0.4, angle=45))
} }
# 5x5 membranes # 5x5 membranes
self.sampleLines = {} self.sampleLines = {}
self.etchLines = {} self.etchLines = {}
for k in range(SampleN*SampleN): for k in range(SampleN*SampleN):
self.sampleLines[k] = self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, fill=False, facecolor='k')) self.sampleLines[k] = self.ax_sam.add_patch(
self.etchLines[k] = self.ax_sam.add_patch(Rectangle((0, 0), 1, 1, fill=False, facecolor='k', alpha=0.4, ls='--')) 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): def RectUpdate(self, rect, xLeft, yBottom, xRight, yTop):
""" Updates the position and size of the given Rectangle. """ Updates the position and size of the given Rectangle.
...@@ -126,13 +163,54 @@ class TZPGcalc(): ...@@ -126,13 +163,54 @@ class TZPGcalc():
rect.set_height(self.scale*yw) rect.set_height(self.scale*yw)
rect.set_width(self.scale*xw) rect.set_width(self.scale*xw)
def UpdateBeams(self, Beams, Z, conf): def PolyUpdate(self, poly, xLeft, yBottom, xRight, yTop):
""" Updates the corner position of a Polygon.
poly: regular Polygon 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
"""
xy = self.scale*np.array([
[xLeft, yBottom],
[xLeft, yTop],
[xRight, yTop],
[xRight, yBottom]])
poly.set_xy(xy)
def LinePlaneIntersection(self, l1, l2, p0, n):
""" Calculate the intersection point in space between a line (beam)
passing through 2 points l1 and l2 and a (sample) plane passing by
p0 with normal n
l1: [x,y,z] point on line
l2: [x,y,z] point on line
p0: [x,y,z] point on plane
n: plane normal vector
"""
# plane parametrized as (p - p0).n = 0
# line parametrized as p = l1 + l12*d with d Real
l12 = l2 - l1
if np.dot(l12,n) == 0:
return [0,0,0] # line is either in the plane or outside the plane
else:
d = np.dot((p0 - l1), n)/np.dot(l12,n)
return l1 + l12*d
def UpdateBeams(self, Beams, Z, incidence, conf):
""" Update the position and size of the beams. """ 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 Beams: dictionary of f'F{f}G{g}' Polygon for f = 0 and 1 zone
g = +1, 0 and -1 grating order plate order and g = +1, 0 and -1 grating order
Z: distance Z between the zone plate and the current imaging plane Z: distance Z between the zone plate and the current imaging plane
conf: dictionnary of distance for calculation {'F', 'TZPGwH', 'TZPGwV', 'TZPGo', 'theta_grating'} incidence: incidence angle of the imaging plane in rad
conf: dictionnary of distance for calculation {'F', 'TZPGwH',
'TZPGwV', 'TZPGo', 'theta_grating'}
""" """
F = conf['F'] F = conf['F']
wH = conf['TZPGwH'] wH = conf['TZPGwH']
...@@ -140,36 +218,34 @@ class TZPGcalc(): ...@@ -140,36 +218,34 @@ class TZPGcalc():
o = conf['TZPGo'] o = conf['TZPGo']
offaxis = wV/2 + o offaxis = wV/2 + o
# side view X # imaging plane
Fx = F*np.arctan(conf['theta_grating']) n = np.array([np.sin(incidence), 0, np.cos(incidence)])
p0 = np.array([0,0,Z])
Xdg0 = np.abs(Z - F)/F*wH/2
Xdg1L = Z/F*(Fx - wH/2) + wH/2 # zone plate 4 corner points
Xdg1 = Z*np.tan(conf['theta_grating']) l1_list = [
Xdg1H = Z/F*(Fx + wH/2) - wH/2 np.array([wH/2,wV/2,0]),
np.array([wH/2,-wV/2,0]),
# top view Y np.array([-wH/2,-wV/2,0]),
YdfL = (o*(Z - F)/F + offaxis) np.array([-wH/2,wV/2,0])
Ydf = ((o + wV/2)*(Z - F)/F + offaxis) ]
YdfH = ((o + wV)*(Z - F)/F + offaxis)
# 6 beam focus point
if Z < F: # before zone plate focus, low and high beam edges are swapped l2_list = {
Xdg1L, Xdg1H = (Xdg1H, Xdg1L) 'F0G0': np.array([0,0,KBS_F]),
YdfL, YdfH = (YdfH, YdfL) 'F0G1': np.array([KBS_F*np.arctan(conf['theta_grating']),0,KBS_F]),
'F0G-1': np.array([-KBS_F*np.arctan(conf['theta_grating']),0,KBS_F]),
self.RectUpdate(Beams['F0G0'], 'F1G0': np.array([0,offaxis,F]),
-wH/2, -wV/2, wH/2, wV/2) 'F1G1': np.array([F*np.arctan(conf['theta_grating']),offaxis,F]),
self.RectUpdate(Beams['F0G1'], 'F1G-1': np.array([-F*np.arctan(conf['theta_grating']),offaxis,F])
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) for beam in l2_list.keys():
l2 = l2_list[beam]
self.RectUpdate(Beams['F1G0'], corners = []
-Xdg0, YdfL, Xdg0, YdfH) for l1 in l1_list:
self.RectUpdate(Beams['F1G1'], corners.append(self.LinePlaneIntersection(l1, l2, p0, n)[:2])
Xdg1L, YdfL, Xdg1H, YdfH) Beams[beam].set_xy(self.scale*np.array(corners))
self.RectUpdate(Beams['F1G-1'],
-Xdg1H, YdfL, -Xdg1L, YdfH)
def DetectorUpdate(self, Xoff, Yoff): def DetectorUpdate(self, Xoff, Yoff):
""" Draw DSSC detector module with filter mask. """ Draw DSSC detector module with filter mask.
...@@ -199,7 +275,8 @@ class TZPGcalc(): ...@@ -199,7 +275,8 @@ class TZPGcalc():
# moving rotated rectangles is a pain in matplotlib # moving rotated rectangles is a pain in matplotlib
self.detLines['diamond'].set_xy((self.scale*Xoff, self.scale*(Yoff - diamondW/2*np.sqrt(2)))) self.detLines['diamond'].set_xy((self.scale*Xoff, self.scale*(Yoff - diamondW/2*np.sqrt(2))))
def SampleUpdate(self, w, p, Xoff, Yoff, thickness=0.525): def SampleUpdate(self, w, p, Xoff, Yoff, thickness=0.525,
incidence=0, etch_angle=54.74):
""" Draw the sample. """ Draw the sample.
w: membrane width w: membrane width
...@@ -207,15 +284,26 @@ class TZPGcalc(): ...@@ -207,15 +284,26 @@ class TZPGcalc():
Xoff: sample x offset Xoff: sample x offset
Yoff: sample y offset Yoff: sample y offset
thickness: sample thickness used to calculate the etched facets thickness: sample thickness used to calculate the etched facets
incidence: incidence angle in rad
etch_angle: etching angle from surface in rad
""" """
# Si etching angle # Si etching angle
wp = w +2*thickness/np.tan(np.deg2rad(54.74)) wp = w +2*thickness/np.tan(etch_angle)
# incidence angle squeezes sample and etch lines
# and induces an apparent shift off the etch lines
ci = np.cos(incidence)
thsi = thickness*np.sin(incidence)
j = 0 j = 0
for k in range(-(SampleN-1)//2, (SampleN-1)//2+1): for k in range(-(SampleN-1)//2, (SampleN-1)//2+1):
for l in range(-(SampleN-1)//2, (SampleN-1)//2+1): for l in range(-(SampleN-1)//2, (SampleN-1)//2+1):
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.sampleLines[j],
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) ci*(k*p - w/2 + Xoff), l*p - w/2 - Yoff,
ci*(k*p + w/2 + Xoff), l*p + w/2 - Yoff)
self.RectUpdate(self.etchLines[j],
ci*(k*p - wp/2 + Xoff)+thsi, l*p - wp/2 - Yoff,
ci*(k*p + wp/2 + Xoff)+thsi, l*p + wp/2 - Yoff)
j+=1 j+=1
def UpdateFig(self): def UpdateFig(self):
...@@ -232,6 +320,7 @@ class TZPGcalc(): ...@@ -232,6 +320,7 @@ class TZPGcalc():
theta_grating = self.grating_slider.value*1e-3 # [rad] theta_grating = self.grating_slider.value*1e-3 # [rad]
sampleZ = self.samz_slider.value*1e-3 # [m] sampleZ = self.samz_slider.value*1e-3 # [m]
samIncidence = np.deg2rad(self.samIncidence_slider.value) # [rad]
detectorZ = self.det_slider.value*1e-3 # [m] detectorZ = self.det_slider.value*1e-3 # [m]
TZPGwH = self.TZPGwH_slider.value*1e-3 #[m] TZPGwH = self.TZPGwH_slider.value*1e-3 #[m]
TZPGwV = self.TZPGwV_slider.value*1e-3 #[m] TZPGwV = self.TZPGwV_slider.value*1e-3 #[m]
...@@ -253,10 +342,10 @@ class TZPGcalc(): ...@@ -253,10 +342,10 @@ class TZPGcalc():
'TZPGwH':TZPGwH, 'TZPGwV':TZPGwV, 'TZPGo':TZPGo} 'TZPGwH':TZPGwH, 'TZPGwV':TZPGwV, 'TZPGo':TZPGo}
# update the beams # update the beams
self.UpdateBeams(self.samBeamsL, Z0 + sampleZ, confL) self.UpdateBeams(self.samBeamsL, Z0 + sampleZ, samIncidence, confL)
self.UpdateBeams(self.detBeamsL, Z0 + detectorZ, confL) self.UpdateBeams(self.detBeamsL, Z0 + detectorZ, 0, confL)
self.UpdateBeams(self.samBeamsH, Z0 + sampleZ, confH) self.UpdateBeams(self.samBeamsH, Z0 + sampleZ, samIncidence, confH)
self.UpdateBeams(self.detBeamsH, Z0 + detectorZ, confH) self.UpdateBeams(self.detBeamsH, Z0 + detectorZ, 0, confH)
# update the detector # update the detector
detXoff = self.detX_slider.value*1e-3 #[m] detXoff = self.detX_slider.value*1e-3 #[m]
...@@ -269,7 +358,9 @@ class TZPGcalc(): ...@@ -269,7 +358,9 @@ class TZPGcalc():
samXoff = self.samX_slider.value*1e-3 #[m] samXoff = self.samX_slider.value*1e-3 #[m]
samYoff = self.samY_slider.value*1e-3 #[m] samYoff = self.samY_slider.value*1e-3 #[m]
samthickness = self.samthickness_slider.value*1e-6 #[m] samthickness = self.samthickness_slider.value*1e-6 #[m]
self.SampleUpdate(samw, samp, samXoff, samYoff, samthickness) samEtchAngle = np.deg2rad(self.samEtchAngle_slider.value) #[rad]
self.SampleUpdate(samw, samp, samXoff, samYoff, samthickness,
samIncidence, samEtchAngle)
def initWidgets(self): def initWidgets(self):
""" Creates the necessary interactive widget controls. """ Creates the necessary interactive widget controls.
...@@ -379,12 +470,28 @@ class TZPGcalc(): ...@@ -379,12 +470,28 @@ class TZPGcalc():
step=1, step=1,
readout_format='.0f', readout_format='.0f',
) )
self.samIncidence_slider = widgets.FloatSlider(
value=0,
min=0,
max=90,
step=1,
readout_format='.0f',
)
self.samEtchAngle_slider = widgets.FloatSlider(
value=54.74,
min=0,
max=90,
step=0.01,
readout_format='.2f',
)
samTab = VBox(children=[HBox([widgets.Label(value='Sample Z (mm):'), self.samz_slider]), 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(children=[HBox([widgets.Label(value='Membrane width (mm):'), self.samw_slider]),
HBox([widgets.Label(value='Membrane pitch (mm):'), self.samp_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(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='Sample Y-Offset (mm):'), self.samY_slider])]),
HBox([widgets.Label(value='Substrate thickness (um):'), self.samthickness_slider]) HBox([widgets.Label(value='Substrate thickness (um):'), self.samthickness_slider]),
HBox([HBox([widgets.Label(value='Normal incidence (deg):'), self.samIncidence_slider]),
HBox([widgets.Label(value='Etch angle from surface (deg):'), self.samEtchAngle_slider])])
]) ])
#detector tab #detector tab
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment