diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calc_sideview.py b/debye_bec/bec_widgets/widgets/digital_twin/calc_sideview.py index ce97a31..5a0f930 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/calc_sideview.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/calc_sideview.py @@ -8,34 +8,34 @@ def calc_sideview(cfg): # beam height (Y=height, Z=along beam) beam = {} - beam['Z'] = [] - beam['Y'] = [] - beam['Z'].append(0) # Source - beam['Y'].append(bl.sourceHeight) - beam['Z'].append(bl.cm.center[1]) # CM - beam['Y'].append(bl.sourceHeight) + beam['x'] = [] + beam['y'] = [] + beam['x'].append(0) # Source + beam['y'].append(bl.sourceHeight) + beam['x'].append(bl.cm.center[1]) # CM + beam['y'].append(bl.sourceHeight) if cfg['mo1_mode'] in 'Monochromatic': diag = bl.mo1.xtalGap[0]/np.sin(cfg['mo1_bragg']) # Calculations for Mono dy = diag*np.sin(2*(cfg['cm_pitch']+cfg['mo1_bragg'])) dz = diag*np.cos(2*(cfg['cm_pitch']+cfg['mo1_bragg'])) - beam['Z'].append(bl.mo1.center[1]-dz/2) # Mono 1.1 - beam['Y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1])) - beam['Z'].append(bl.mo1.center[1]+dz/2) # Mono 1.2 - beam['Y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1])+dy) - beam['Z'].append(bl.fm.center[1]) # FM - beam['Y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy) - beam['Z'].append(cfg['smpl']) # Experiment - beam['Y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy+np.tan(2*(cfg['cm_pitch']-cfg['fm_pitch']))*(cfg['smpl']-bl.fm.center[1])) + beam['x'].append(bl.mo1.center[1]-dz/2) # Mono 1.1 + beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1])) + beam['x'].append(bl.mo1.center[1]+dz/2) # Mono 1.2 + beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1])+dy) + beam['x'].append(bl.fm.center[1]) # FM + beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy) + beam['x'].append(cfg['smpl']) # Experiment + beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy+np.tan(2*(cfg['cm_pitch']-cfg['fm_pitch']))*(cfg['smpl']-bl.fm.center[1])) elif cfg['mo1_mode'] == 'Pinkbeam': - beam['Z'].append(bl.fm.center[1]) # FM - beam['Y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1])) - beam['Z'].append(cfg['smpl']) # Experiment - beam['Y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1])+np.tan(2*(cfg['cm_pitch']-cfg['fm_pitch']))*(cfg['smpl']-bl.fm.center[1])) + beam['x'].append(bl.fm.center[1]) # FM + beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1])) + beam['x'].append(cfg['smpl']) # Experiment + beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1])+np.tan(2*(cfg['cm_pitch']-cfg['fm_pitch']))*(cfg['smpl']-bl.fm.center[1])) - dy_fm_ex = beam['Y'][-1] - beam['Y'][-2] - dz_fm_ex = beam['Z'][-1] - beam['Z'][-2] - dz_fm_win = bl.ehWindow.center[1] - beam['Z'][-2] - h_at_win = beam['Y'][-2] + dy_fm_ex / dz_fm_ex * dz_fm_win + dy_fm_ex = beam['y'][-1] - beam['y'][-2] + dz_fm_ex = beam['x'][-1] - beam['x'][-2] + dz_fm_win = bl.ehWindow.center[1] - beam['x'][-2] + h_at_win = beam['y'][-2] + dy_fm_ex / dz_fm_ex * dz_fm_win beam['heightWindow'] = h_at_win diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py b/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py index 55bd5e1..11c1c42 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py @@ -1,43 +1,9 @@ -import sys import re import numpy as np from scipy.interpolate import UnivariateSpline from xrt.backends.raycing.physconsts import CHeVcm, AVOGADRO from bec_lib import bec_logger -# pylint: disable=E0611 -from qtpy.QtWidgets import ( - QWidget, - QVBoxLayout, - QHBoxLayout, - QApplication, - QLayout, -) -# pylint: disable=E0611 -from qtpy.QtCore import ( - Qt, - QTimer, -) -from qtpy.QtGui import ( - QColor, - QBrush, -) -import pyqtgraph as pg - -from bec_widgets.utils.bec_widget import BECWidget -from bec_widgets.utils.error_popups import SafeSlot - -from debye_bec.bec_widgets.widgets.qt_widgets import ( - InputNumberField, - ComboBox, - Group, - NumberIndicator, - Mover, -) -from debye_bec.bec_widgets.widgets.digital_twin.calc_positions import calc_positions -from debye_bec.bec_widgets.widgets.digital_twin.calc_sideview import calc_sideview -from debye_bec.bec_widgets.widgets.digital_twin.calc_surfaces import calc_surfaces - import debye_bec.bec_widgets.widgets.x01da_parameters as bl logger = bec_logger.logger @@ -171,3 +137,66 @@ def cm_critical_angle(cm_stripe, energy): numberDensity = stripe.rho*1e3*AVOGADRO/(stripe.elements[0].mass/1e3) criticalAngle = np.sqrt(numberDensity*2.8179e-15*w**2*f1/np.pi) return criticalAngle + + +def mirror_surface_geometries(mirror): + if mirror in "cm": + surface = bl.cm.surface + limOptX = bl.cm.limOptX + limOptY = bl.cm.limOptY + elif mirror in 'fm_toroid': + surface = bl.fm.surfaceToroid + limOptX = bl.fm.limOptXToroid + limOptY = bl.fm.limOptYToroid + elif mirror in 'fm_flat': + surface = bl.fm.surfaceFlat + limOptX = bl.fm.limOptXFlat + limOptY = bl.fm.limOptYFlat + else: + raise ValueError(f'Requested mirror {mirror} not available!') + geom = {} + for sf, lx, hx, ly, hy in zip(surface, limOptX[0], limOptX[1], limOptY[0], limOptY[1]): + geom[sf] = (lx, ly, hx-lx, hy-ly) + return geom + +def mo_surface_geometries(mo, plane): + if mo in 'mo1': + xtal = bl.mo1.xtal + xtal_width = bl.mo1.xtalWidth + xtal_offset_x = bl.mo1.xtalOffsetX + if plane == 0: + xtal_length = bl.mo1.xtalLength1 + else: + xtal_length = bl.mo1.xtalLength2 + else: + raise ValueError(f'Requested mono {mo} not available!') + geom = {} + for sf, w, offx, length in zip(xtal, xtal_width, xtal_offset_x, xtal_length): + geom[sf] = (offx-w/2, -length/2, w, length) + return geom + +def wall_geometries(): + geom = [] + for i, _ in enumerate(bl.walls.start): + geom.append([ + bl.walls.start[i], + bl.walls.height[i][0], + bl.walls.end[i] - bl.walls.start[i], + bl.walls.height[i][1] - bl.walls.height[i][0], + ]) + return geom + +def pipe_geometries(): + pipes = [] + for i, _ in enumerate(bl.vacuum_pipes.center): + top = bl.vacuum_pipes.center[i] + bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight + bottom = bl.vacuum_pipes.center[i] - bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight + pipes.append({ + 'x': np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]), + 'y': np.array([top, top]) + }) + pipes.append({ + 'x': np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]), + 'y': np.array([bottom, bottom]) + }) + return pipes diff --git a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py index cabaa96..2326e1d 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py @@ -3,10 +3,7 @@ Digital Twin: Custom BEC widget to support the beamline alignment. """ import sys -import re import numpy as np -from scipy.interpolate import UnivariateSpline -from xrt.backends.raycing.physconsts import CHeVcm, AVOGADRO from bec_lib import bec_logger # pylint: disable=E0611 @@ -51,10 +48,12 @@ from debye_bec.bec_widgets.widgets.digital_twin.calc_varia import ( mo1_bragg_angle, fm_ideal_pitch, cm_critical_angle, + mirror_surface_geometries, + mo_surface_geometries, + wall_geometries, + pipe_geometries, ) -import debye_bec.bec_widgets.widgets.x01da_parameters as bl - logger = bec_logger.logger class DigitalTwin(BECWidget, QWidget): @@ -245,21 +244,24 @@ class DigitalTwin(BECWidget, QWidget): self.input.fm_pitch_ideal.setVisible(True) self.input.fm_focx.setVisible(False) self.input.fm_focy.setVisible(False) + self.input.fm_pitch_ideal.setLabel('Incidence Angle for focused beam') elif fm_focus in 'Focused': self.input.fm_pitch.setVisible(False) self.input.fm_pitch_ideal.setVisible(True) self.input.fm_focx.setVisible(False) self.input.fm_focy.setVisible(False) + self.input.fm_pitch_ideal.setLabel('Incidence Angle for focused beam') else: # Defocused self.input.fm_pitch.setVisible(False) self.input.fm_pitch_ideal.setVisible(True) self.input.fm_focx.setVisible(True) self.input.fm_focy.setVisible(True) + self.input.fm_pitch_ideal.setLabel('Incidence Angle for defocused beam') def calc_reality(self): config = self.get_reality_config() beam = calc_sideview(config) - data = {'x': beam['Z'], 'y': beam['Y']} # TODO: Refactor sideview calculator to match data format + data = {'x': beam['x'], 'y': beam['y']} self.sideview_plot.update_curves('reality', data) surfaces = calc_surfaces(config) self.surface_plots.update_surfaces(scene='reality', data=surfaces) @@ -298,7 +300,7 @@ class DigitalTwin(BECWidget, QWidget): def calc_assistant_sideview(self): beam = calc_sideview(self.get_assistant_config()) - data = {'x': beam['Z'], 'y': beam['Y']} + data = {'x': beam['x'], 'y': beam['y']} self.sideview_plot.update_curves('assistant', data) def calc_assistant_surfaces(self): @@ -382,11 +384,11 @@ class InputPanel(QWidget): self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore # Energy - self.energy = InputNumberField('energy', 'Energy [eV]', init=8979, decimals=0, single_step=100, ll=4000, hl=65000) + self.energy = InputNumberField('energy', 'Energy', unit='eV', init=8979, decimals=0, single_step=100, ll=4000, hl=65000) # FE Slits Acceptance - self.sldi_hacc = InputNumberField('h_acc', 'Horizontal [± mrad]', init=0.25, decimals=2, single_step=0.01, ll=-0.1, hl=0.9) - self.sldi_vacc = InputNumberField('v_acc', 'Vertical [± mrad]', init=0.1, decimals=2, single_step=0.01, ll=-0.1, hl=0.5) + self.sldi_hacc = InputNumberField('h_acc', 'Horizontal', unit='mrad', prefix='±', init=0.25, decimals=2, single_step=0.01, ll=-0.1, hl=0.9) + self.sldi_vacc = InputNumberField('v_acc', 'Vertical', unit='mrad', prefix='±', init=0.1, decimals=2, single_step=0.01, ll=-0.1, hl=0.5) self.sldi_ass_group = Group( 'FE Slits Acceptance', [ @@ -397,7 +399,7 @@ class InputPanel(QWidget): # Collimating mirror self.cm_stripe = ComboBox('cm_stripe', 'Stripe', ['Si', 'Rh', 'Pt']) - self.cm_pitch = InputNumberField('cm_pitch', 'Pitch [mrad]', init=-2.391, decimals=3, single_step=0.01, ll=-4.6, hl=-1.2) + self.cm_pitch = InputNumberField('cm_pitch', 'Pitch', unit='mrad', init=-2.391, decimals=3, single_step=0.01, ll=-4.6, hl=-1.2) self.cm_pitch_critical = NumberIndicator('Critical Pitch', 'mrad', decimals=3) self.cm_refl = NumberIndicator('Reflectivity at x eV', '%', decimals=0) self.cm_refl_harm = NumberIndicator('Reflectivity at x eV', '%', decimals=0) @@ -430,9 +432,9 @@ class InputPanel(QWidget): # Focusing Mirror self.fm_stripe = ComboBox('fm_stripe', 'Stripe', ['Rh (toroid)', 'Rh (flat)', 'Pt (toroid)', 'Pt (flat)']) self.fm_focus = ComboBox('fm_focus', 'Focus Type', ['Manual', 'Focused', 'Defocused']) - self.fm_pitch = InputNumberField('fm_pitch', 'Incidence Angle [mrad]', init=-2.391, decimals=3, single_step=0.01, ll=-10, hl=2) - self.fm_focx = InputNumberField('fm_focx', 'Beam Size Horizontal [mm]', init=1, decimals=1, single_step=0.1, ll=0, hl=30) - self.fm_focy = InputNumberField('fm_focy', 'Beam Size Vertical [mm]', init=1, decimals=1, single_step=0.1, ll=0, hl=10) + self.fm_pitch = InputNumberField('fm_pitch', 'Incidence Angle', unit='mrad', init=-2.391, decimals=3, single_step=0.01, ll=-10, hl=2) + self.fm_focx = InputNumberField('fm_focx', 'Beam Size Horizontal', unit='mm', init=1, decimals=1, single_step=0.1, ll=0, hl=30) + self.fm_focy = InputNumberField('fm_focy', 'Beam Size Vertical', unit='mm', init=1, decimals=1, single_step=0.1, ll=0, hl=10) self.fm_pitch_ideal = NumberIndicator('Incidence Angle for focused beam', 'mrad', decimals=3) self.fm_refl = NumberIndicator('Reflectivity at x eV', '%', decimals=0) self.fm_refl_harm = NumberIndicator('Reflectivity at x eV', '%', decimals=0) @@ -452,7 +454,7 @@ class InputPanel(QWidget): # Sample self.cm_fm_harm_suppr = NumberIndicator('Total Suppression Factor at x eV', '', decimals=0) - self.smpl = InputNumberField('smpl', 'Sample Position [mm]', init=23511, decimals=0, single_step=100, ll=23000, hl=30000) + self.smpl = InputNumberField('smpl', 'Sample Position', unit='mm', init=23511, decimals=0, single_step=100, ll=23000, hl=30000) # Assemble complete assitant group self.input_group = Group( @@ -767,52 +769,29 @@ class SurfacePlots(QWidget): def plot_walls(self): - def plot_mirror_stripe(widget, surface, limOptX, limOptY): - for sf, lx, hx, ly, hy in zip(surface, limOptX[0], limOptX[1], limOptY[0], limOptY[1]): - rect = pg.QtWidgets.QGraphicsRectItem( # pylint: disable=E1101 - lx, - ly, - hx - lx, - hy - ly, - ) + def plot_surface(widget, surfaces): + for name, surface in surfaces.items(): + rect = pg.QtWidgets.QGraphicsRectItem(*surface) # pylint: disable=E1101 rect.setBrush(pg.QtGui.QBrush(pg.QtGui.QColor(*self.color_impenetrable))) # pylint: disable=E1101 rect.setPen(pg.mkPen(color=self.color_impenetrable, width=2)) widget.addItem(rect) - text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5)) + text = pg.TextItem(name, color=self.text_color, anchor=(0.5, 0.5)) widget.addItem(text) - text.setPos((hx+lx)/2, (hy+ly)/2) - text.setZValue(10) - self.walls.append(rect) - self.texts.append(text) - - def plot_mono_surface(widget, xtal, xtalWidth, xtalOffsetX, xtalLength): - for sf, w, offx, length in zip(xtal, xtalWidth, xtalOffsetX, xtalLength): - rect = pg.QtWidgets.QGraphicsRectItem( # pylint: disable=E1101 - offx - w/2, - -length/2, - w, - length, - ) - rect.setBrush(pg.QtGui.QBrush(pg.QtGui.QColor(*self.color_impenetrable))) # pylint: disable=E1101 - rect.setPen(pg.mkPen(color=self.color_impenetrable, width=2)) - widget.addItem(rect) - text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5)) - widget.addItem(text) - text.setPos(offx, 0) + text.setPos(surface[0]+surface[2]/2, surface[1]+surface[3]/2) text.setZValue(10) self.walls.append(rect) self.texts.append(text) for name, plot in self.plots.items(): if name in 'cm': - plot_mirror_stripe(plot['widget'], bl.cm.surface, bl.cm.limOptX, bl.cm.limOptY) + plot_surface(plot['widget'], mirror_surface_geometries('cm')) elif name in 'mo1_1': - plot_mono_surface(plot['widget'], bl.mo1.xtal, bl.mo1.xtalWidth, bl.mo1.xtalOffsetX, bl.mo1.xtalLength1) + plot_surface(plot['widget'], mo_surface_geometries ('mo1', 0)) elif name in 'mo1_2': - plot_mono_surface(plot['widget'], bl.mo1.xtal, bl.mo1.xtalWidth, bl.mo1.xtalOffsetX, bl.mo1.xtalLength2) + plot_surface(plot['widget'], mo_surface_geometries ('mo1', 1)) elif name in 'fm': - plot_mirror_stripe(plot['widget'], bl.fm.surfaceFlat, bl.fm.limOptXFlat, bl.fm.limOptYFlat) - plot_mirror_stripe(plot['widget'], bl.fm.surfaceToroid, bl.fm.limOptXToroid, bl.fm.limOptYToroid) + plot_surface(plot['widget'], mirror_surface_geometries('fm_flat')) + plot_surface(plot['widget'], mirror_surface_geometries('fm_toroid')) else: raise Exception(f'Plot {name} not found!') for name, plot in self.plots.items(): @@ -929,32 +908,22 @@ class SideviewPlot(QWidget): pipe.setPen(pg.mkPen(color=self.color_impenetrable, width=2)) def plot_vacuum_pipes(self): - for i, _ in enumerate(bl.vacuum_pipes.center): - top = bl.vacuum_pipes.center[i] + bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight - bottom = bl.vacuum_pipes.center[i] - bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight + pipes = pipe_geometries() + for pipe in pipes: self.pipes.append(self.plot_widget.plot( - x=np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]), - y=np.array([top, top]), - pen=pg.mkPen(color=self.color_impenetrable, width=2), - )) - self.pipes.append(self.plot_widget.plot( - x=np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]), - y=np.array([bottom, bottom]), + x=pipe['x'], + y=pipe['y'], pen=pg.mkPen(color=self.color_impenetrable, width=2), )) def plot_walls(self): - for i, _ in enumerate(bl.walls.start): - rect = pg.QtWidgets.QGraphicsRectItem( # pylint: disable=E1101 - bl.walls.start[i], - bl.walls.height[i][0], - bl.walls.end[i] - bl.walls.start[i], - bl.walls.height[i][1] - bl.walls.height[i][0], - ) + walls = wall_geometries() + for wall in walls: + rect = pg.QtWidgets.QGraphicsRectItem(*wall) # pylint: disable=E1101 rect.setBrush(pg.QtGui.QBrush(pg.QtGui.QColor(*self.color_impenetrable))) # pylint: disable=E1101 rect.setPen(pg.mkPen(color=self.color_impenetrable, width=2)) self.plot_widget.addItem(rect) - self.walls.append(rect) + self.walls.append(rect) def update_curves(self, scene, data): self.data[scene] = data diff --git a/debye_bec/bec_widgets/widgets/qt_widgets.py b/debye_bec/bec_widgets/widgets/qt_widgets.py index 3791faf..c5f2057 100644 --- a/debye_bec/bec_widgets/widgets/qt_widgets.py +++ b/debye_bec/bec_widgets/widgets/qt_widgets.py @@ -113,7 +113,7 @@ class NumberIndicator(QWidget): # ) class InputNumberField(QWidget): - def __init__(self, identifier='', label='', init=0.0, decimals=1, single_step=0.1, ll=-1e6, hl=1e6): + def __init__(self, identifier='', label='', unit=None, prefix=None, init=0.0, decimals=1, single_step=0.1, ll=-1e6, hl=1e6): super().__init__() layout = QHBoxLayout(self) layout.setContentsMargins(10, 0, 0, 0) @@ -129,6 +129,10 @@ class InputNumberField(QWidget): self.val.setDecimals(decimals) self.val.setSingleStep(single_step) self.val.setValue(init) + if unit is not None: + self.val.setSuffix(' ' + unit) + if prefix is not None: + self.val.setPrefix(prefix + ' ') # self.val.setFixedWidth(140) layout.addWidget(self.val)