Compare commits

..

309 Commits

Author SHA1 Message Date
2cf2f4b4e4 test(controller): Fix test for controller wait_for_connection
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m16s
CI for csaxs_bec / test (push) Successful in 1m26s
2026-01-16 10:52:24 +01:00
1a9a0beb86 fix(controller): Ensure wait_for_connection calls controller.on()
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m13s
CI for csaxs_bec / test (pull_request) Successful in 1m19s
2026-01-15 18:09:34 +01:00
9f9aef348a some more docs
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m16s
CI for csaxs_bec / test (push) Successful in 1m18s
2026-01-08 12:36:14 +01:00
x01dc
f7a313b37f added file selection in gui docs
Some checks failed
CI for csaxs_bec / test (push) Has been cancelled
2026-01-08 12:06:47 +01:00
7326c471f8 test(falcon): fix test for improved patched_device method in ophyd_devices
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m19s
CI for csaxs_bec / test (push) Successful in 1m18s
2026-01-07 11:17:50 +01:00
4b95ebace3 test(falcon): fix test after falcon refactoring 2026-01-07 11:17:50 +01:00
c1dee287b8 refactor(falcon): Migrate Falcon integration to PsiDeviceBase 2026-01-07 11:17:50 +01:00
dd3b0144b9 feat(pilatus): deprecate pilatus integration 2026-01-07 11:17:50 +01:00
149af32ab1 fix: rate limit warning log in live mode
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m30s
CI for csaxs_bec / test (push) Successful in 1m32s
2026-01-06 13:22:43 +01:00
x01dc
47f0b66791 mod gui tools for pdf viewer
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m29s
CI for csaxs_bec / test (push) Successful in 1m28s
2026-01-06 12:49:40 +01:00
2c0fced9b7 FZP layout 60 nm
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m32s
2026-01-06 12:00:56 +01:00
d99b44b619 refactor(configs):Migrate deviceClass for EpicsMotorEX to EpicsUserMotorVME
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m30s
CI for csaxs_bec / test (push) Successful in 1m30s
2025-12-11 07:52:52 +01:00
x12sa
dbab981ac2 add micfoc and change some motor settings 2025-12-11 07:52:52 +01:00
x12sa
bdd7f1767f new yaml file sastt for sastt setup 2025-12-11 07:52:52 +01:00
0f41648053 test(rt-flomni): fix tests for rt-flomni
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m25s
CI for csaxs_bec / test (push) Successful in 1m35s
2025-12-09 16:51:24 +01:00
ec45bb4c33 fix(controller): deprecate get_device_manager() in favor of dm 2025-12-09 16:34:53 +01:00
ac8177a132 fix(controller): add controller.on to wait_for_connection for devices with socket controllers 2025-12-09 16:34:53 +01:00
36e8d87411 refactor(controller): refactor set_device_enable method from controller to set_device_read_write 2025-12-09 16:34:53 +01:00
f56a834db5 fix(controller): fix controller init for all controller instances, fix formatting 2025-12-09 16:34:53 +01:00
90d2c99c4a fix: remove deprecated bl_check
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m15s
CI for csaxs_bec / test (push) Successful in 1m22s
2025-12-05 17:16:13 +01:00
6a4bfc73f6 fix(status): fix usage of Compare, Transition Status in delaygenerator integration
Some checks failed
CI for csaxs_bec / test (pull_request) Failing after 1m18s
CI for csaxs_bec / test (push) Successful in 1m23s
2025-11-30 22:27:52 +01:00
x01dc
22d8dbe972 added force monochrome mode plus delay after disconnect to ensure
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m45s
CI for csaxs_bec / test (push) Successful in 1m18s
working after config reload
2025-11-12 13:36:12 +01:00
x01dc
2411e7be56 new shutter device
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m17s
CI for csaxs_bec / test (push) Successful in 1m17s
2025-11-07 14:34:05 +01:00
f8b20752f5 updated gui section
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m22s
2025-11-05 13:12:59 +01:00
e301b94e7c fix: add num_rotation_90 and transpose to ids_camera
Some checks failed
CI for csaxs_bec / test (pull_request) Failing after 1m0s
CI for csaxs_bec / test (push) Successful in 1m25s
2025-11-04 14:12:04 +01:00
x01dc
61011f098d attribute based checking if window exists
Some checks failed
CI for csaxs_bec / test (pull_request) Failing after 42s
CI for csaxs_bec / test (push) Successful in 1m33s
2025-11-04 13:39:35 +01:00
x01dc
efca170f04 minor fixes: basepath correction file, raise gui
Some checks failed
CI for csaxs_bec / test (pull_request) Failing after 38s
CI for csaxs_bec / test (push) Successful in 1m21s
2025-11-03 11:23:32 +01:00
x01dc
af2a69f825 various fixes or adjustments during testing the new alignment gui
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m18s
CI for csaxs_bec / test (push) Successful in 1m18s
2025-10-31 11:38:27 +01:00
c8c71d466c refactor(xray_gui): minor cleanup
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m17s
CI for csaxs_bec / test (push) Successful in 1m14s
2025-10-23 14:39:59 +02:00
94d984b8a2 fix(camera): BGR to RGB logic moved to Camera 2025-10-23 14:39:59 +02:00
x01dc
39d2c97247 fix: xray eye align script changes 2025-10-23 14:39:59 +02:00
x01dc
de22611941 refactor(ids_camera): old ids_camera deleted, ids_camera_new is now ids_camera 2025-10-23 14:39:59 +02:00
4723f6768b feat(xray_eye): add XRayEye widget and plugin for GUI integration 2025-10-23 11:07:30 +02:00
x01dc
5b76c3f769 feat(gui_instruction_device): added gui instruction device from epics 2025-10-23 11:04:21 +02:00
4590b85010 fix(omny_alignment): disabled not working signal connection to on_move_up 2025-10-23 11:02:28 +02:00
7233fb8d35 w
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m16s
CI for csaxs_bec / test (push) Successful in 1m19s
2025-10-22 10:55:58 +02:00
32d3232008 fix(ids-camera): fix roi_signal after AsyncSignal refactoring
Some checks failed
CI for csaxs_bec / test (pull_request) Failing after 3s
CI for csaxs_bec / test (push) Failing after 23s
2025-10-22 10:48:40 +02:00
x01dc
725eed17ed finalized flomni temp and humidity display
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m16s
CI for csaxs_bec / test (push) Successful in 1m15s
2025-10-14 11:41:31 +02:00
x01dc
1d9fb39c0e initial version flomni temp humidity 2025-10-14 11:41:31 +02:00
x01dc
f1dd299fad gui tools running 2025-10-14 11:41:31 +02:00
1fcb213336 ci: pull from github instead of gitea
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m23s
CI for csaxs_bec / test (push) Successful in 1m22s
2025-10-08 16:33:34 +02:00
x01dc
b08c7bf44b removed debug info
Some checks failed
CI for csaxs_bec / test (push) Failing after 34s
CI for csaxs_bec / test (pull_request) Failing after 35s
2025-10-08 16:23:38 +02:00
x01dc
d16f6b703c tested gui tools 2025-10-08 16:23:38 +02:00
x01dc
f526d5cc05 gui tools running 2025-10-08 16:23:38 +02:00
gac-x01dc
8f7914b978 - added camera viewer device
- fixed some issues in flomni sample
storage device
2025-10-08 16:22:13 +02:00
gac-x01dc
6c65d5546c fixed issue with flomni storage not updating and start using OMNYTools for yesno 2025-10-08 16:22:13 +02:00
2bb6667f30 test(eiger): cleanup and add tests
Some checks failed
CI for csaxs_bec / test (push) Failing after 34s
2025-09-17 17:40:43 +02:00
e3f337e7c3 fix: deprecate old eiger9m_csaxs integration 2025-09-17 17:40:43 +02:00
419d15dcdb fix(mcs-card): fix mcs card test for on_connected 2025-09-17 17:40:43 +02:00
84537eafde refactor(eiger): cleanup, remove auto initialization 2025-09-17 17:40:43 +02:00
6a864c9bc6 refactor(jungfrau-joch-client): Improve wait_till_idle method 2025-09-17 17:40:43 +02:00
afbf7adff5 refactor(eiger): add file event signal 2025-09-17 17:40:43 +02:00
dd304f2f3b feat(eiger): add jfj integration 2025-09-17 17:40:43 +02:00
216d8c200b fix: assign session to session_name instead of username
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m32s
CI for csaxs_bec / test (push) Successful in 1m30s
2025-09-12 17:01:11 +02:00
x12sa
01a9d607a6 add slit1 center and size to device config file
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m30s
CI for csaxs_bec / test (push) Successful in 1m32s
2025-09-12 15:50:36 +02:00
9e48dca5e2 refactor: copier upgrade to v1-2-2
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m31s
CI for csaxs_bec / test (push) Successful in 1m31s
2025-09-12 15:44:09 +02:00
Holler
dc3d51afc4 fixed issue with move_to_start 2025-09-12 15:43:38 +02:00
Holler
2a579c957d fixes 2025-09-12 15:43:38 +02:00
Holler
66cd89ed63 flomni fixes 2025-09-12 15:43:38 +02:00
d06fe65491 feat: update repository with copier template for migration to gitea 2025-09-11 15:23:35 +02:00
189141a047 refactor(ids-camera): add additional information to the docstrings 2025-08-06 15:30:10 +02:00
f69e643092 refactor(mcs): imrove documentation for counter updates 2025-08-06 14:05:18 +02:00
5c263bdb63 refactor(ids-camera): improve docs and camera class 2025-08-06 14:05:18 +02:00
9f5254abe2 refactor(ddg): cleanup and improve comments 2025-08-06 13:43:54 +02:00
a58c23845f refactor(ddg1): remove threadpool, use dedicated thread for polling 2025-08-06 10:50:39 +02:00
c7e11eeabc refactor(ddg): use threadpool with active polling for state 2025-08-05 16:11:56 +02:00
a03e99d615 refactor(ddg): passiv readout of event_status register 2025-08-05 12:31:54 +02:00
3c9192d6a5 refactor(ddg2): add check for negative pulse widths. 2025-08-05 12:31:42 +02:00
a01593fa4b refactor(ids_camera): add deprecation warning for ids_camera 2025-08-05 12:31:13 +02:00
gac-x12sa
40d6acf431 refactor: change sl1 names following convention of controls 2025-08-05 12:31:13 +02:00
9e45e927a0 test(ids-camera): add tests for the IDSCamera integration 2025-08-05 12:31:13 +02:00
8f7ada2f92 refactor(ids): refactor ids camera 2025-08-05 12:31:13 +02:00
gac-x12sa
b6af93806a fix: remove get_config from pre_startup 2025-08-05 12:31:13 +02:00
gac-x12sa
3db74d9877 fix: put idgap to readonly false 2025-07-22 11:57:12 +02:00
gac-x12sa
e6d7e0d82f refactor: rename ddg test config to endstation, add to first light 2025-07-22 11:57:12 +02:00
14713c0d2f fix: remove old mcs card from config 2025-07-22 11:57:12 +02:00
3fd3d54003 refactor(mcs-card): cleanup class, add tests 2025-07-21 22:27:14 +02:00
c2cba873d4 refactor: remove old mcs card 2025-07-21 22:27:14 +02:00
7679fa1383 fix: cleanup, fix mcs_clock 2025-07-21 22:27:14 +02:00
cdac29bed1 fix(xbpms): generalize describe method of sum,diff signals 2025-07-21 22:26:58 +02:00
7a3ce97e30 refactor: cleanup and formatting 2025-07-21 22:26:26 +02:00
gac-x12sa
62ecdd37ff refactor(mcs-card): fix mcs card integration at the beamline 2025-07-21 22:26:26 +02:00
256adb14d4 refactor(mcs-card): refactor mcs card integration 2025-07-21 22:26:26 +02:00
gac-x12sa
3cda1c74b4 fix(xbpm): add auto monitor and timestamp updates 2025-07-18 12:15:00 +02:00
e3077f6d2f feat(xbpms): add diag signal and normalize diff signals 2025-07-18 12:00:11 +02:00
a4c1b89395 refactor: update copier template to v1.1.2 2025-07-17 14:59:20 +02:00
gac-x12sa
909f0d2c96 fix: add sleep to ddg2 for pre_scan to be ready for triggers 2025-07-16 14:25:45 +02:00
4812323e4b refactor: cleanup and remove old classes from configs 2025-07-16 14:25:45 +02:00
809a8834fa test: add tests for ddg1 and ddg2 implementation at csaxs 2025-07-16 14:25:45 +02:00
98e4364176 feat: adding ddg1 and ddg2 logic for csaxs 2025-07-16 14:25:45 +02:00
173893ec33 refacto: remove old configs 2025-07-16 14:25:45 +02:00
1c539a1e9c refactor(ddg): final refactoring and cleanup 2025-07-16 14:25:45 +02:00
293e56fba8 feat(ddg): rewrite of the delay generator integration for cSAXS 2025-07-16 14:25:45 +02:00
gac-x12sa
44a4dfc818 add comments to mark devices as deprecated 2025-07-15 17:02:26 +02:00
gac-x12sa
53d1798c84 feat: added xbpm device and added in config 2025-07-15 16:56:01 +02:00
gac-x12sa
a4aa1da5dd add xbpm1 current monitors and fix X12SA-UIND in idgap 2025-07-15 15:31:52 +02:00
gac-x12sa
f332e06240 feat:add frontend xbpm and slit motors to device config 2025-07-11 15:46:44 +02:00
gac-x12sa
ef76e4b49b change xbpm1 to xbpm2 in optics hutch yaml file 2025-07-10 09:47:22 +02:00
gac-x12sa
6734143dc8 feat:add frontend motor and first_light config 2025-07-09 11:08:54 +02:00
gac-x12sa
66622fd75d change cu_foilx motor name to lower case c 2025-07-04 15:32:27 +02:00
gac-x12sa
e9fc6930ff add and test smaract stages inside OPbox 2025-07-04 15:26:06 +02:00
gac-x12sa
8ca1f00d1c add channel-cut motors and change motor names to shorter names 2025-07-04 14:28:25 +02:00
gac-x12sa
fad4611144 set readOnly to false for DMM motors and correct description 2025-07-01 11:42:39 +02:00
gac-x12sa
c51379a2cc feat:add initial optics hutch config 2025-06-27 16:21:08 +02:00
2baaaac1f3 fix: copier update for pre_startup 2025-06-11 16:53:34 +02:00
gac-x12sa
8eac0a2c38 wip 2025-06-11 16:45:51 +02:00
2894b1e5c2 wip test template for bec_config.yaml resolution 2025-06-11 16:45:51 +02:00
gac-x12sa
afd6590360 fix(pre-startup): update pre_startup get_config for test, production and local usage 2025-06-11 16:45:51 +02:00
63ee870c89 fix(test-x-ray-eye): update DeviceBase inits with proper config as needed due to PR #506 in BEC 2025-06-11 16:26:52 +02:00
36a2e55474 fix: update upstream repo for plugin template 2025-06-11 16:04:58 +02:00
19d117619a refactor: replace deprecated consumer/producer with connector 2025-06-04 19:38:30 +02:00
447a9c3924 refactor: upgrade to copier version v1 2025-05-24 11:37:51 +02:00
Holler Mirko
17e525689c initial version of omny alignment gui 2025-04-23 13:04:13 +02:00
4907c5db27 fix: add get_config template to pre_startup 2025-01-22 09:49:45 +01:00
0dfcedc47f build: add jjf_client to dependencies 2025-01-15 08:20:59 +01:00
8f2335396b test: fix mock due to refactoring in ophyd devices 2025-01-14 17:53:32 +01:00
8a7a005b56 feat: add JungFrauJoch client draf and test 2025-01-14 16:46:58 +01:00
e19ce733a2 refactor: initial commit for updates on ddg from csaxs 2025-01-14 16:46:58 +01:00
gac-x12sa
ccaf996512 refactor: refactored ddg csaxs with new base class in ophyd devices 2025-01-14 16:46:58 +01:00
Holler Mirko
83de8b75db added show all methods 2025-01-09 14:22:23 +01:00
Holler Mirko
829bdc11c1 adjusted to new gui manager 2025-01-09 14:20:29 +01:00
Holler Mirko
cbb3f9e8bc added example table for temperatures 2025-01-08 13:22:19 +01:00
Holler Mirko
5f819d9835 further fix omny doc 2025-01-08 11:51:02 +01:00
Holler Mirko
8f55b6dbcb Fixes in omny documentation plus added show all for all relevant omny components 2025-01-08 11:46:49 +01:00
b2095f8d7b Merge branch 'main' into 'lamnidoc'
# Conflicts:
#   docs/user/ptychography/omny.md
2024-12-16 17:03:19 +01:00
Mirko Holler
9186853faf lamni doc 2024-12-16 16:50:53 +01:00
4d241d26f3 Update omny.md values of interferometer SSI 2024-12-13 13:19:19 +01:00
f47ca6e174 Update lamni.md 2024-12-02 13:56:07 +01:00
e86e6ba63c Update ptychography.md 2024-12-02 13:56:07 +01:00
7ff2f7dc1b Update omny.md 2024-12-02 13:56:07 +01:00
ef1ab749a9 Update flomni.md 2024-12-02 13:56:07 +01:00
Mirko Holler
ec53bb7000 fixes in flomni doc and completion of first version of omny doc 2024-12-02 13:44:18 +01:00
Mirko Holler
5f1449365d some mods omny doc 2024-11-29 17:28:28 +01:00
Mirko Holler
9699b39a7e continued with omny documentation 2024-11-29 13:20:39 +01:00
4fa81ddc5c fix: fixes related to BEC v3 2024-11-18 13:51:12 +01:00
Mirko Holler
79f63b84fc completed initial version of flomni docs 2024-11-18 13:14:21 +01:00
Holler Mirko
5f9f2c8762 started editing flomni doc 2024-11-15 16:36:44 +01:00
Holler Mirko
58960944f8 init version 2024-11-15 15:37:41 +01:00
Holler Mirko
dfb4fa75d9 initial version 2024-11-15 15:37:04 +01:00
70b196403b docs: cleanup to support rtd autosummary 2024-11-14 17:43:05 +01:00
f14a5f220e docs: cleanup for rtd 2024-11-14 16:37:23 +01:00
a4077be379 build: added bec_widgets as dependency 2024-11-14 16:37:23 +01:00
cf96658625 docs: fixed install path 2024-11-14 16:27:55 +01:00
c21d6cf3c6 docs: install repo for rtd 2024-11-14 16:23:33 +01:00
4f22de66f6 docs: fixed config path 2024-11-14 16:16:32 +01:00
dadee3efc1 docs: fixed install for rtd 2024-11-14 16:10:17 +01:00
Holler Mirko
19ac208f73 config fix ocsx ocsy 2024-11-12 15:42:35 +01:00
a327d0ccaa refactor(gui_tools): cleanup of unused imports 2024-11-12 15:36:17 +01:00
Holler Mirko
9107b1f14b fixed camera init
minor adjustments progress gui
2024-11-12 15:35:45 +01:00
Holler Mirko
bb8bf3a072 req key args 2024-11-12 15:34:40 +01:00
9c331c13e0 refactor(gui_tools): minor cleanup 2024-11-12 15:29:48 +01:00
Holler Mirko
8a1951766b fixes 2024-11-07 12:09:14 +01:00
Holler Mirko
a70c4588a0 added textbox to gui 2024-11-07 11:40:49 +01:00
Holler Mirko
55aa7128e2 work on omny gui, initial status progress bar 2024-11-06 16:56:53 +01:00
b31a1ea996 docs: fixed api reference 2024-11-05 22:22:52 +01:00
820b673144 docs: fixed landing page 2024-11-05 22:15:08 +01:00
805d4b5a13 docs: add readthedocs configuration 2024-11-05 22:08:49 +01:00
ci_update_bot
011955ca38 docs: Update device list 2024-11-05 14:12:11 +00:00
04f123ab2b fix(ids_camera): safe-guarded the import of ids libs 2024-11-05 15:06:35 +01:00
372205957b refactor: formatting 2024-11-05 15:06:35 +01:00
Holler Mirko
41d574253e added cameras plus some fixes 2024-11-05 15:06:35 +01:00
Holler Mirko
c9536d9380 various fixes 2024-11-05 15:06:35 +01:00
831ace2533 fix: added missing pyueye lib 2024-11-05 15:06:35 +01:00
Holler Mirko
20a4f4d0bc fixes. ready for first merge. 2024-11-05 15:06:35 +01:00
Holler Mirko
3ae62c8ff9 fix 2024-11-05 15:06:35 +01:00
Holler Mirko
40c460d120 first scanning via tomo_scan_projection, added mirror corrections 2024-11-05 15:06:35 +01:00
Holler Mirko
0d66d2d364 added tracker check 2024-11-05 15:06:35 +01:00
Holler Mirko
2e3ed9f6f0 cleanup and move mirror parameters to server and transfer to client 2024-11-05 15:06:35 +01:00
Holler Mirko
fff0b8ce41 moved auto alignment methods from client to rt server 2024-11-05 15:06:35 +01:00
Holler Mirko
d7308a8b81 omny fermat scan 2024-11-05 15:06:35 +01:00
Holler Mirko
86e71ca255 first adjustments towards scanning 2024-11-05 15:06:35 +01:00
Holler Mirko
d3c86b07a0 introduce 25 deg offset in osamroy to have 0 to 360
initial work on omny class
2024-11-05 15:06:35 +01:00
Holler Mirko
af2a2df55f rt movement possible. added slew rate limiter querry to rt communication 2024-11-05 15:06:35 +01:00
Holler Mirko
d2e4a8c703 axis not stopping 2024-11-05 15:06:35 +01:00
Holler Mirko
a8cc2bcbe0 interferometer feedback running 2024-11-05 15:06:35 +01:00
Holler Mirko
cb16dcfb4c multiple cameras and fix cam id 2024-11-05 15:06:35 +01:00
Holler Mirko
4282d4ead6 first version ids 2024-11-05 15:06:35 +01:00
Holler Mirko
a07a56822f continued with auto alignment and laser tracker 2024-11-05 15:06:35 +01:00
Holler Mirko
07543755d9 first commissioning of interferometer auto alignment 2024-11-05 15:06:35 +01:00
Holler Mirko
793eedb3f4 continued with mirror tweaking. started with laser tracker. implemented tweak curser function. 2024-11-05 15:06:35 +01:00
Holler Mirko
dbae426a87 started with implementation of RT omny 2024-11-05 15:06:35 +01:00
Holler Mirko
9e84c1570c omny vacuum control system device 2024-11-05 15:06:35 +01:00
Holler Mirko
2002f2fabf added device for dewar monitoring 2024-11-05 15:06:35 +01:00
Holler Mirko
fb49295a92 fixes temperature device 2024-11-05 15:06:35 +01:00
Holler Mirko
85ecf8b55f added show all cryo 2024-11-05 15:06:35 +01:00
Holler Mirko
a1eacdf54b continued devel temperature device 2024-11-05 15:06:35 +01:00
Holler Mirko
c9a1fd1cd6 initial version of temperature device 2024-11-05 15:06:35 +01:00
Holler Mirko
54b9a5004a completed sample transfer commissioning 2024-11-05 15:06:35 +01:00
Holler Mirko
972d010954 gripper move to and from sample stage commissioned 2024-11-05 15:06:35 +01:00
Holler Mirko
b1f4207738 sample transfer put and get at shuttle positions commissioned 2024-11-05 15:06:35 +01:00
Holler Mirko
45dfc1305e further sample transfer. first gripper movements 2024-11-05 15:06:35 +01:00
Holler Mirko
5f90b32210 started commissioning of sample transfer. shuttle align, open and close 2024-11-05 15:06:35 +01:00
Holler Mirko
29a8e1b281 optics movements 2024-11-05 15:06:35 +01:00
Holler Mirko
78b838f516 first init of omny stages. adding color printing, moving methods to tools and further auto init 2024-11-05 15:06:35 +01:00
Holler Mirko
17d4fcb8db messages to client 2024-11-05 15:06:35 +01:00
Holler Mirko
e03b3b84e0 started commissioning with hardware. first movements. added smaract to config 2024-11-05 15:06:35 +01:00
Holler Mirko
dd2f20fba4 interferometer tuning added to rt client 2024-11-05 15:06:35 +01:00
Holler Mirko
d62f398351 started tweak cursor for laser mirrors 2024-11-05 15:06:35 +01:00
Holler Mirko
c17a836858 continued rt ophyd. requires testing now. rt client will contain interferometer mirror tweaking 2024-11-05 15:06:35 +01:00
Holler Mirko
f359e32610 continue rt and laser 2024-11-05 15:06:35 +01:00
Holler Mirko
b28649e6af started with rt 2024-11-05 15:06:35 +01:00
Holler Mirko
a0f2269da3 started with rt 2024-11-05 15:06:35 +01:00
Holler Mirko
143c4dbd65 first test of transfer functions (setting and getting samples, without hw, just epics level) 2024-11-05 15:06:35 +01:00
Holler Mirko
347c63d500 fixes after first load in bec 2024-11-05 15:06:35 +01:00
Holler Mirko
f0df487131 added new files for alignment, tools and laser 2024-11-05 15:06:35 +01:00
Holler Mirko
90449d22ee initial draft of sample transfer completed 2024-11-05 15:06:35 +01:00
Holler Mirko
3a06ca484b continue with sample transfer 2024-11-05 15:06:35 +01:00
Holler Mirko
8ba32172ca continued sample transfer 2024-11-05 15:06:35 +01:00
Holler Mirko
9652545440 continue sample transfer 2024-11-05 15:06:35 +01:00
Holler Mirko
55f7015834 continued with sample transfer 2024-11-05 15:06:35 +01:00
Holler Mirko
afd5d5548f continued with optics alignment and started with sample transfer 2024-11-05 15:06:35 +01:00
Holler Mirko
cc04810361 continued 2024-11-05 15:06:35 +01:00
Holler Mirko
fb6ec1c350 continue omny init and started optics alignment 2024-11-05 15:06:35 +01:00
Holler Mirko
d32d7401a3 continue with init 2024-11-05 15:06:35 +01:00
Holler Mirko
c7f9d37c12 continue init script of omny, various mods in galil 2024-11-05 15:06:35 +01:00
Holler Mirko
f3d2100b03 starting omny client 2024-11-05 15:06:35 +01:00
Holler Mirko
79d69c9492 omny galil 2024-11-05 15:06:35 +01:00
Holler Mirko
e4a2d01488 omny galil 2024-11-05 15:06:35 +01:00
5648b4dbd6 fix: added find_reference and folerr public methods 2024-11-05 15:06:35 +01:00
Holler Mirko
977a2beb4c omny galil 2024-11-05 15:06:35 +01:00
Holler Mirko
8f9c43ad99 omny galil 2024-11-05 15:06:35 +01:00
Holler Mirko
8bb89cbf76 omny galil 2024-11-05 15:06:35 +01:00
Holler Mirko
294d3b0d4e omny galil first commit 2024-11-05 15:06:35 +01:00
87f002a68b added license 2024-11-04 21:13:02 +01:00
ci_update_bot
b5a5082919 docs: Update device list 2024-10-03 13:42:26 +00:00
3a40b4a37e refactor(npoint): minor improvements to the npoint config template 2024-10-02 18:31:53 +02:00
baafa982e3 refactor(npoint): cleanup 2024-10-02 18:27:02 +02:00
8c2d705a89 docs: improved npoint test docs 2024-10-02 18:21:11 +02:00
59db1c067b test: fixed npoint tests after axis refactoring 2024-10-02 18:11:51 +02:00
gac-x12sa
15bdbe2e03 feat: added npoint support to ophyd 2024-10-02 11:50:39 +02:00
0d2b4c4423 refactor(npoint): cleanup 2024-09-18 21:49:49 +02:00
59e0755e14 test: fixed test for unique rids 2024-06-25 10:06:26 +02:00
ci_update_bot
2e6d1ad343 docs: Update device list 2024-06-23 09:04:52 +00:00
fdfb6db84b refactor: publish file location with successful and done 2024-06-06 15:43:32 +02:00
4e4ca325ab fix: use on_complete instead of on_unstage and adapt changes for base class 2024-06-04 09:02:06 +02:00
ci_update_bot
319771e2aa docs: Update device list 2024-05-27 10:48:14 +00:00
26b8ac997d refactor: cleanup imports, tests and classes 2024-05-27 08:49:53 +02:00
3f21090441 refactor: moved patch_dual_pvs to devices.tests_utils 2024-05-27 08:34:11 +02:00
34fdc39ebc refactor: moved patch pvs to conftest.py 2024-05-27 08:34:11 +02:00
f3d7f1ba64 feat: adapt detector classes and test for psi_detector_base refactoring 2024-05-27 08:34:11 +02:00
ci_update_bot
fad8b516a6 docs: Update device list 2024-05-23 12:28:05 +00:00
a9ff092a66 fix: commented outdated epics devices 2024-05-23 14:26:05 +02:00
37d80e9a7b fix: added backward compatible fix 2024-05-22 11:52:32 +02:00
ed335dc308 fix: renamed move_and_wait 2024-05-22 11:46:02 +02:00
78d2dd2436 fix: fixed scan_progress after renaming in bec 2024-05-21 12:51:05 +02:00
ci_update_bot
bf2e6baba5 docs: Update device list 2024-05-17 15:45:17 +00:00
fcf9ee4545 refactor: restructured device module 2024-05-17 17:40:26 +02:00
ci_update_bot
53a200928f docs: Update device list 2024-05-16 17:57:58 +00:00
ci_update_bot
1572dd4484 docs: Update device list 2024-05-16 17:55:57 +00:00
ci_update_bot
f72be7ab70 docs: Update device list 2024-05-16 17:53:55 +00:00
ci_update_bot
8aee3e3c0d docs: Update device list 2024-05-16 17:51:51 +00:00
ci_update_bot
6a517d3ac5 docs: Update device list 2024-05-16 17:49:48 +00:00
ci_update_bot
b6b2850853 docs: Update device list 2024-05-16 17:47:44 +00:00
ci_update_bot
5841a56255 docs: Update device list 2024-05-16 17:45:47 +00:00
ci_update_bot
333b4f1dd2 docs: Update device list 2024-05-16 17:43:45 +00:00
ci_update_bot
97e09cf622 docs: Update device list 2024-05-16 17:41:44 +00:00
ci_update_bot
174c0689bc docs: Update device list 2024-05-16 15:39:19 +00:00
ci_update_bot
a3899bf7a5 docs: Update device list 2024-05-16 15:35:03 +00:00
ci_update_bot
d4c12a3c9c docs: Update device list 2024-05-16 15:33:00 +00:00
ci_update_bot
8f0ed4e250 docs: Update device list 2024-05-16 15:29:28 +00:00
ci_update_bot
0359f1f431 docs: Update device list 2024-05-16 15:27:23 +00:00
ci_update_bot
b45070332d docs: Update device list 2024-05-16 15:25:21 +00:00
ci_update_bot
35e4ea0916 docs: Update device list 2024-05-16 15:23:14 +00:00
ae86fbd329 fix: fixed lgalil init 2024-05-16 16:15:07 +02:00
a195be64e7 test: support for repeated runs 2024-05-16 15:38:41 +02:00
e980bf2ee6 fix: fixed import 2024-05-16 15:07:53 +02:00
ce876f58d6 fix: fixed config after refactoring 2024-05-16 15:07:53 +02:00
Holler Mirko
b0a1c32b47 conig_files 2024-05-16 15:07:53 +02:00
11ed6e112f test: ensure to reset the controller instances 2024-05-16 15:07:53 +02:00
8a24692e82 fix: fixed import after refactoring 2024-05-16 15:07:53 +02:00
1d266c5da9 refactor: added galil base class and moved lamni-specific galil components to subclass 2024-05-16 15:07:53 +02:00
7d93154761 refactor: renamed rt_lamni module to rt 2024-05-16 15:07:53 +02:00
c85c6ec5ab refactor: cleanup for rt 2024-05-16 15:07:53 +02:00
Holler Mirko
d567e49b53 fixes at beamline 2024-05-16 15:07:53 +02:00
73ce61dd96 refactor: merged controller methods 2024-05-16 15:07:53 +02:00
8410b3ceec fix: disabled detector_table_theta class 2024-05-16 13:19:37 +02:00
477567e61a fix: fixed bec_lib imports 2024-05-15 18:41:17 +02:00
7da1bddef8 fix: apply black and isort 2024-05-15 14:47:48 +02:00
5007b182ca fix: change imports according to latest bec_lib 2024-05-15 14:47:48 +02:00
063308042d ci: added child pipeline branch 2024-05-13 16:10:21 +02:00
0180e4a3f9 fix: update to new ophyd_devices structure 2024-05-08 17:37:56 +02:00
fc37892ea1 fix: fixed import for new ophyd_devices repo structure 2024-05-08 16:59:04 +02:00
Holler Mirko
d6f88b599a improve show all 2024-05-07 15:24:55 +02:00
Holler Mirko
ab2270683f feature_lamni_init 2024-05-07 15:21:06 +02:00
Holler Mirko
7b6652f867 feature_lamni_init 2024-05-07 15:21:06 +02:00
Holler Mirko
cb525e8778 feature: initialization of lamni stages 2024-05-07 15:21:06 +02:00
21c3e3cab4 Merge branch 'wakonig_k-main-patch-ef88' into 'main'
moved gitlab ci to awi-utils

See merge request bec/csaxs_bec!16
2024-05-07 09:30:43 +02:00
adfdb6fe5d ci: moved to awi utils template 2024-05-07 09:27:55 +02:00
73d9a967d6 Merge branch 'test/async_readback_fix' into 'main'
test: fixed async readback dummy data

See merge request bec/csaxs_bec!17
2024-05-07 09:25:40 +02:00
787e0afcec test: fixed async readback dummy data 2024-05-07 09:23:04 +02:00
74d941c249 Merge branch 'backlashcommandfix' into 'main'
backlashcommandfix

See merge request bec/csaxs_bec!13
2024-04-26 10:32:17 +02:00
f601593c77 test(galil): fixed wrong backlash assert 2024-04-26 10:29:57 +02:00
Holler Mirko
e0d93cd84e backlashcommandfix 2024-04-26 10:00:44 +02:00
803dffb9cd Merge branch 'fix' into 'main'
fix

See merge request bec/csaxs_bec!11
2024-04-25 23:32:09 +02:00
Holler Mirko
3cbafa51be fix 2024-04-25 15:40:53 +02:00
de10eb95e1 Merge branch 'fix/file_writer' into 'main'
fix: added missing nexus layout

See merge request bec/csaxs_bec!10
2024-04-23 19:07:11 +02:00
bb7786c808 fix: added missing nexus layout 2024-04-23 18:24:34 +02:00
cdd47e8dbb Merge branch 'build/fixed_plugin_import' into 'main'
fix: fixed CA startup for device server

See merge request bec/csaxs_bec!9
2024-04-22 19:34:10 +02:00
97224e37cf fix: fixed CA startup for device server 2024-04-22 19:29:28 +02:00
124878a064 Merge branch 'build/fixed_plugin_import' into 'main'
build: fixed plugin import

See merge request bec/csaxs_bec!8
2024-04-22 19:19:50 +02:00
1a4e992d2f fix: fixed startup and entry points 2024-04-22 19:06:43 +02:00
0a59439ecc build: fixed plugin import 2024-04-22 18:44:22 +02:00
140ceb7359 Merge branch 'feat/transition_to_new_plugin_structure' into 'main'
feat: removed old and introduced new structure

See merge request bec/csaxs-bec!7
2024-04-19 17:56:43 +02:00
4326c5b4a0 ci: add ci file 2024-04-19 17:48:35 +02:00
daf1ec0317 refactor: updated configs, minor improvements, formatting 2024-04-19 17:48:35 +02:00
c109abce12 feat: populated csaxs_bec from ophyd and bec 2024-04-19 17:48:30 +02:00
96921da618 feat: removed old and introduced new structure 2024-04-18 09:37:58 +02:00
058be66aaa Merge branch 'doc-pmodule-update' into 'master'
doc: psi-python311/2024.02 is now stable

See merge request bec/csaxs-bec!6
2024-03-13 21:29:46 +01:00
b2786dca83 doc: psi-python311/2024.02 is now stable 2024-03-12 13:32:35 +01:00
244 changed files with 38509 additions and 12682 deletions

9
.copier-answers.yml Normal file
View File

@@ -0,0 +1,9 @@
# Do not edit this file!
# It is needed to track the repo template version, and editing may break things.
# This file will be overwritten by copier on template updates.
_commit: v1.2.2
_src_path: https://github.com/bec-project/plugin_copier_template.git
make_commit: false
project_name: csaxs_bec
widget_plugins_input: []

3
.git_hooks/post-commit Executable file
View File

@@ -0,0 +1,3 @@
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
semantic-release changelog -D version_variable=$SCRIPT_DIR/../../semantic_release/__init__.py:__version__
semantic-release version -D version_variable=$SCRIPT_DIR/../../semantic_release/__init__.py:__version__

View File

@@ -1,2 +1,3 @@
black --line-length=100 $(git diff --cached --name-only --diff-filter=ACM)
git add $(git diff --cached --name-only --diff-filter=ACM)
black --line-length=100 $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
isort --line-length=100 --profile=black --multi-line=3 --trailing-comma $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
git add $(git diff --cached --name-only --diff-filter=ACM -- '*.py')

85
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,85 @@
name: CI for csaxs_bec
on:
push:
pull_request:
workflow_dispatch:
inputs:
BEC_WIDGETS_BRANCH:
description: "Branch of BEC Widgets to install"
required: false
type: string
default: "main"
BEC_CORE_BRANCH:
description: "Branch of BEC Core to install"
required: false
type: string
default: "main"
OPHYD_DEVICES_BRANCH:
description: "Branch of Ophyd Devices to install"
required: false
type: string
default: "main"
BEC_PLUGIN_REPO_BRANCH:
description: "Branch of the BEC Plugin Repository to install"
required: false
type: string
default: "main"
PYTHON_VERSION:
description: "Python version to use"
required: false
type: string
default: "3.11"
permissions:
pull-requests: write
jobs:
test:
runs-on: ubuntu-latest
env:
QTWEBENGINE_DISABLE_SANDBOX: 1
QT_QPA_PLATFORM: "offscreen"
steps:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "${{ inputs.PYTHON_VERSION || '3.11' }}"
- name: Checkout BEC Core
run: git clone --depth 1 --branch "${{ inputs.BEC_CORE_BRANCH || 'main' }}" https://github.com/bec-project/bec.git ./bec
- name: Checkout Ophyd Devices
run: git clone --depth 1 --branch "${{ inputs.OPHYD_DEVICES_BRANCH || 'main' }}" https://github.com/bec-project/ophyd_devices.git ./ophyd_devices
- name: Checkout BEC Widgets
run: git clone --depth 1 --branch "${{ inputs.BEC_WIDGETS_BRANCH || 'main' }}" https://github.com/bec-project/bec_widgets.git ./bec_widgets
- name: Checkout BEC Plugin Repository
uses: actions/checkout@v4
with:
repository: bec/csaxs_bec
ref: "${{ inputs.BEC_PLUGIN_REPO_BRANCH || github.head_ref || github.sha }}"
path: ./csaxs_bec
- name: Install dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y libgl1 libegl1 x11-utils libxkbcommon-x11-0 libdbus-1-3 xvfb
sudo apt-get -y install libnss3 libxdamage1 libasound2t64 libatomic1 libxcursor1
- name: Install Python dependencies
shell: bash
run: |
pip install uv
uv pip install --system -e ./ophyd_devices
uv pip install --system -e ./bec/bec_lib[dev]
uv pip install --system -e ./bec/bec_ipython_client
uv pip install --system -e ./bec/bec_server[dev]
uv pip install --system -e ./bec_widgets[dev,pyside6]
uv pip install --system -e ./csaxs_bec
- name: Run Pytest with Coverage
id: coverage
run: pytest --random-order --cov=./csaxs_bec --cov-config=./csaxs_bec/pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./csaxs_bec/tests/ || test $? -eq 5

3
.gitignore vendored
View File

@@ -8,6 +8,9 @@
**/.pytest_cache
**/*.egg*
# recovery_config files
recovery_config_*
# file writer data
**.h5

20
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,20 @@
include:
- project: bec/awi_utils
file: /templates/plugin-repo-template.yml
inputs:
name: "csaxs"
target: "csaxs_bec"
branch: $CHILD_PIPELINE_BRANCH
pages:
stage: Deploy
needs: []
variables:
TARGET_BRANCH: $CI_COMMIT_REF_NAME
rules:
- if: "$CI_COMMIT_TAG != null"
variables:
TARGET_BRANCH: $CI_COMMIT_TAG
- if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "bec/csaxs_bec"'
script:
- curl -X POST -d "branches=$CI_COMMIT_REF_NAME" -d "token=$RTD_TOKEN" https://readthedocs.org/api/v2/webhook/sls-csaxs/270162/

29
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,29 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.10"
jobs:
pre_install:
- pip install .
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
# formats:
# - pdf
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt

29
LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2025, Paul Scherrer Institute
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -6,15 +6,12 @@ You might want to run cSAXS copy scripts before in case you want to have the for
## Overview
1. Clone cSAXS BEC repository into e-account (e.g. into ~/Data10/software/.)
2. Install BEC
3. Start Epics iocs
4. Start BEC, BEC server and load/modify the device config with relevant hardware
5. BEC commands
6. Start BEC widgets (GUI for motor control, eiger live plot)
7. Troubleshooting and common problems
- Clone cSAXS BEC repository into e-account (e.g. into ~/Data10/software/.)
- Start Epics iocs
- Start BEC, BEC server and load/modify the device config with relevant hardware
- BEC commands
## 1. Clone cSAXS BEC repository
## Clone cSAXS BEC repository
Clone the current cSAXS BEC repository from GIT into the new e-account.
Create directory
@@ -24,19 +21,9 @@ cd ~/Data10/software
```
Clone repository
```bash
git clone https://gitlab.psi.ch/bec/csaxs-bec.git
git clone https://gitlab.psi.ch/bec/csaxs_bec.git
```
## 2. Install BEC
There is a bash sript in the followin directory.
Go to the directory and run the script on pc15543 logged in as the e-account (BEC server):
```bash
ssh pc15543
cd ~/Data10/software/csaxs-bec/bin/
./setup_bec.sh
```
## 3. Start epics iocs
## Start epics iocs
You can start up the iocs while the *./setup_bec.sh* script is running. Be aware though that the scripts requires you to interact with it.
@@ -94,7 +81,7 @@ iocsh -7.0.6 startup.script
```
Be aware -7.0.6 is referring to the current epics version and might change in future (SLS 2.0)
## 4. Start BEC, BEC server and load device config
## Start BEC, BEC server and load device config
Step 1 needs to have finished for continuing with these steps.
What remains now is to start the bec server. Connect to pc15543 and open a new terminal to run:
@@ -127,7 +114,7 @@ bec.config.save_current_session('~/Data10/software/current_config.yaml')
```
The second command is helpful if you adjust limits of motors, which will then be stored in the config and loaded if a reload of the configuration is needed.
## 5. BEC commands
## BEC commands
A number of commands that are useful:
@@ -147,41 +134,3 @@ scans.line_scan(dev.samx, -1, 1, dev.samy, -1, 1, steps=20, exp_time=0.5, readou
scans.sgalil_grid(start_y = , end_y = , interval_y = , start_x=, end_x=, interval_x =, exp_time=0.5, readout_time=3e-3, relative=True)
```
## 6. Start BEC widgets (GUI for motor control, eiger live plot)
To start the BEC widgets, the first step is to make the bec_widgets_venv using the start startup script.
Follow the commands below:
``` bash
cd ~/Data10/software/csaxs-bec/bin
./setup_bec_widgets.sh
```
Afterwards, activate the environment on either cons-01 comp-1/2
``` bash
cd ~/Data10/software/
source activate bec_widgets_venv/bin/activate
```
Each Plot needs their own shell with activate environment
1. Eiger Plot
``` bash
cd ~/Data10/software/bec-widgets/bec_widgets/examples/eiger_plot
python eiger_plot.py
```
2. Motor Controller
``` bash
cd ~/Data10/software/bec-widgets/bec_widgets/examples/motor_movement
python motor_example.py --config csaxs_config.yaml
```
## 7. Troubleshooting and common problems
Sometimes the data backend for the Eiger gets stuck or misses frames, this will result in an error
``` python
raise EigerTimeoutError(
ophyd_devices.epics.devices.eiger9m_csaxs.EigerTimeoutError: Reached timeout with detector state 1, std_daq state FINISHED and received frames of 100 for the file writer)
```
This happens more likely after CTRL C of a scan. To recover from this more reliably, perform the an acquisition in burst mode with 100 frames, little exposure until no error message is raised after. This can be up to 3 times from former experience.
``` bash
scans.acquire(exp_time=0.02, frames_per_trigger=100, readout_time= 3e-3)
```
Afterwards, you should be good to continue with 2D gridscans.

View File

@@ -1 +0,0 @@
from .bec_client import *

View File

@@ -1 +0,0 @@
from .plugins import *

View File

@@ -1,245 +0,0 @@
from bec_lib.devicemanager import Device
from bec_lib.scan_report import ScanReport
# pylint:disable=undefined-variable
# pylint: disable=too-many-arguments
def dscan(
motor1: Device, m1_from: float, m1_to: float, steps: int, exp_time: float, **kwargs
) -> ScanReport:
"""Relative line scan with one device.
Args:
motor1 (Device): Device that should be scanned.
m1_from (float): Start position relative to the current position.
m1_to (float): End position relative to the current position.
steps (int): Number of steps.
exp_time (float): Exposure time.
Returns:
ScanReport: Status object.
Examples:
>>> dscan(dev.motor1, -5, 5, 10, 0.1)
"""
return scans.line_scan(
motor1, m1_from, m1_to, steps=steps, exp_time=exp_time, relative=True, **kwargs
)
def d2scan(
motor1: Device,
m1_from: float,
m1_to: float,
motor2: Device,
m2_from: float,
m2_to: float,
steps: int,
exp_time: float,
**kwargs
) -> ScanReport:
"""Relative line scan with two devices.
Args:
motor1 (Device): First device that should be scanned.
m1_from (float): Start position of the first device relative to its current position.
m1_to (float): End position of the first device relative to its current position.
motor2 (Device): Second device that should be scanned.
m2_from (float): Start position of the second device relative to its current position.
m2_to (float): End position of the second device relative to its current position.
steps (int): Number of steps.
exp_time (float): Exposure time
Returns:
ScanReport: Status object.
Examples:
>>> d2scan(dev.motor1, -5, 5, dev.motor2, -8, 8, 10, 0.1)
"""
return scans.line_scan(
motor1,
m1_from,
m1_to,
motor2,
m2_from,
m2_to,
steps=steps,
exp_time=exp_time,
relative=True,
**kwargs
)
def ascan(motor1, m1_from, m1_to, steps, exp_time, **kwargs):
"""Absolute line scan with one device.
Args:
motor1 (Device): Device that should be scanned.
m1_from (float): Start position.
m1_to (float): End position.
steps (int): Number of steps.
exp_time (float): Exposure time.
Returns:
ScanReport: Status object.
Examples:
>>> ascan(dev.motor1, -5, 5, 10, 0.1)
"""
return scans.line_scan(
motor1, m1_from, m1_to, steps=steps, exp_time=exp_time, relative=False, **kwargs
)
def a2scan(motor1, m1_from, m1_to, motor2, m2_from, m2_to, steps, exp_time, **kwargs):
"""Absolute line scan with two devices.
Args:
motor1 (Device): First device that should be scanned.
m1_from (float): Start position of the first device.
m1_to (float): End position of the first device.
motor2 (Device): Second device that should be scanned.
m2_from (float): Start position of the second device.
m2_to (float): End position of the second device.
steps (int): Number of steps.
exp_time (float): Exposure time
Returns:
ScanReport: Status object.
Examples:
>>> a2scan(dev.motor1, -5, 5, dev.motor2, -8, 8, 10, 0.1)
"""
return scans.line_scan(
motor1,
m1_from,
m1_to,
motor2,
m2_from,
m2_to,
steps=steps,
exp_time=exp_time,
relative=False,
**kwargs
)
def dmesh(motor1, m1_from, m1_to, m1_steps, motor2, m2_from, m2_to, m2_steps, exp_time, **kwargs):
"""Relative mesh scan (grid scan) with two devices.
Args:
motor1 (Device): First device that should be scanned.
m1_from (float): Start position of the first device relative to its current position.
m1_to (float): End position of the first device relative to its current position.
m1_steps (int): Number of steps for motor1.
motor2 (Device): Second device that should be scanned.
m2_from (float): Start position of the second device relative to its current position.
m2_to (float): End position of the second device relative to its current position.
m2_steps (int): Number of steps for motor2.
exp_time (float): Exposure time
Returns:
ScanReport: Status object.
Examples:
>>> dmesh(dev.motor1, -5, 5, 10, dev.motor2, -8, 8, 10, 0.1)
"""
return scans.grid_scan(
motor1,
m1_from,
m1_to,
m1_steps,
motor2,
m2_from,
m2_to,
m2_steps,
exp_time=exp_time,
relative=True,
)
def amesh(motor1, m1_from, m1_to, m1_steps, motor2, m2_from, m2_to, m2_steps, exp_time, **kwargs):
"""Absolute mesh scan (grid scan) with two devices.
Args:
motor1 (Device): First device that should be scanned.
m1_from (float): Start position of the first device.
m1_to (float): End position of the first device.
m1_steps (int): Number of steps for motor1.
motor2 (Device): Second device that should be scanned.
m2_from (float): Start position of the second device.
m2_to (float): End position of the second device.
m2_steps (int): Number of steps for motor2.
exp_time (float): Exposure time
Returns:
ScanReport: Status object.
Examples:
>>> amesh(dev.motor1, -5, 5, 10, dev.motor2, -8, 8, 10, 0.1)
"""
return scans.grid_scan(
motor1,
m1_from,
m1_to,
m1_steps,
motor2,
m2_from,
m2_to,
m2_steps,
exp_time=exp_time,
relative=False,
)
def umv(*args) -> ScanReport:
"""Updated absolute move (i.e. blocking) for one or more devices.
Returns:
ScanReport: Status object.
Examples:
>>> umv(dev.samx, 1)
>>> umv(dev.samx, 1, dev.samy, 2)
"""
return scans.umv(*args, relative=False)
def umvr(*args) -> ScanReport:
"""Updated relative move (i.e. blocking) for one or more devices.
Returns:
ScanReport: Status object.
Examples:
>>> umvr(dev.samx, 1)
>>> umvr(dev.samx, 1, dev.samy, 2)
"""
return scans.umv(*args, relative=True)
def mv(*args) -> ScanReport:
"""Absolute move for one or more devices.
Returns:
ScanReport: Status object.
Examples:
>>> mv(dev.samx, 1)
>>> mv(dev.samx, 1, dev.samy, 2)
"""
return scans.mv(*args, relative=False)
def mvr(*args) -> ScanReport:
"""Relative move for one or more devices.
Returns:
ScanReport: Status object.
Examples:
>>> mvr(dev.samx, 1)
>>> mvr(dev.samx, 1, dev.samy, 2)
"""
return scans.mv(*args, relative=True)

View File

@@ -1,2 +0,0 @@
from .load_additional_correction import lamni_read_additional_correction
from .x_ray_eye_align import LamNI, XrayEyeAlign, MagLamNI, DataDrivenLamNI

View File

@@ -1,269 +0,0 @@
def bl_show_all '{
local gap
printf("beamline status at %s:\n",date())
if (!_bl_hall_temperature_ok()) {
bl_hall_temperature
printf("\n")
}
if (_bl_sls_status_unusual()) {
bl_sls_status
} else {
bl_ring_current
}
bl_chk_beam _show
printf("\n")
printf("U19 ID gap : ",gap)
gap = _id_get_gap_mm()
if (gap >= 8) {
text_bf
}
printf("%.3f mm\n",gap)
text_non_bf
if (!_id_loss_rate_ok()) {
id_loss_rate
}
bl_shutter_status
if (_bl_cvd_filter_open()) {
text_bf
printf("CVD diamond filter : open / out\n")
text_non_bf
}
if (!_bl_xbox_valve_es1_open()) {
bl_xbox_valve_es1 _show
}
if (_bl_ln2_non_standard()) {
text_bf
printf("\nNon standard liquid nitrogen cooling-warning parameters occur. Please report this to your local contact.\n")
text_non_bf
printf("The macro bl_ln2_warn can be used to control this e-mail warning feature.\n")
bl_ln2_warn "show"
printf("\n")
}
printf("\n")
bl_flight_tube_pressure
printf("\n")
bl_attended _show
_bl_check_alarm_records(1,1)
printf("\n")
bl_op_msg
}'
def _bl_hall_temperature_ok() '{
local temp_ok
local stat
temp_ok = 1
# EH T02 average temperature
stat = epics_get("ILUUL-02AV:TEMP")
if ((stat < 23.0) || (stat > 26.0)) {
temp_ok = 0
}
# EH T02 temperature at T0204 axis 16
stat = epics_get("ILUUL-0200-EB104:TEMP")
if ((stat < 23.0) || (stat > 26.0)) {
temp_ok = 0
}
# EH T02 temperature at T0205 axis 18
stat = epics_get("ILUUL-0200-EB105:TEMP")
if ((stat < 23.0) || (stat > 26.0)) {
temp_ok = 0
}
return (temp_ok)
}'
# ----------------------------------------------------------------------
def bl_hall_temperature '{
local stat
stat = epics_get("ILUUL-02AV:TEMP")
printf("hall T02 average temperature : ")
if ((stat < 23.0) || (stat > 25.0)) {
text_bf
}
printf("%.2f deg.C\n",stat)
text_non_bf
stat = epics_get("ILUUL-0200-EB104:TEMP")
printf("hall temperature at T0204 axis 16 : ")
if ((stat < 23) || (stat > 25)) {
text_bf
}
printf("%.2f deg.C\n",stat)
text_non_bf
stat = epics_get("ILUUL-0200-EB105:TEMP")
printf("hall temperature at T0205 axis 18 : ")
if ((stat < 23) || (stat > 25)) {
text_bf
}
printf("%.2f deg.C\n",stat)
text_non_bf
# stat = epics_get("ILUUL-0300-EB102:TEMP")
# printf("EH T03 temperature at T0302 axis 21: ")
# if ((stat < 23) || (stat > 25)) {
# text_bf
# }
# printf("%.2f deg.C\n",stat)
# text_non_bf
}'
def _bl_sls_status_unusual() '{
local unusual
local stat
unusual = 0
stat = epics_get("X12SA-SR-VAC:SETPOINT")
if (stat != "OK") {
unusual = 1
}
stat = epics_get("ACOAU-ACCU:OP-MODE.VAL")
if ((stat != "Light Available") && (stat != "Light-Available")) {
unusual = 1
}
stat = epics_get("ALIRF-GUN:INJ-MODE")
if (stat != "TOP-UP") {
unusual = 1
}
# current threshold
stat = epics_get("ALIRF-GUN:CUR-LOWLIM")
if (stat < 350) {
unusual = 1
}
# current deadband
stat = epics_get("ALIRF-GUN:CUR-DBAND")
if (stat > 2) {
unusual = 1
}
# orbit feedback mode
stat = epics_get("ARIDI-BPM:OFB-MODE")
if (stat != "fast") {
unusual = 1
}
# fast orbit feedback
stat = epics_get("ARIDI-BPM:FOFBSTATUS-G")
if (stat != "running") {
unusual = 1
}
return(unusual)
}'
def bl_sls_status '{
local stat
stat = epics_get("ACOAU-ACCU:OP-MODE.VAL")
printf("SLS status : ")
if ((stat != "Light Available") && (stat != "Light-Available")) {
text_bf
}
printf("%s\n",stat)
text_non_bf
stat = epics_get("ALIRF-GUN:INJ-MODE")
printf("SLS injection mode : ")
if (stat != "TOP-UP") {
text_bf
}
printf("%s\n",stat)
text_non_bf
stat = epics_get("ALIRF-GUN:CUR-LOWLIM")
printf("SLS current threshold : ")
if (stat < 350) {
text_bf
}
printf("%7.3f\n",stat)
text_non_bf
stat = epics_get("ALIRF-GUN:CUR-DBAND")
printf("SLS current deadband : ")
if (stat > 2) {
text_bf
}
printf("%7.3f\n",stat)
text_non_bf
stat = epics_get("ACORF-FILL:PAT-SELECT")
printf("SLS filling pattern : ")
printf("%s\n",stat)
bl_ring_current
stat = epics_get("ARIDI-PCT:TAU-HOUR")
printf("SLS filling life time : ")
printf("%.2f h\n",stat)
stat = epics_get("ARIDI-BPM:OFB-MODE")
printf("orbit feedback mode : ")
if (stat != "fast") {
text_bf
}
printf("%s\n",stat)
text_non_bf
stat = epics_get("ARIDI-BPM:FOFBSTATUS-G")
printf("fast orbit feedback : ")
if (stat != "running") {
text_bf
}
printf("%s\n",stat)
text_non_bf
}'
def _bl_get_ring_current() '{
return epics_get("ARIDI-PCT:CURRENT")
}'
# ----------------------------------------------------------------------
def _bl_no_ring_current() '{
# set an arbitrary current limit of 100mA as no-beam limit
if (_bl_get_ring_current() < 100) {
return 1
} else {
return 0
}
}'
# ----------------------------------------------------------------------
def bl_ring_current '{
local curr
curr = _bl_get_ring_current()
if (curr < 300) {
text_bf
}
printf("SLS ring current : %.3f mA\n",curr)
text_non_bf
}'

View File

@@ -1,161 +0,0 @@
import builtins
import time
from rich import box
from rich.console import Console
from rich.table import Table
from bec_client.plugins.cSAXS import epics_get, epics_put, fshclose
# import builtins to avoid linter errors
dev = builtins.__dict__.get("dev")
umv = builtins.__dict__.get("umv")
bec = builtins.__dict__.get("bec")
class LamNIOpticsMixin:
@staticmethod
def _get_user_param_safe(device, var):
param = dev[device].user_parameter
if not param or param.get(var) is None:
raise ValueError(f"Device {device} has no user parameter definition for {var}.")
return param.get(var)
def leye_out(self):
self.loptics_in()
fshclose()
leyey_out = self._get_user_param_safe("leyey", "out")
umv(dev.leyey, leyey_out)
epics_put("XOMNYI-XEYE-ACQ:0", 2)
# move rotation stage to zero to avoid problems with wires
umv(dev.lsamrot, 0)
umv(dev.dttrz, 5854, dev.fttrz, 2395)
def leye_in(self):
bec.queue.next_dataset_number += 1
# move rotation stage to zero to avoid problems with wires
umv(dev.lsamrot, 0)
umv(dev.dttrz, 6419.677, dev.fttrz, 2959.979)
while True:
moved_out = (input("Did the flight tube move out? (Y/n)") or "y").lower()
if moved_out == "y":
break
if moved_out == "n":
return
leyex_in = self._get_user_param_safe("leyex", "in")
leyey_in = self._get_user_param_safe("leyey", "in")
umv(dev.leyex, leyex_in, dev.leyey, leyey_in)
self.align.update_frame()
def _lfzp_in(self):
loptx_in = self._get_user_param_safe("loptx", "in")
lopty_in = self._get_user_param_safe("lopty", "in")
umv(
dev.loptx, loptx_in, dev.lopty, lopty_in
) # for 7.2567 keV and 150 mu, 60 nm fzp, loptz 83.6000 for propagation 1.4 mm
def lfzp_in(self):
"""
move in the lamni zone plate.
This will disable rt feedback, move the FZP and re-enabled the feedback.
"""
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_disable()
self._lfzp_in()
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_enable_with_reset()
def loptics_in(self):
"""
Move in the lamni optics, including the FZP and the OSA.
"""
self.lfzp_in()
self.losa_in()
def loptics_out(self):
"""Move out the lamni optics"""
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_disable()
# self.lcs_out()
self.losa_out()
loptx_out = self._get_user_param_safe("loptx", "out")
lopty_out = self._get_user_param_safe("lopty", "out")
umv(dev.loptx, loptx_out, dev.lopty, lopty_out)
if "rtx" in dev and dev.rtx.enabled:
time.sleep(1)
dev.rtx.controller.feedback_enable_with_reset()
def lcs_in(self):
# umv lcsx -1.852 lcsy -0.095
pass
def lcs_out(self):
umv(dev.lcsy, 3)
def losa_in(self):
# 6.2 keV, 170 um FZP
# umv(dev.losax, -1.4450000, dev.losay, -0.1800)
# umv(dev.losaz, -1)
# 6.7, 170
# umv(dev.losax, -1.4850, dev.losay, -0.1930)
# umv(dev.losaz, 1.0000)
# 7.2, 150
losax_in = self._get_user_param_safe("losax", "in")
losay_in = self._get_user_param_safe("losay", "in")
losaz_in = self._get_user_param_safe("losaz", "in")
umv(dev.losax, losax_in, dev.losay, losay_in)
umv(dev.losaz, losaz_in)
# 11 kev
# umv(dev.losax, -1.161000, dev.losay, -0.196)
# umv(dev.losaz, 1.0000)
def losa_out(self):
losay_out = self._get_user_param_safe("losay", "out")
losaz_out = self._get_user_param_safe("losaz", "out")
umv(dev.losaz, losaz_out)
umv(dev.losay, losay_out)
def lfzp_info(self):
loptz_val = dev.loptz.read()["loptz"]["value"]
distance = -loptz_val + 85.6 + 52
print(f"The sample is in a distance of {distance:.1f} mm from the FZP.")
diameters = [80e-6, 100e-6, 120e-6, 150e-6, 170e-6, 200e-6, 220e-6, 250e-6]
mokev_val = dev.mokev.read()["mokev"]["value"]
console = Console()
table = Table(
title=f"At the current energy of {mokev_val:.4f} keV we have following options:",
box=box.SQUARE,
)
table.add_column("Diameter", justify="center")
table.add_column("Focal distance", justify="center")
table.add_column("Current beam size", justify="center")
wavelength = 1.2398e-9 / mokev_val
for diameter in diameters:
outermost_zonewidth = 60e-9
focal_distance = diameter * outermost_zonewidth / wavelength
beam_size = (
-diameter / (focal_distance * 1000) * (focal_distance * 1000 - distance) * 1e6
)
table.add_row(
f"{diameter*1e6:.2f} microns",
f"{focal_distance:.2f} mm",
f"{beam_size:.2f} microns",
)
console.print(table)
print("OSA Information:")
# print(f"Current losaz %.1f\n", A[losaz])
# print("The OSA will collide with the sample plane at %.1f\n\n", 89.3-A[loptz])
print(
"The numbers presented here are for a sample in the plane of the lamni sample holder.\n"
)

View File

@@ -1,3 +0,0 @@
from .cSAXS import *
# from .LamNI import *

View File

@@ -1 +0,0 @@
from .cSAXS_beamline import fshopen, fshclose, fshstatus, epics_get, epics_put

View File

@@ -1,25 +0,0 @@
"""
Pre-startup script for BEC client. This script is executed before the BEC client
is started. It can be used to set up the BEC client configuration. The script is
executed in the global namespace of the BEC client. This means that all
variables defined here are available in the BEC client.
To set up the BEC client configuration, use the ServiceConfig class. For example,
to set the configuration file path, add the following lines to the script:
import pathlib
from bec_lib import ServiceConfig
current_path = pathlib.Path(__file__).parent.resolve()
CONFIG_PATH = f"{current_path}/<path_to_my_config_file.yaml>"
config = ServiceConfig(CONFIG_PATH)
If this startup script defined a ServiceConfig object, the BEC client will use
it to configure itself. Otherwise, the BEC client will use the default config.
"""
# example:
# current_path = pathlib.Path(__file__).parent.resolve()
# CONFIG_PATH = f"{current_path}/../../../bec_config.yaml"
# config = ServiceConfig(CONFIG_PATH)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,152 +0,0 @@
import yaml
# TODO: fix imports, those classes are located in .../bec/scibec/init_scibec/configs/config.py
# (see also lamni_config.py in bec repository)
from .config import DemoConfig, X12SAConfig
class LamNIConfig(DemoConfig, X12SAConfig):
def run(self):
# self.write_galil_motors()
# self.write_rt_motors()
# self.write_smaract_motors()
# self.write_eiger1p5m()
self.write_x12sa_status()
self.write_sls_status()
self.load_csaxs_config()
# self.write_sim_user_motors()
# self.write_sim_beamline_motors()
# self.write_sim_beamline_monitors()
def write_galil_motors(self):
lamni_galil_motors = [
("lsamx", "A", -1, 0.5, {"center": 8.768000}),
("lsamy", "B", 1, 0.5, {"center": 10.041000}),
("lsamrot", "C", 1, 0.5, {}),
("loptz", "D", -1, 0.5, {}),
("loptx", "E", 1, 0.5, {"in": -0.8380, "out": -0.699}),
("lopty", "F", 1, 0.5, {"in": 3.3540, "out": 3.53}),
("leyex", "G", -1, 0.001, {"in": 14.117000}),
("leyey", "H", -1, 0.001, {"in": 48.069000, "out": 0.5}),
]
out = {}
for m in lamni_galil_motors:
out[m[0]] = dict(
{
"status": {"enabled": True, "enabled_set": True},
"deviceClass": "GalilMotor",
"deviceConfig": {
"axis_Id": m[1],
"name": m[0],
"labels": m[0],
"host": "mpc2680.psi.ch",
"port": 8081,
"sign": m[2],
"limits": [0, 0],
"tolerance": m[3],
"device_access": True,
"device_mapping": {"rt": "rtx"},
},
"acquisitionConfig": {
"schedule": "sync",
"acquisitionGroup": "motor",
"readoutPriority": "baseline",
},
"deviceTags": ["lamni"],
}
)
if m[4]:
out[m[0]]["userParameter"] = m[4]
self.write_section(out, "LamNI Galil motors")
def write_rt_motors(self):
lamni_rt_motors = [
("rtx", "A", 1),
("rty", "B", 1),
]
out = dict()
for m in lamni_rt_motors:
out[m[0]] = dict(
{
"status": {"enabled": True, "enabled_set": True},
"deviceClass": "RtLamniMotor",
"deviceConfig": {
"axis_Id": m[1],
"name": m[0],
"labels": m[0],
"host": "mpc2680.psi.ch",
"port": 3333,
"limits": [0, 0],
"sign": m[2],
"device_access": True,
},
"acquisitionConfig": {
"schedule": "sync",
"acquisitionGroup": "motor",
"readoutPriority": "baseline",
},
"deviceTags": ["lamni"],
}
)
self.write_section(out, "LamNI RT")
def write_smaract_motors(self):
lamni_smaract_motors = [
("losax", "A", -1, {"in": -0.848000}),
("losay", "B", -1, {"in": 0.135000, "out": 3.8}),
("losaz", "C", 1, {"in": -1, "out": -3}),
("lcsx", "D", -1, {}),
("lcsy", "E", -1, {}),
]
out = dict()
for m in lamni_smaract_motors:
out[m[0]] = dict(
{
"status": {"enabled": True, "enabled_set": True},
"deviceClass": "SmaractMotor",
"deviceConfig": {
"axis_Id": m[1],
"name": m[0],
"labels": m[0],
"host": "mpc2680.psi.ch",
"port": 8085,
"limits": [0, 0],
"sign": m[2],
"tolerance": 0.05,
},
"acquisitionConfig": {
"schedule": "sync",
"acquisitionGroup": "motor",
"readoutPriority": "baseline",
},
"deviceTags": ["lamni"],
}
)
if m[3]:
out[m[0]]["userParameter"] = m[3]
self.write_section(out, "LamNI SmarAct motors")
def write_eiger1p5m(self):
out = {
"eiger1p5m": {
"description": "Eiger 1.5M in vacuum detector, in-house developed, PSI",
"status": {"enabled": True, "enabled_set": True},
"deviceClass": "Eiger1p5MDetector",
"deviceConfig": {"device_access": True, "name": "eiger1p5m"},
"acquisitionConfig": {
"schedule": "sync",
"acquisitionGroup": "detector",
"readoutPriority": "monitored",
},
"deviceTags": ["detector"],
}
}
self.write_section(out, "LamNI Eiger 1.5M in vacuum")
def load_csaxs_config(self):
CONFIG_PATH = "./init_scibec/configs/test_config_cSAXS.yaml"
content = {}
with open(CONFIG_PATH, "r") as csaxs_config_file:
content = yaml.safe_load(csaxs_config_file.read())
self.write_section(content, "Default cSAXS config")

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
import os
def setup_epics_ca():
os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO"
os.environ["EPICS_CA_ADDR_LIST"] = "129.129.122.255 sls-x12sa-cagw.psi.ch:5836"
os.environ["PYTHONIOENCODING"] = "latin1"
def run():
setup_epics_ca()

View File

@@ -1,182 +0,0 @@
import os
import json
import subprocess
import requests
from bec_lib.file_utils import FileWriterMixin
#from bec_lib.bec_service import SERVICE_CONFIG
class csaxs_archiver:
"""Class to archive data from a beamtime.
To run this script from a shell, go to discovery.psi.ch and copy your token.
Complement the information in user_input below in the if __name__ == __main__ part of the script.
Afterwards, get a Keberos token (kinit) for yourself in the shell.
Activate the bec_venv by doing "source bec_venv/bin/activate" and then run this code via python $filename.
As a last step, adjust the dictionary below in if __name__ == '__main__' with your token as well as information about the experiment
"""
def __init__(
self,
start_scan: int,
stop_scan: int,
base_path: str,
log_path: str,
eacc: str,
pi: str,
pi_email: str,
token: str,
type: str = "raw",
overwrite: bool = False,
online: bool = True,
):
self.start_scan = start_scan
self.stop_scan = stop_scan
self.log_path = os.path.expanduser(log_path)
self.eacc = eacc
self.pi = pi
self.pi_email = pi_email
self.token = token
self.type = type
self.overwrite = overwrite
self.online = online
#from bec_lib.bec_service import SERVICE_CONFIG
#SERVICE_CONFIG.config["service_config"]["file_writer"]
self._load_datacatalogue_module()
self._create_directory(base_path)
self._disable_mail_confirmation()
self.service_cfg = {'base_path' : f'{self.base_path}'}
self.file_writer = FileWriterMixin(self.service_cfg)
def _disable_mail_confirmation(self):
# Define the URL and payload
url = "https://dacat.psi.ch/api/v3/Policies/updatewhere"
payload = {
"ownerGroupList": f'p{self.eacc[1:]}',
"data": '{"archiveEmailNotification": false, "accessGroups": ["slscsaxs"]}'
}
# Define headers
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
}
# Add the access_token to the URL
url += "?access_token=" + self.token
# Make a POST request
print(url, payload, headers)
response = requests.post(url, data=payload, headers=headers)
# Check the response
if response.status_code == 200:
print("Request was successful.")
print(response.json())
else:
print("Request failed with status code:", response.status_code)
print(response.text)
def _create_directory(self, base_path: str) -> None:
if self.online:
self.base_path = os.path.expanduser("~/Data10")
else:
self.base_path = base_path
if not os.path.exists(self.log_path):
os.makedirs(self.log_path)
def _load_datacatalogue_module(self):
command = 'module add datacatalog/1.1.9'
os.popen(command)
# result = subprocess.run(
# command,
# shell=False,
# stdout=subprocess.PIPE,
# stderr=subprocess.PIPE,
# universal_newlines=True,
# )
# if result.returncode == 0:
# print(f"Command {command} was succesful")
# else:
# print(f"Failed to run command {command} with return message {result.returncode}")
def prep_metadata(self, scannr: int) -> dict:
user_metadata = {}
user_metadata.update(
{
"principalInvestigator": self.pi_email,
"owner": self.pi,
"ownerEmail": self.pi_email,
"sourceFolder": self.base_path,
"creationLocation": "/PSI/SLS/CSAXS",
"type": "raw",
"ownerGroup": f"p{self.eacc.strip('e')}",
"datasetName": f"S{scannr:05d}",
}
)
return user_metadata
def _write_ingestion_log(self, scannr: int) -> None:
...
def run_for_all_scans(self):
for scan in range(self.start_scan, self.stop_scan + 1):
print(f"Start ingestion for scan {scan}")
fname = os.path.join(os.path.expanduser(self.log_path), f"ingestion_log_S{scan:05d}")
self.datafile_name = f"{fname}.txt"
if os.path.isfile(self.datafile_name) and not self.overwrite == True:
print(
f"Skipping scan {scan}, already ingested due to logs, moving on to next scan {scan+1}"
)
continue
user_metadata = self.prep_metadata(scan)
# Write metadata file in json file
self.metadata_file = f"{fname}.json"
with open(self.metadata_file, "w") as file:
json.dump(user_metadata, file)
# Compile datapath based on structure a cSAXS
datadir_path = os.path.join('data', self.file_writer.get_scan_directory(scan, 1000, 5))
print(f"Archiving directory {datadir_path}")
if not os.path.isdir(os.path.join(self.base_path, datadir_path)):
print(f"Did not find directory {datadir_path}, skipping scan {scan}")
continue
# Create datafile path for archiving
with open(self.datafile_name, "w") as file:
file.write(datadir_path)
print(f"Starting ingestion for S#{scan}")
command = f'datasetIngestor -allowexistingsource -ingest -autoarchive -noninteractive -token {self.token} {self.metadata_file} {self.datafile_name}'
rtr = os.popen(command)
#with open(os.path.join(fname,'_log.txt'), "w") as file:
# print(f'Writing reponse to file')
# file.write(rtr.read())
# result = subprocess.run(command, shell=False, stdout = subprocess.PIPE, stderr = subprocess.PIPE, universal_newlines=True)
# if result.returncode == 0:
# print(f"Command {command} was succesful")
# else:
# print(f"Failed to run command {command} with return message {result.returncode}")
if __name__ == "__main__":
# Generate dictionary with user input.
user_input = {
"base_path": "~/Data10",
"eacc": "e20638",
"pi": "Emma Sparr",
"pi_email": "emma.sparr@fkem1.lu.se",
'log_path' : '~/Data10/documentation/ingestion_logs/',
'token' : 'YK8gkmQmEVxVxjiA57D6tVmpBVs7T235nWEuBT0behN9BPM2BdWARWPPgEsQVrPe',
'start_scan' : 1,
'stop_scan' : 450,
}
archiver = csaxs_archiver(**user_input)
archiver.run_for_all_scans()

View File

@@ -1,88 +0,0 @@
import csv
import os
from collections import defaultdict
from collections.abc import Callable
import numpy as np
class ScanItem:
def __init__(self, offset_xy: Callable) -> None:
self.start_entry = None
self.end_entry = None
self.offset_xy = offset_xy
def to_scan_params(self) -> dict:
scan_params = {
"start_x": float(self.start_entry["X"]) + self.offset_xy()[0],
"start_y": float(self.start_entry["Y"]) + self.offset_xy()[1],
"end_x": float(self.end_entry["X"]) + self.offset_xy()[0],
"end_y": float(self.end_entry["Y"]) + self.offset_xy()[1],
"interval_x": int(
np.round(
np.abs(float(self.start_entry["X"]) - float(self.end_entry["X"]))
/ (float(self.start_entry["step_x [mu]"]) * 1e-3)
)
),
"interval_y": int(
np.round(
np.abs(float(self.start_entry["Y"]) - float(self.end_entry["Y"]))
/ (float(self.start_entry["step_y [mu]"]) * 1e-3)
)
),
"exp_time": float(self.start_entry["exp_time [s]"]),
"readout_time": 3e-3,
"md": {"sample_name": self.start_entry["sample name"]},
}
if scan_params["interval_x"] < 1 or scan_params["interval_x"] < 1:
raise ValueError("Bugger off...")
return scan_params
class SAXSParams:
def __init__(self, offset: Callable):
self.offset_xy = offset
self.data = defaultdict(lambda: ScanItem(offset))
def load_from_csv(self, file_path: str) -> None:
"""
Load the acquisition parameter from a csv file.
"""
if not os.path.exists(file_path):
raise FileNotFoundError(
f"The specified CSV file could not be found. Please check that the given path is correct: {file_path}."
)
data_transposed = defaultdict(lambda: [])
with open(os.path.expanduser(file_path), "r") as file:
csv_reader = csv.DictReader(file)
for row in csv_reader:
for key, val in row.items():
data_transposed[key].append(val)
if int(row["start"]):
self.data[row["sample name"]].start_entry = row
else:
self.data[row["sample name"]].end_entry = row
self._check_params(dict(data_transposed))
def _check_params(self, data_transposed: dict) -> dict:
sample_names = set(data_transposed["sample name"])
if len(data_transposed["start"]) != len(sample_names) * 2:
raise ValueError(
f"The given params file does not provide N*2 start/stop positions. Found {len(sample_names)} samples and {len(data_transposed['start'])} start/stop positions."
)
if __name__ == "__main__":
from pprint import pprint
INPUT_FILE = "/sls/X12SA/data/e21206/Data10/software/test_script.csv"
def my_offset():
return [0, 0]
params = SAXSParams(my_offset)
params.load_from_csv(INPUT_FILE)
for key in params.data:
pprint(params.data[key].to_scan_params())

View File

@@ -1,13 +0,0 @@
import yaml
CONFIG_PATH = "/sls/X12SA/data/gac-x12saop/bec/config/bec_service_config.yaml"
def load_service_config() -> dict:
"""Load the service configuration from the YAML file."""
with open(CONFIG_PATH, "r", encoding="utf-8") as stream:
try:
config = yaml.safe_load(stream)
except yaml.YAMLError as exc:
print(exc)
return config

1
bin/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
# Add anything you don't want to check in to git, e.g. very large files

View File

@@ -3,8 +3,9 @@ from __future__ import annotations
import os
import subprocess
from bec_lib import MessageEndpoints, RedisConnector, bec_logger, messages
from bec_lib.redis_connector import MessageObject
from bec_lib import bec_logger, messages
from bec_lib.endpoints import MessageEndpoints
from bec_lib.redis_connector import MessageObject, RedisConnector
logger = bec_logger.logger
@@ -12,7 +13,6 @@ logger = bec_logger.logger
class PilatusConverter:
def __init__(self, host: str, port: int) -> None:
self._connector = RedisConnector(f"{host}:{port}")
self._producer = self._connector.producer()
def start(self) -> None:
"""start the consumer"""
@@ -27,7 +27,7 @@ class PilatusConverter:
message (MessageObject): Message object
parent (PilatusConverter): Parent object
"""
msg = messages.MessageReader.loads(message.value)
msg = message.value
print(msg)
if not msg:
return
@@ -56,10 +56,9 @@ class PilatusConverter:
"""
Start the consumer.
"""
file_consumer = self._connector.consumer(
self._connector.register(
MessageEndpoints.file_event("pilatus_2"), cb=self.on_new_message, parent=self
)
file_consumer.start()
if __name__ == "__main__":

View File

@@ -1,5 +0,0 @@
for i in `seq 1 8`
do
ssh -N -R 6379:localhost:6379 x12sa-cn-$i &
done

View File

@@ -1,48 +0,0 @@
if [ "pc15543.psi.ch" != "$(hostname)" ]; then
echo "Please run this script on pc15543"
exit 1
fi
module use unstable
module add psi-python311/2024.02
echo module add tmux/3.2 >> ~/.bashrc
echo module add redis/7.0.12 >> ~/.bashrc
source ~/.bashrc
cd ~/Data10
mkdir -p software/
mkdir -p ~/bec/scripts
cd software
git clone https://gitlab.psi.ch/bec/bec.git
git clone https://gitlab.psi.ch/bec/ophyd_devices.git
git clone https://gitlab.psi.ch/bec/bec-widgets.git
python -m venv ./bec_venv
source ./bec_venv/bin/activate
cd bec
git checkout sastt-online-changes
pip install -e ./bec_server[dev]
cd ../csaxs-bec
pip install -e .[dev]
#redis-server --protected-mode no &
read -p "Do you want to set the current BEC account to $(whoami)? (yes/no) " yn
val=$(whoami)
case $yn in
yes ) echo ok, setting account to $val;
redis-cli SET internal/account:val $val;;
no ) echo ;;
* ) echo invalid response;
exit 1;;
esac
$(pwd)/open_tunnel.sh

View File

@@ -1,17 +0,0 @@
module use unstable
module add psi-python311/2024.02
cd ~/Data10/software
python -m venv ./bec_widgets_venv
source ./bec_widgets_venv/bin/activate
pip install --upgrade pip
cd ~/Data10/software/bec/bec_lib
pip install -e .
cd ~/Data10/software/csaxs-bec
pip install -e .
cd ~/Data10/software/bec-widgets
pip install -e .
echo "For the moment widgets only run on beamline consoles comp1/2 and cons1"

View File

@@ -1,3 +1,7 @@
""" Script used for parsing scan parameters from a CSV file as created by the motormap GUI.
This needs to be reviewed and tested during the deployment phase."""
# pylint: skip-file
import csv
import os
from collections import defaultdict

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -0,0 +1,2 @@
from .load_additional_correction import lamni_read_additional_correction
from .x_ray_eye_align import DataDrivenLamNI, LamNI, MagLamNI, XrayEyeAlign

View File

@@ -0,0 +1,329 @@
import builtins
import time
from rich import box
from rich.console import Console
from rich.table import Table
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
# import builtins to avoid linter errors
dev = builtins.__dict__.get("dev")
umv = builtins.__dict__.get("umv")
bec = builtins.__dict__.get("bec")
class LamNIInitError(Exception):
pass
class LaMNIInitStagesMixin:
def lamni_init_stages(self):
user_input = input("Starting initialization of LamNI stages. OK? [y/n]")
if user_input == "y":
print("staring...")
dev.lsamrot.enabled = True
else:
return
if self.check_all_axes_of_lamni_referenced():
user_input = input("Continue anyways? [y/n]")
if user_input == "y":
print("ok then...")
else:
return
axis_id_lsamrot = dev.lsamrot._config["deviceConfig"].get("axis_Id")
if dev.lsamrot.controller.get_motor_limit_switch(axis_id_lsamrot)[1] == False:
user_input = input("The rotation stage will be moved to one limit [y/n]")
if user_input == "y":
print("starting...")
else:
return
self.drive_axis_to_limit(dev.lsamrot, "forward")
dev.lsamrot.enabled = False
print("Now hard reboot the controller and run the initialization routine again.")
print("The controller will be disabled in bec. To enable dev.lsamrot.enabled=True")
return
user_input = input(
"Init of loptz. Can the stage move to the upstream limit without collision?? [y/n]"
)
if user_input == "y":
print("ok then...")
else:
return
print("Referencing loptz")
self.drive_axis_to_limit(dev.loptz, "forward")
self.find_reference_mark(dev.loptz)
print("Referencing loptx")
self.drive_axis_to_limit(dev.loptx, "reverse")
self.find_reference_mark(dev.loptx)
print("Referencing lopty")
self.drive_axis_to_limit(dev.lopty, "forward")
self.find_reference_mark(dev.lopty)
print("Referencing lsamx")
self.drive_axis_to_limit(dev.lsamx, "forward")
self.find_reference_mark(dev.lsamx)
print("Referencing lsamy")
self.drive_axis_to_limit(dev.lsamy, "reverse")
self.find_reference_mark(dev.lsamy)
# the dual encoder requires the reference mark to pass on both encoders
print("Referencing lsamrot")
self.drive_axis_to_limit(dev.lsamrot, "reverse")
time.sleep(0.1)
self.find_reference_mark(dev.lsamrot)
user_input = input("Init of leye. Can the stage move to -x limit without collision? [y/n]")
if user_input == "y":
print("starting...")
else:
return
print("Referencing leyex")
self.drive_axis_to_limit(dev.leyex, "forward")
print("Referencing leyey")
self.drive_axis_to_limit(dev.leyey, "forward")
# set_lm lsamx 6 14
# set_lm lsamy 6 14
# set_lm lsamrot -3 362
# set_lm loptx -1 -0.2
# set_lm lopty 3.0 3.6
# set_lm loptz 82 87
# set_lm leyex 0 25
# set_lm leyey 0.5 50
print("Init of Smaract stages")
dev.losax.controller.find_reference_mark(2, 0, 1000, 1)
time.sleep(1)
dev.losax.controller.find_reference_mark(0, 0, 1000, 1)
time.sleep(1)
dev.losax.controller.find_reference_mark(1, 0, 1000, 1)
time.sleep(1)
# dev.losax.controller.find_reference_mark(3, 1, 1000, 1)
# time.sleep(1)
# dev.losax.controller.find_reference_mark(4, 1, 1000, 1)
# time.sleep(1)
# set_lm losax -1.5 0.25
# set_lm losay -2.5 4.1
# set_lm losaz -4.1 -0.5
# set_lm lcsy -1.5 5
self._align_setup()
def find_reference_mark(self, device):
axis_id = device._config["deviceConfig"].get("axis_Id")
axis_id_numeric = self.axis_id_to_numeric(axis_id)
device.controller.find_reference(axis_id_numeric)
def drive_axis_to_limit(self, device, direction):
axis_id = device._config["deviceConfig"].get("axis_Id")
axis_id_numeric = self.axis_id_to_numeric(axis_id)
device.controller.drive_axis_to_limit(axis_id_numeric, direction)
def axis_id_to_numeric(self, axis_id) -> int:
return ord(axis_id.lower()) - 97
def _align_setup(self):
user_input = input("Start moving stages to default initial positions? [y/n]")
if user_input == "y":
print("Start moving stages...")
else:
print("Stopping.")
return
lsamx_center = dev.lsamx.user_parameter.get("center")
if lsamx_center is None:
raise LamNIInitError(
"Could not find a lsamx center position. Please check your device config."
)
lsamy_center = dev.lsamy.user_parameter.get("center")
if lsamy_center is None:
raise LamNIInitError(
"Could not find a lsamy center position. Please check your device config."
)
umv(dev.lsamx, lsamx_center, dev.lsamy, lsamy_center, dev.loptx, -0.3, dev.lopty, 0)
umv(dev.losax, -1)
umv(dev.loptz, 82.25)
umv(dev.lsamrot, -1)
umv(dev.lsamrot, 0)
time.sleep(2)
dev.rtx.controller.feedback_disable_and_even_reset_lamni_angle_interferometer()
def check_all_axes_of_lamni_referenced(self):
if (
dev.losax.controller.axis_is_referenced(0)
& dev.losax.controller.axis_is_referenced(1)
& dev.losax.controller.axis_is_referenced(2)
& dev.lsamx.controller.all_axes_referenced()
):
print("All axes of LamNI are referenced.")
return True
else:
return False
class LamNIOpticsMixin:
@staticmethod
def _get_user_param_safe(device, var):
param = dev[device].user_parameter
if not param or param.get(var) is None:
raise ValueError(f"Device {device} has no user parameter definition for {var}.")
return param.get(var)
def leye_out(self):
self.loptics_in()
fshclose()
leyey_out = self._get_user_param_safe("leyey", "out")
umv(dev.leyey, leyey_out)
epics_put("XOMNYI-XEYE-ACQ:0", 2)
# move rotation stage to zero to avoid problems with wires
umv(dev.lsamrot, 0)
umv(dev.dttrz, 5854, dev.fttrz, 2395)
def leye_in(self):
bec.queue.next_dataset_number += 1
# move rotation stage to zero to avoid problems with wires
umv(dev.lsamrot, 0)
umv(dev.dttrz, 6419.677, dev.fttrz, 2959.979)
while True:
moved_out = (input("Did the flight tube move out? (Y/n)") or "y").lower()
if moved_out == "y":
break
if moved_out == "n":
return
leyex_in = self._get_user_param_safe("leyex", "in")
leyey_in = self._get_user_param_safe("leyey", "in")
umv(dev.leyex, leyex_in, dev.leyey, leyey_in)
self.align.update_frame()
def _lfzp_in(self):
loptx_in = self._get_user_param_safe("loptx", "in")
lopty_in = self._get_user_param_safe("lopty", "in")
umv(
dev.loptx, loptx_in, dev.lopty, lopty_in
) # for 7.2567 keV and 150 mu, 60 nm fzp, loptz 83.6000 for propagation 1.4 mm
def lfzp_in(self):
"""
move in the lamni zone plate.
This will disable rt feedback, move the FZP and re-enabled the feedback.
"""
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_disable()
self._lfzp_in()
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_enable_with_reset()
def loptics_in(self):
"""
Move in the lamni optics, including the FZP and the OSA.
"""
self.lfzp_in()
self.losa_in()
def loptics_out(self):
"""Move out the lamni optics"""
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_disable()
# self.lcs_out()
self.losa_out()
loptx_out = self._get_user_param_safe("loptx", "out")
lopty_out = self._get_user_param_safe("lopty", "out")
umv(dev.loptx, loptx_out, dev.lopty, lopty_out)
if "rtx" in dev and dev.rtx.enabled:
time.sleep(1)
dev.rtx.controller.feedback_enable_with_reset()
def lcs_in(self):
# umv lcsx -1.852 lcsy -0.095
pass
def lcs_out(self):
umv(dev.lcsy, 3)
def losa_in(self):
# 6.2 keV, 170 um FZP
# umv(dev.losax, -1.4450000, dev.losay, -0.1800)
# umv(dev.losaz, -1)
# 6.7, 170
# umv(dev.losax, -1.4850, dev.losay, -0.1930)
# umv(dev.losaz, 1.0000)
# 7.2, 150
losax_in = self._get_user_param_safe("losax", "in")
losay_in = self._get_user_param_safe("losay", "in")
losaz_in = self._get_user_param_safe("losaz", "in")
umv(dev.losax, losax_in, dev.losay, losay_in)
umv(dev.losaz, losaz_in)
# 11 kev
# umv(dev.losax, -1.161000, dev.losay, -0.196)
# umv(dev.losaz, 1.0000)
def losa_out(self):
losay_out = self._get_user_param_safe("losay", "out")
losaz_out = self._get_user_param_safe("losaz", "out")
umv(dev.losaz, losaz_out)
umv(dev.losay, losay_out)
def lfzp_info(self, mokev_val=-1):
if mokev_val == -1:
try:
mokev_val = dev.mokev.readback.get()
except:
print(
"Device mokev does not exist. You can specify the energy in keV as an argument instead."
)
return
loptz_val = dev.loptz.read()["loptz"]["value"]
distance = -loptz_val + 85.6 + 52
print(f"The sample is in a distance of {distance:.1f} mm from the FZP.")
diameters = [80e-6, 100e-6, 120e-6, 150e-6, 170e-6, 200e-6, 220e-6, 250e-6]
console = Console()
table = Table(
title=f"At the current energy of {mokev_val:.4f} keV we have following options:",
box=box.SQUARE,
)
table.add_column("Diameter", justify="center")
table.add_column("Focal distance", justify="center")
table.add_column("Current beam size", justify="center")
wavelength = 1.2398e-9 / mokev_val
for diameter in diameters:
outermost_zonewidth = 60e-9
focal_distance = diameter * outermost_zonewidth / wavelength
beam_size = (
-diameter / (focal_distance * 1000) * (focal_distance * 1000 - distance) * 1e6
)
table.add_row(
f"{diameter*1e6:.2f} microns",
f"{focal_distance:.2f} mm",
f"{beam_size:.2f} microns",
)
console.print(table)
print("OSA Information:")
# print(f"Current losaz %.1f\n", A[losaz])
# print("The OSA will collide with the sample plane at %.1f\n\n", 89.3-A[loptz])
print(
"The numbers presented here are for a sample in the plane of the lamni sample holder.\n"
)

View File

@@ -1,7 +1,6 @@
def lamni_read_additional_correction():
# "additional_correction_shift"
# [0][] x , [1][] y, [2][] angle, [3][0] number of elements
import numpy as np
with open("correction_lamni_um_S01405_.txt", "r") as f:
num_elements = f.readline()

View File

@@ -9,13 +9,14 @@ from pathlib import Path
import h5py
import numpy as np
from bec_client.plugins.cSAXS import epics_get, epics_put, fshclose, fshopen
from bec_lib import bec_logger
from bec_lib.alarm_handler import AlarmBase
from bec_lib.pdf_writer import PDFWriter
from typeguard import typechecked
from .lamni_optics_mixin import LamNIOpticsMixin
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen
from .lamni_optics_mixin import LaMNIInitStagesMixin, LamNIOpticsMixin
logger = bec_logger.logger
bec = builtins.__dict__.get("bec")
@@ -31,7 +32,7 @@ class XrayEyeAlign:
self.lamni = lamni
self.device_manager = client.device_manager
self.scans = client.scans
self.xeye = self.device_manager.devices.xeye
# self.xeye = self.device_manager.devices.xeye
self.alignment_values = defaultdict(list)
self._reset_init_values()
self.corr_pos_x = []
@@ -188,7 +189,8 @@ class XrayEyeAlign:
val_y = epics_get(f"XOMNYI-XEYE-YVAL_Y:{k}") * self.PIXEL_CALIBRATION # in mm
self.alignment_values[k] = [val_x, val_y]
print(
f"Clicked position {k}: x {self.alignment_values[k][0]}, y {self.alignment_values[k][1]}"
f"Clicked position {k}: x {self.alignment_values[k][0]}, y"
f" {self.alignment_values[k][1]}"
)
if k == 0: # received center value of FZP
@@ -213,7 +215,10 @@ class XrayEyeAlign:
elif (
k == 1
): # received sample center value at samroy 0 ie the final base shift values
msg = f"Base shift values from movement are x {self.shift_xy[0]}, y {self.shift_xy[1]}"
msg = (
f"Base shift values from movement are x {self.shift_xy[0]}, y"
f" {self.shift_xy[1]}"
)
print(msg)
logger.info(msg)
self.shift_xy[0] += (
@@ -223,13 +228,12 @@ class XrayEyeAlign:
self.alignment_values[1][1] - self.alignment_values[0][1]
) * 1000
print(
f"Base shift values from movement and clicked position are x {self.shift_xy[0]}, y {self.shift_xy[1]}"
"Base shift values from movement and clicked position are x"
f" {self.shift_xy[0]}, y {self.shift_xy[1]}"
)
self.scans.lamni_move_to_scan_center(
self.shift_xy[0] / 1000,
self.shift_xy[1] / 1000,
self.get_tomo_angle(),
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
).wait()
self.send_message("please wait ...")
@@ -238,9 +242,7 @@ class XrayEyeAlign:
time.sleep(1)
self.scans.lamni_move_to_scan_center(
self.shift_xy[0] / 1000,
self.shift_xy[1] / 1000,
self.get_tomo_angle(),
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
).wait()
epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle())
@@ -257,16 +259,12 @@ class XrayEyeAlign:
self._disable_rt_feedback()
self.tomo_rotate((k - 1) * 45 - 45 / 2)
self.scans.lamni_move_to_scan_center(
self.shift_xy[0] / 1000,
self.shift_xy[1] / 1000,
self.get_tomo_angle(),
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
).wait()
self._disable_rt_feedback()
self.tomo_rotate((k - 1) * 45)
self.scans.lamni_move_to_scan_center(
self.shift_xy[0] / 1000,
self.shift_xy[1] / 1000,
self.get_tomo_angle(),
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
).wait()
epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle())
@@ -293,9 +291,7 @@ class XrayEyeAlign:
self.shift_xy[0] = self.shift_xy[0] + _xrayeyalignmvx
self.shift_xy[1] = self.shift_xy[1] + _xrayeyalignmvy
self.scans.lamni_move_to_scan_center(
self.shift_xy[0] / 1000,
self.shift_xy[1] / 1000,
self.get_tomo_angle(),
self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
).wait()
print(
f"Current center horizontal {self.shift_xy[0]} vertical {self.shift_xy[1]}"
@@ -310,12 +306,14 @@ class XrayEyeAlign:
fovx = self._xray_fov_xy[0] * self.PIXEL_CALIBRATION * 1000 / 2
fovy = self._xray_fov_xy[1] * self.PIXEL_CALIBRATION * 1000 / 2
print(
f"The largest field of view from the xrayeyealign was \nfovx = {fovx:.0f} microns, fovy = {fovy:.0f} microns"
f"The largest field of view from the xrayeyealign was \nfovx = {fovx:.0f} microns, fovy"
f" = {fovy:.0f} microns"
)
print("Use matlab routine to fit the current alignment...")
print(
f"This additional shift is applied to the base shift values\n which are x {self.shift_xy[0]}, y {self.shift_xy[1]}"
"This additional shift is applied to the base shift values\n which are x"
f" {self.shift_xy[0]}, y {self.shift_xy[1]}"
)
self._disable_rt_feedback()
@@ -323,7 +321,8 @@ class XrayEyeAlign:
self.tomo_rotate(0)
print(
"\n\nNEXT LOAD ALIGNMENT PARAMETERS\nby running lamni.align.read_xray_eye_correction()\n"
"\n\nNEXT LOAD ALIGNMENT PARAMETERS\nby running"
" lamni.align.read_xray_eye_correction()\n"
)
self.client.set_global_var("tomo_fov_offset", self.shift_xy)
@@ -332,12 +331,13 @@ class XrayEyeAlign:
with open(
os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues"), "w"
) as alignment_values_file:
alignment_values_file.write(f"angle\thorizontal\tvertical\n")
alignment_values_file.write("angle\thorizontal\tvertical\n")
for k in range(2, 11):
fovx_offset = (self.alignment_values[0][0] - self.alignment_values[k][0]) * 1000
fovy_offset = (self.alignment_values[k][1] - self.alignment_values[0][1]) * 1000
print(
f"Writing to file new alignment: number {k}, value x {fovx_offset}, y {fovy_offset}"
f"Writing to file new alignment: number {k}, value x {fovx_offset}, y"
f" {fovy_offset}"
)
alignment_values_file.write(f"{(k-2)*45}\t{fovx_offset}\t{fovy_offset}\n")
@@ -422,7 +422,8 @@ class XrayEyeAlign:
additional_correction_shift_x = self.corr_pos_x[-1]
additional_correction_shift_y = self.corr_pos_y[-1]
print(
f"Additional correction shifts: {additional_correction_shift_x} {additional_correction_shift_y}"
"Additional correction shifts:"
f" {additional_correction_shift_x} {additional_correction_shift_y}"
)
return (additional_correction_shift_x, additional_correction_shift_y)
@@ -476,7 +477,8 @@ class XrayEyeAlign:
additional_correction_shift_x = self.corr_pos_x_2[-1]
additional_correction_shift_y = self.corr_pos_y_2[-1]
print(
f"Additional correction shifts 2: {additional_correction_shift_x} {additional_correction_shift_y}"
"Additional correction shifts 2:"
f" {additional_correction_shift_x} {additional_correction_shift_y}"
)
return (additional_correction_shift_x, additional_correction_shift_y)
@@ -509,7 +511,7 @@ class LamNI(LamNIOpticsMixin):
super().__init__()
self.client = client
self.align = XrayEyeAlign(client, self)
self.init = LaMNIInitStagesMixin()
self.check_shutter = True
self.check_light_available = True
self.check_fofb = True
@@ -525,7 +527,8 @@ class LamNI(LamNIOpticsMixin):
def get_beamline_checks_enabled(self):
print(
f"Shutter: {self.check_shutter}\nFOFB: {self.check_fofb}\nLight available: {self.check_light_available}"
f"Shutter: {self.check_shutter}\nFOFB: {self.check_fofb}\nLight available:"
f" {self.check_light_available}"
)
@property
@@ -583,6 +586,27 @@ class LamNI(LamNIOpticsMixin):
def tomo_circfov(self, val: float):
self.client.set_global_var("tomo_circfov", val)
@property
def tomo_type(self):
val = self.client.get_global_var("tomo_type")
if val is None:
return 1
return val
@tomo_type.setter
def tomo_type(self, val: float):
if val == 1:
# equally spaced tomography with 8 sub tomograms
self.client.set_global_var("tomo_type", val)
# elif val == 2:
# # golden ratio tomography (sorted bunches)
# self.client.set_global_var("tomo_type", val)
# elif val == 3:
# # equally spaced tomography with starting angles shifted by golden ratio
# self.client.set_global_var("tomo_type", val)
else:
raise ValueError("Unknown tomo_type.")
@property
def tomo_countingtime(self):
val = self.client.get_global_var("tomo_countingtime")
@@ -766,13 +790,18 @@ class LamNI(LamNIOpticsMixin):
# pylint: disable=undefined-variable
self._current_scan_list.append(bec.queue.next_scan_number)
logger.info(
f"scans.lamni_fermat_scan(fov_size=[{self.lamni_piezo_range_x},{self.lamni_piezo_range_y}], step={self.tomo_shellstep}, stitch_x={0}, stitch_y={0}, stitch_overlap={1},"
f"center_x={self.align.tomo_fovx_offset}, center_y={self.align.tomo_fovy_offset}, "
f"shift_x={self.manual_shift_x+correction_xeye_mu[0]-additional_correction[0]-additional_correction_2[0]}, "
f"shift_y={self.manual_shift_y+correction_xeye_mu[1]-additional_correction[1]-additional_correction_2[1]}, "
f"fov_circular={self.tomo_circfov}, angle={angle}, scan_type='fly')"
f"scans.lamni_fermat_scan(fov_size=[{self.lamni_piezo_range_x},{self.lamni_piezo_range_y}],"
f" step={self.tomo_shellstep}, stitch_x={0}, stitch_y={0},"
f" stitch_overlap={1},center_x={self.align.tomo_fovx_offset},"
f" center_y={self.align.tomo_fovy_offset},"
f" shift_x={self.manual_shift_x+correction_xeye_mu[0]-additional_correction[0]-additional_correction_2[0]},"
f" shift_y={self.manual_shift_y+correction_xeye_mu[1]-additional_correction[1]-additional_correction_2[1]},"
f" fov_circular={self.tomo_circfov}, angle={angle}, scan_type='fly')"
)
log_message = (
f"{str(datetime.datetime.now())}: LamNI scan projection at angle {angle}, scan"
f" number {bec.queue.next_scan_number}.\n"
)
log_message = f"{str(datetime.datetime.now())}: LamNI scan projection at angle {angle}, scan number {bec.queue.next_scan_number}.\n"
self.write_to_spec_log(log_message)
# self.write_to_scilog(log_message, ["BEC_scans", self.sample_name])
corridor_size = self.corridor_size if self.corridor_size > 0 else None
@@ -814,10 +843,7 @@ class LamNI(LamNIOpticsMixin):
msgs.append("Check beam failed: Shutter is closed.")
if self.check_light_available:
machine_status = dev.sls_machine_status.read(cached=True)
if machine_status["value"] not in [
"Light Available",
"Light-Available",
]:
if machine_status["value"] not in ["Light Available", "Light-Available"]:
self._beam_is_okay = False
msgs.append("Check beam failed: Light not available.")
if self.check_fofb:
@@ -858,7 +884,8 @@ class LamNI(LamNIOpticsMixin):
try:
msg = bec.logbook.LogbookMessage()
msg.add_text(
f"<p><mark class='pen-red'><strong>Beamline checks failed at {str(datetime.datetime.now())}: {''.join(self._check_msgs)}</strong></mark></p>"
"<p><mark class='pen-red'><strong>Beamline checks failed at"
f" {str(datetime.datetime.now())}: {''.join(self._check_msgs)}</strong></mark></p>"
).add_tag(["BEC", "beam_check"])
self.client.logbook.send_logbook_message(msg)
except Exception:
@@ -875,25 +902,20 @@ class LamNI(LamNIOpticsMixin):
try:
msg = bec.logbook.LogbookMessage()
msg.add_text(
f"<p><mark class='pen-red'><strong>Operation resumed at {str(datetime.datetime.now())}.</strong></mark></p>"
"<p><mark class='pen-red'><strong>Operation resumed at"
f" {str(datetime.datetime.now())}.</strong></mark></p>"
).add_tag(["BEC", "beam_check"])
self.client.logbook.send_logbook_message(msg)
except Exception:
logger.warning("Failed to send update to SciLog.")
def add_sample_database(
self,
samplename,
date,
eaccount,
scan_number,
setup,
sample_additional_info,
user,
self, samplename, date, eaccount, scan_number, setup, sample_additional_info, user
):
"""Add a sample to the omny sample database. This also retrieves the tomo id."""
subprocess.run(
f"wget --user=omny --password=samples -q -O /tmp/currsamplesnr.txt 'https://omny.web.psi.ch/samples/newmeasurement.php?sample={samplename}&date={date}&eaccount={eaccount}&scannr={scan_number}&setup={setup}&additional={sample_additional_info}&user={user}'",
"wget --user=omny --password=samples -q -O /tmp/currsamplesnr.txt"
f" 'https://omny.web.psi.ch/samples/newmeasurement.php?sample={samplename}&date={date}&eaccount={eaccount}&scannr={scan_number}&setup={setup}&additional={sample_additional_info}&user={user}'",
shell=True,
)
with open("/tmp/currsamplesnr.txt") as tomo_number_file:
@@ -947,25 +969,63 @@ class LamNI(LamNIOpticsMixin):
# _tomo_shift_angles (potential global variable)
_tomo_shift_angles = 0
angle_end = start_angle + 360
for angle in np.linspace(
angles = np.linspace(
start_angle + _tomo_shift_angles,
angle_end,
num=int(360 / self.tomo_angle_stepsize) + 1,
endpoint=True,
):
successful = False
error_caught = False
if 0 <= angle < 360.05:
print(f"Starting LamNI scan for angle {angle}")
while not successful:
self._start_beam_check()
if not self.special_angles:
self._current_special_angles = []
if self._current_special_angles:
next_special_angle = self._current_special_angles[0]
if np.isclose(angle, next_special_angle, atol=0.5):
self._current_special_angles.pop(0)
num_repeats = self.special_angle_repeats
)
# reverse even sub-tomograms
if not (subtomo_number % 2):
angles = np.flip(angles)
for angle in angles:
self.progress["subtomo"] = subtomo_number
self.progress["subtomo_projection"] = angles.index(angle)
self.progress["subtomo_total_projections"] = 180 / self.tomo_angle_stepsize
self.progress["projection"] = (subtomo_number - 1) * self.progress[
"subtomo_total_projections"
] + self.progress["subtomo_projection"]
self.progress["total_projections"] = 180 / self.tomo_angle_stepsize * 8
self.progress["angle"] = angle
self._tomo_scan_at_angle(angle, subtomo_number)
def _print_progress(self):
print("\x1b[95mProgress report:")
print(f"Tomo type: ....................... {self.progress['tomo_type']}")
print(f"Projection: ...................... {self.progress['projection']}")
print(f"Total projections expected ....... {self.progress['total_projections']}")
print(f"Angle: ........................... {self.progress['angle']}")
print(f"Current subtomo: ................. {self.progress['subtomo']}")
print(f"Current projection within subtomo: {self.progress['subtomo_projection']}\x1b[0m")
def _tomo_scan_at_angle(self, angle, subtomo_number):
successful = False
error_caught = False
if 0 <= angle < 360.05:
print(f"Starting LamNI scan for angle {angle} in subtomo {subtomo_number}")
self._print_progress()
while not successful:
self._start_beam_check()
if not self.special_angles:
self._current_special_angles = []
if self._current_special_angles:
next_special_angle = self._current_special_angles[0]
if np.isclose(angle, next_special_angle, atol=0.5):
self._current_special_angles.pop(0)
num_repeats = self.special_angle_repeats
else:
num_repeats = 1
try:
start_scan_number = bec.queue.next_scan_number
for i in range(num_repeats):
self._at_each_angle(angle)
error_caught = False
except AlarmBase as exc:
if exc.alarm_type == "TimeoutError":
bec.queue.request_queue_reset()
time.sleep(2)
error_caught = True
else:
num_repeats = 1
try:
@@ -1005,7 +1065,7 @@ class LamNI(LamNIOpticsMixin):
scans = builtins.__dict__.get("scans")
self._current_special_angles = self.special_angles.copy()
if subtomo_start == 1 and start_angle is None:
if self.tomo_type == 1 and subtomo_start == 1 and start_angle is None:
# pylint: disable=undefined-variable
self.tomo_id = self.add_sample_database(
self.sample_name,
@@ -1028,14 +1088,16 @@ class LamNI(LamNIOpticsMixin):
print(f"Counting time <ctime> = {self.tomo_countingtime} s")
print(f"Stepsize microns <step> = {self.tomo_shellstep}")
print(
f"Piezo range (max 80) <microns> = {self.lamni_piezo_range_x}, {self.lamni_piezo_range_y}"
f"Piezo range (max 80) <microns> = {self.lamni_piezo_range_x},"
f" {self.lamni_piezo_range_y}"
)
print(f"Stitching number x,y = {self.lamni_stitch_x}, {self.lamni_stitch_y}")
print(f"Stitching overlap = {self.tomo_stitch_overlap}")
print(f"Circuilar FOV diam <microns> = {self.tomo_circfov}")
print(f"Reconstruction queue name = {self.ptycho_reconstruct_foldername}")
print(
"For information, fov offset is rotating and finding the ROI, manual shift moves rotation center"
"For information, fov offset is rotating and finding the ROI, manual shift moves"
" rotation center"
)
print(f" _tomo_fovx_offset <mm> = {self.align.tomo_fovx_offset}")
print(f" _tomo_fovy_offset <mm> = {self.align.tomo_fovy_offset}")
@@ -1091,7 +1153,7 @@ class LamNI(LamNIOpticsMixin):
with open(ptycho_queue_file, "w") as queue_file:
scans = " ".join([str(scan) for scan in self._current_scan_list])
queue_file.write(f"p.scan_number {scans}\n")
queue_file.write(f"p.check_nextscan_started 1\n")
queue_file.write("p.check_nextscan_started 1\n")
def write_pdf_report(self):
"""create and write the pdf report with the current LamNI settings"""
@@ -1116,17 +1178,25 @@ class LamNI(LamNIOpticsMixin):
f"{'Dataset ID:':<{padding}}{dataset_id:>{padding}}\n",
f"{'Sample Info:':<{padding}}{'Sample Info':>{padding}}\n",
f"{'e-account:':<{padding}}{str(self.client.username):>{padding}}\n",
f"{'Number of projections:':<{padding}}{int(360 / self.tomo_angle_stepsize * 8):>{padding}}\n",
(
f"{'Number of projections:':<{padding}}{int(360 / self.tomo_angle_stepsize * 8):>{padding}}\n"
),
f"{'First scan number:':<{padding}}{self.client.queue.next_scan_number:>{padding}}\n",
f"{'Last scan number approx.:':<{padding}}{self.client.queue.next_scan_number + int(360 / self.tomo_angle_stepsize * 8) + 10:>{padding}}\n",
f"{'Current photon energy:':<{padding}}{dev.mokev.read(cached=True)['value']:>{padding}.4f}\n",
(
f"{'Last scan number approx.:':<{padding}}{self.client.queue.next_scan_number + int(360 / self.tomo_angle_stepsize * 8) + 10:>{padding}}\n"
),
(
f"{'Current photon energy:':<{padding}}{dev.mokev.read(cached=True)['value']:>{padding}.4f}\n"
),
f"{'Exposure time:':<{padding}}{self.tomo_countingtime:>{padding}.2f}\n",
f"{'Fermat spiral step size:':<{padding}}{self.tomo_shellstep:>{padding}.2f}\n",
f"{'Piezo range (FOV sample plane):':<{padding}}{piezo_range:>{padding}}\n",
f"{'Restriction to circular FOV:':<{padding}}{self.tomo_circfov:>{padding}.2f}\n",
f"{'Stitching:':<{padding}}{stitching:>{padding}}\n",
f"{'Number of individual sub-tomograms:':<{padding}}{8:>{padding}}\n",
f"{'Angular step within sub-tomogram:':<{padding}}{self.tomo_angle_stepsize:>{padding}.2f}\n",
(
f"{'Angular step within sub-tomogram:':<{padding}}{self.tomo_angle_stepsize:>{padding}.2f}\n"
),
]
content = "".join(content)
user_target = os.path.expanduser(f"~/Data10/documentation/tomo_scan_ID_{self.tomo_id}.pdf")
@@ -1134,20 +1204,13 @@ class LamNI(LamNIOpticsMixin):
file.write(header)
file.write(content)
subprocess.run(
"xterm /work/sls/spec/local/XOMNY/bin/upload/upload_last_pon.sh &",
shell=True,
"xterm /work/sls/spec/local/XOMNY/bin/upload/upload_last_pon.sh &", shell=True
)
# status = subprocess.run(f"cp /tmp/spec-e20131-specES1.pdf {user_target}", shell=True)
msg = bec.logbook.LogbookMessage()
logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LamNI_logo.png")
msg.add_file(logo_path).add_text("".join(content).replace("\n", "</p><p>")).add_tag(
[
"BEC",
"tomo_parameters",
f"dataset_id_{dataset_id}",
"LamNI",
self.sample_name,
]
["BEC", "tomo_parameters", f"dataset_id_{dataset_id}", "LamNI", self.sample_name]
)
self.client.logbook.send_logbook_message(msg)
@@ -1232,16 +1295,12 @@ class DataDrivenLamNI(LamNI):
def sub_tomo_scan(self):
raise NotImplementedError(
"Cannot run sub_tomo_scan with data-driven LamNI. Please use lamni.tomo_scan(subtomo_start=<START_NUM>) instead."
"Cannot run sub_tomo_scan with data-driven LamNI. Please use"
" lamni.tomo_scan(subtomo_start=<START_NUM>) instead."
)
def _at_each_angle(
self,
angle=None,
stepsize=None,
loptz_pos=None,
manual_shift_x=0,
manual_shift_y=0,
self, angle=None, stepsize=None, loptz_pos=None, manual_shift_x=0, manual_shift_y=0
):
# Do something...
# self.tomo_parameters

View File

@@ -0,0 +1,2 @@
from .cSAXS_beamline import epics_get, epics_put, fshclose, fshopen, fshstatus
from .csaxs_bl_checks import cSAXSBeamlineChecks

View File

@@ -1,10 +1,9 @@
import builtins
from bec_ipython_client.beamline_mixin import BeamlineShowInfo
from rich import box
from rich.table import Table
from bec_client.beamline_mixin import BeamlineShowInfo
class BeamlineInfo(BeamlineShowInfo):
def show(self):

View File

@@ -0,0 +1,122 @@
import builtins
import datetime
import threading
import time
from bec_lib import bec_logger
logger = bec_logger.logger
if builtins.__dict__.get("bec"):
bec = builtins.__dict__.get("bec")
class cSAXSBeamlineChecks:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.check_shutter = True
self.check_light_available = True
self.check_fofb = True
self._check_msgs = []
self._beam_is_okay = True
self._stop_beam_check_event = None
self.beam_check_thread = None
def get_beamline_checks_enabled(self):
print(
f"Shutter: {self.check_shutter}\nFOFB: {self.check_fofb}\nLight available:"
f" {self.check_light_available}"
)
@property
def beamline_checks_enabled(self):
return {
"shutter": self.check_shutter,
"fofb": self.check_fofb,
"light available": self.check_light_available,
}
@beamline_checks_enabled.setter
def beamline_checks_enabled(self, val: bool):
self.check_shutter = val
self.check_light_available = val
self.check_fofb = val
self.get_beamline_checks_enabled()
def _run_beamline_checks(self):
msgs = []
dev = builtins.__dict__.get("dev")
try:
if self.check_shutter:
shutter_val = dev.x12sa_es1_shutter_status.read(cached=True)
if shutter_val["value"].lower() != "open":
self._beam_is_okay = False
msgs.append("Check beam failed: Shutter is closed.")
if self.check_light_available:
machine_status = dev.sls_machine_status.read(cached=True)
if machine_status["value"] not in ["Light Available", "Light-Available"]:
self._beam_is_okay = False
msgs.append("Check beam failed: Light not available.")
if self.check_fofb:
fast_orbit_feedback = dev.sls_fast_orbit_feedback.read(cached=True)
if fast_orbit_feedback["value"] != "running":
self._beam_is_okay = False
msgs.append("Check beam failed: Fast orbit feedback is not running.")
except Exception:
logger.warning("Failed to check beam.")
return msgs
def _check_beam(self):
while not self._stop_beam_check_event.is_set():
self._check_msgs = self._run_beamline_checks()
if not self._beam_is_okay:
self._stop_beam_check_event.set()
time.sleep(1)
def _start_beam_check(self):
self._beam_is_okay = True
self._stop_beam_check_event = threading.Event()
self.beam_check_thread = threading.Thread(target=self._check_beam, daemon=True)
self.beam_check_thread.start()
def _was_beam_okay(self):
self._stop_beam_check_event.set()
self.beam_check_thread.join()
return self._beam_is_okay
def _print_beamline_checks(self):
for msg in self._check_msgs:
logger.warning(msg)
def _wait_for_beamline_checks(self):
self._print_beamline_checks()
try:
msg = bec.logbook.LogbookMessage()
msg.add_text(
"<p><mark class='pen-red'><strong>Beamline checks failed at"
f" {str(datetime.datetime.now())}: {''.join(self._check_msgs)}</strong></mark></p>"
).add_tag(["BEC", "beam_check"])
bec.logbook.send_logbook_message(msg)
except Exception:
logger.warning("Failed to send update to SciLog.")
while True:
self._beam_is_okay = True
self._check_msgs = self._run_beamline_checks()
if self._beam_is_okay:
break
self._print_beamline_checks()
time.sleep(1)
try:
msg = bec.logbook.LogbookMessage()
msg.add_text(
"<p><mark class='pen-red'><strong>Operation resumed at"
f" {str(datetime.datetime.now())}.</strong></mark></p>"
).add_tag(["BEC", "beam_check"])
bec.logbook.send_logbook_message(msg)
except Exception:
logger.warning("Failed to send update to SciLog.")

View File

@@ -0,0 +1 @@
from .flomni import Flomni

View File

@@ -0,0 +1,351 @@
corr_elements = 175
corr_angle[0] = 0.100000
corr_angle[1] = 1.001000
corr_angle[2] = 1.902000
corr_angle[3] = 2.798000
corr_angle[4] = 7.305000
corr_angle[5] = 8.204000
corr_angle[6] = 9.104000
corr_angle[7] = 10.005000
corr_angle[8] = 14.504000
corr_angle[9] = 15.404000
corr_angle[10] = 16.304000
corr_angle[11] = 17.204000
corr_angle[12] = 21.704000
corr_angle[13] = 22.604000
corr_angle[14] = 23.504000
corr_angle[15] = 24.404000
corr_angle[16] = 28.904000
corr_angle[17] = 29.804000
corr_angle[18] = 30.704000
corr_angle[19] = 31.604000
corr_angle[20] = 36.104000
corr_angle[21] = 37.004000
corr_angle[22] = 37.904000
corr_angle[23] = 38.804000
corr_angle[24] = 43.305000
corr_angle[25] = 44.205000
corr_angle[26] = 45.104000
corr_angle[27] = 46.005000
corr_angle[28] = 50.504000
corr_angle[29] = 51.404000
corr_angle[30] = 52.305000
corr_angle[31] = 53.205000
corr_angle[32] = 57.705000
corr_angle[33] = 58.605000
corr_angle[34] = 59.505000
corr_angle[35] = 60.405000
corr_angle[36] = 64.904000
corr_angle[37] = 65.804000
corr_angle[38] = 66.704000
corr_angle[39] = 67.604000
corr_angle[40] = 72.104000
corr_angle[41] = 73.004000
corr_angle[42] = 73.904000
corr_angle[43] = 74.804000
corr_angle[44] = 79.304000
corr_angle[45] = 80.204000
corr_angle[46] = 81.104000
corr_angle[47] = 82.005000
corr_angle[48] = 86.505000
corr_angle[49] = 87.404000
corr_angle[50] = 88.304000
corr_angle[51] = 89.205000
corr_angle[52] = 93.704000
corr_angle[53] = 94.604000
corr_angle[54] = 95.505000
corr_angle[55] = 96.404000
corr_angle[56] = 100.904000
corr_angle[57] = 101.804000
corr_angle[58] = 102.704000
corr_angle[59] = 103.604000
corr_angle[60] = 108.104000
corr_angle[61] = 109.004000
corr_angle[62] = 109.904000
corr_angle[63] = 110.804000
corr_angle[64] = 115.304000
corr_angle[65] = 116.204000
corr_angle[66] = 117.104000
corr_angle[67] = 118.004000
corr_angle[68] = 122.504000
corr_angle[69] = 123.404000
corr_angle[70] = 124.304000
corr_angle[71] = 125.204000
corr_angle[72] = 129.704000
corr_angle[73] = 130.604000
corr_angle[74] = 131.504000
corr_angle[75] = 132.404000
corr_angle[76] = 136.904000
corr_angle[77] = 137.804000
corr_angle[78] = 138.701000
corr_angle[79] = 139.604000
corr_angle[80] = 144.104000
corr_angle[81] = 145.004000
corr_angle[82] = 145.904000
corr_angle[83] = 146.804000
corr_angle[84] = 151.304000
corr_angle[85] = 152.204000
corr_angle[86] = 153.104000
corr_angle[87] = 154.004000
corr_angle[88] = 158.504000
corr_angle[89] = 159.404000
corr_angle[90] = 160.304000
corr_angle[91] = 161.204000
corr_angle[92] = 165.704000
corr_angle[93] = 166.604000
corr_angle[94] = 167.504000
corr_angle[95] = 168.404000
corr_angle[96] = 172.904000
corr_angle[97] = 173.805000
corr_angle[98] = 174.704000
corr_angle[99] = 180.104000
corr_angle[100] = 183.704000
corr_angle[101] = 184.603000
corr_angle[102] = 185.504000
corr_angle[103] = 190.904000
corr_angle[104] = 191.805000
corr_angle[105] = 192.704000
corr_angle[106] = 198.104000
corr_angle[107] = 199.004000
corr_angle[108] = 199.904000
corr_angle[109] = 205.304000
corr_angle[110] = 206.204000
corr_angle[111] = 207.104000
corr_angle[112] = 212.504000
corr_angle[113] = 213.404000
corr_angle[114] = 214.304000
corr_angle[115] = 219.704000
corr_angle[116] = 220.604000
corr_angle[117] = 221.504000
corr_angle[118] = 226.904000
corr_angle[119] = 227.804000
corr_angle[120] = 228.704000
corr_angle[121] = 234.104000
corr_angle[122] = 235.004000
corr_angle[123] = 235.904000
corr_angle[124] = 241.304000
corr_angle[125] = 242.204000
corr_angle[126] = 243.104000
corr_angle[127] = 248.504000
corr_angle[128] = 249.404000
corr_angle[129] = 250.304000
corr_angle[130] = 255.704000
corr_angle[131] = 256.604000
corr_angle[132] = 257.504000
corr_angle[133] = 262.904000
corr_angle[134] = 263.804000
corr_angle[135] = 264.704000
corr_angle[136] = 270.104000
corr_angle[137] = 271.004000
corr_angle[138] = 271.904000
corr_angle[139] = 277.304000
corr_angle[140] = 278.205000
corr_angle[141] = 279.104000
corr_angle[142] = 284.504000
corr_angle[143] = 285.405000
corr_angle[144] = 286.304000
corr_angle[145] = 291.703000
corr_angle[146] = 292.604000
corr_angle[147] = 293.504000
corr_angle[148] = 298.904000
corr_angle[149] = 299.804000
corr_angle[150] = 300.704000
corr_angle[151] = 306.104000
corr_angle[152] = 307.004000
corr_angle[153] = 307.904000
corr_angle[154] = 313.304000
corr_angle[155] = 314.204000
corr_angle[156] = 315.104000
corr_angle[157] = 320.504000
corr_angle[158] = 321.404000
corr_angle[159] = 322.304000
corr_angle[160] = 327.704000
corr_angle[161] = 328.605000
corr_angle[162] = 329.504000
corr_angle[163] = 334.904000
corr_angle[164] = 335.804000
corr_angle[165] = 336.705000
corr_angle[166] = 342.104000
corr_angle[167] = 343.004000
corr_angle[168] = 343.904000
corr_angle[169] = 349.304000
corr_angle[170] = 350.204000
corr_angle[171] = 351.104000
corr_angle[172] = 356.504000
corr_angle[173] = 357.404000
corr_angle[174] = 358.304000
corr_pos[0] = 0.012330
corr_pos[1] = 0.024870
corr_pos[2] = 0.037262
corr_pos[3] = 0.049438
corr_pos[4] = 0.108462
corr_pos[5] = 0.119791
corr_pos[6] = 0.130986
corr_pos[7] = 0.142044
corr_pos[8] = 0.195045
corr_pos[9] = 0.205834
corr_pos[10] = 0.216589
corr_pos[11] = 0.226402
corr_pos[12] = 0.271931
corr_pos[13] = 0.281684
corr_pos[14] = 0.290769
corr_pos[15] = 0.299392
corr_pos[16] = 0.337986
corr_pos[17] = 0.345178
corr_pos[18] = 0.352253
corr_pos[19] = 0.358528
corr_pos[20] = 0.379520
corr_pos[21] = 0.382947
corr_pos[22] = 0.386110
corr_pos[23] = 0.388887
corr_pos[24] = 0.395645
corr_pos[25] = 0.396154
corr_pos[26] = 0.396679
corr_pos[27] = 0.396367
corr_pos[28] = 0.392400
corr_pos[29] = 0.392530
corr_pos[30] = 0.391826
corr_pos[31] = 0.391325
corr_pos[32] = 0.379188
corr_pos[33] = 0.376449
corr_pos[34] = 0.373195
corr_pos[35] = 0.369588
corr_pos[36] = 0.343021
corr_pos[37] = 0.336754
corr_pos[38] = 0.330310
corr_pos[39] = 0.323447
corr_pos[40] = 0.281171
corr_pos[41] = 0.272735
corr_pos[42] = 0.263888
corr_pos[43] = 0.254469
corr_pos[44] = 0.203024
corr_pos[45] = 0.192980
corr_pos[46] = 0.182359
corr_pos[47] = 0.170780
corr_pos[48] = 0.111580
corr_pos[49] = 0.101222
corr_pos[50] = 0.090051
corr_pos[51] = 0.077918
corr_pos[52] = 0.015225
corr_pos[53] = 0.003876
corr_pos[54] = -0.007756
corr_pos[55] = -0.020383
corr_pos[56] = -0.084910
corr_pos[57] = -0.096848
corr_pos[58] = -0.108732
corr_pos[59] = -0.121432
corr_pos[60] = -0.184349
corr_pos[61] = -0.195904
corr_pos[62] = -0.207113
corr_pos[63] = -0.219319
corr_pos[64] = -0.274799
corr_pos[65] = -0.284942
corr_pos[66] = -0.294809
corr_pos[67] = -0.305437
corr_pos[68] = -0.352114
corr_pos[69] = -0.359689
corr_pos[70] = -0.367451
corr_pos[71] = -0.375629
corr_pos[72] = -0.404347
corr_pos[73] = -0.407683
corr_pos[74] = -0.410857
corr_pos[75] = -0.414904
corr_pos[76] = -0.424625
corr_pos[77] = -0.424357
corr_pos[78] = -0.424411
corr_pos[79] = -0.424615
corr_pos[80] = -0.421392
corr_pos[81] = -0.419977
corr_pos[82] = -0.417860
corr_pos[83] = -0.416400
corr_pos[84] = -0.398108
corr_pos[85] = -0.392613
corr_pos[86] = -0.386455
corr_pos[87] = -0.380338
corr_pos[88] = -0.341288
corr_pos[89] = -0.332298
corr_pos[90] = -0.322767
corr_pos[91] = -0.311630
corr_pos[92] = -0.256798
corr_pos[93] = -0.244808
corr_pos[94] = -0.232932
corr_pos[95] = -0.219785
corr_pos[96] = -0.158685
corr_pos[97] = -0.146198
corr_pos[98] = -0.130743
corr_pos[99] = -0.054066
corr_pos[100] = -0.001498
corr_pos[101] = 0.012010
corr_pos[102] = 0.025195
corr_pos[103] = 0.094982
corr_pos[104] = 0.109235
corr_pos[105] = 0.120813
corr_pos[106] = 0.179893
corr_pos[107] = 0.192147
corr_pos[108] = 0.201902
corr_pos[109] = 0.245092
corr_pos[110] = 0.250501
corr_pos[111] = 0.255536
corr_pos[112] = 0.280598
corr_pos[113] = 0.280673
corr_pos[114] = 0.282529
corr_pos[115] = 0.293286
corr_pos[116] = 0.292713
corr_pos[117] = 0.291123
corr_pos[118] = 0.288721
corr_pos[119] = 0.288260
corr_pos[120] = 0.286480
corr_pos[121] = 0.271630
corr_pos[122] = 0.268004
corr_pos[123] = 0.265418
corr_pos[124] = 0.239187
corr_pos[125] = 0.233224
corr_pos[126] = 0.226652
corr_pos[127] = 0.189034
corr_pos[128] = 0.180302
corr_pos[129] = 0.170931
corr_pos[130] = 0.125760
corr_pos[131] = 0.116433
corr_pos[132] = 0.106845
corr_pos[133] = 0.057551
corr_pos[134] = 0.048531
corr_pos[135] = 0.038276
corr_pos[136] = -0.012292
corr_pos[137] = -0.021223
corr_pos[138] = -0.030046
corr_pos[139] = -0.077647
corr_pos[140] = -0.085863
corr_pos[141] = -0.093816
corr_pos[142] = -0.138532
corr_pos[143] = -0.145584
corr_pos[144] = -0.152699
corr_pos[145] = -0.192594
corr_pos[146] = -0.200980
corr_pos[147] = -0.208816
corr_pos[148] = -0.241983
corr_pos[149] = -0.248319
corr_pos[150] = -0.253789
corr_pos[151] = -0.276869
corr_pos[152] = -0.279240
corr_pos[153] = -0.281538
corr_pos[154] = -0.294317
corr_pos[155] = -0.295586
corr_pos[156] = -0.296029
corr_pos[157] = -0.297443
corr_pos[158] = -0.297185
corr_pos[159] = -0.295946
corr_pos[160] = -0.284463
corr_pos[161] = -0.282598
corr_pos[162] = -0.281260
corr_pos[163] = -0.256509
corr_pos[164] = -0.250326
corr_pos[165] = -0.244372
corr_pos[166] = -0.204043
corr_pos[167] = -0.196147
corr_pos[168] = -0.187928
corr_pos[169] = -0.131831
corr_pos[170] = -0.121351
corr_pos[171] = -0.110548
corr_pos[172] = -0.038951
corr_pos[173] = -0.025887
corr_pos[174] = -0.012501

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,177 @@
import time
from rich import box
from rich.console import Console
from rich.table import Table
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
class FlomniOpticsMixin:
@staticmethod
def _get_user_param_safe(device, var):
param = dev[device].user_parameter
if not param or param.get(var) is None:
raise ValueError(f"Device {device} has no user parameter definition for {var}.")
return param.get(var)
def feye_out(self):
fshclose()
self.foptics_in()
feyex_out = self._get_user_param_safe("feyex", "out")
umv(dev.feyex, feyex_out)
epics_put("XOMNYI-XEYE-ACQ:0", 2)
# move rotation stage to zero to avoid problems with wires
umv(dev.fsamroy, 0)
# umv(dev.fttrx1, 9.2)
def feye_in(self):
bec.queue.next_dataset_number += 1
# umv(dev.fttrx1, -17)
feyex_in = self._get_user_param_safe("feyex", "in")
feyey_in = self._get_user_param_safe("feyey", "in")
umv(dev.feyex, feyex_in, dev.feyey, feyey_in)
#self.align.update_frame()
def _ffzp_in(self):
foptx_in = self._get_user_param_safe("foptx", "in")
fopty_in = self._get_user_param_safe("fopty", "in")
umv(dev.foptx, foptx_in)
umv(
dev.fopty, fopty_in
) # for 7.2567 keV and 150 mu, 60 nm fzp, loptz 83.6000 for propagation 1.4 mm
def ffzp_in(self):
"""
move in the flomni zone plate.
This will disable rt feedback, move the FZP and re-enabled the feedback.
"""
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_disable()
self._ffzp_in()
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_enable_with_reset()
def foptics_in(self):
"""
Move in the flomni optics, including the FZP and the OSA.
"""
self.ffzp_in()
self.fosa_in()
def foptics_out(self):
"""Move out the flomni optics"""
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_disable()
self.fosa_out()
fopty_out = self._get_user_param_safe("fopty", "out")
umv(dev.fopty, fopty_out)
if "rtx" in dev and dev.rtx.enabled:
time.sleep(1)
dev.rtx.controller.feedback_enable_with_reset()
def fosa_in(self):
# 6.2 keV, 170 um FZP
# umv(dev.losax, -1.4450000, dev.losay, -0.1800)
# umv(dev.losaz, -1)
# 6.7, 170
# umv(dev.losax, -1.4850, dev.losay, -0.1930)
# umv(dev.losaz, 1.0000)
# 7.2, 150
fosax_in = self._get_user_param_safe("fosax", "in")
fosay_in = self._get_user_param_safe("fosay", "in")
fosaz_in = self._get_user_param_safe("fosaz", "in")
dev.fosax.limits = [fosax_in - 0.1, fosax_in + 0.1]
dev.fosay.limits = [fosay_in - 0.1, fosay_in + 0.1]
dev.fosaz.limits = [fosaz_in - 0.1, fosaz_in + 0.1]
umv(dev.fosax, fosax_in, dev.fosay, fosay_in)
umv(dev.fosaz, fosaz_in)
# 11 kev
# umv(dev.losax, -1.161000, dev.losay, -0.196)
# umv(dev.losaz, 1.0000)
def fosa_out(self):
self.ensure_fheater_up()
curtain_is_triggered = dev.foptz.controller.fosaz_light_curtain_is_triggered()
if not curtain_is_triggered:
fosaz_out = self._get_user_param_safe("fosaz", "out")
dev.fosaz.limits = [fosaz_out - 0.1, fosaz_out + 0.1]
umv(dev.fosaz, fosaz_out)
fosax_out = self._get_user_param_safe("fosax", "out")
dev.fosax.limits = [fosax_out - 0.1, fosax_out + 0.1]
umv(dev.fosax, fosax_out)
def ffzp_info(self, mokev_val=-1):
if mokev_val == -1:
try:
mokev_val = dev.mokev.readback.get()
except:
print(
"Device mokev does not exist. You can specify the energy in keV as an argument instead."
)
return
foptz_val = dev.foptz.readback.get()
distance = -foptz_val + 43.15 + 36.7
print(f"\nThe sample is in a distance of \033[1m{distance:.1f} mm\033[0m from the FZP.\n")
print(f"At the current energy of {mokev_val:.4f} keV we have following options:\n")
diameters = [80e-6, 100e-6, 120e-6, 150e-6, 170e-6, 200e-6, 220e-6, 250e-6]
console = Console()
table = Table(title="Outermost zone width \033[1m60 nm\033[0m", box=box.SQUARE)
table.add_column("Diameter", justify="center")
table.add_column("Focal distance", justify="center")
table.add_column("Current beam size", justify="center")
wavelength = 1.2398e-9 / mokev_val
for diameter in diameters:
outermost_zonewidth = 60e-9
focal_distance = diameter * outermost_zonewidth / wavelength * 1000
beam_size = -diameter / (focal_distance * 1000) * (focal_distance - distance) * 1e9
table.add_row(
f"{diameter*1e6:.2f} microns",
f"{focal_distance:.2f} mm",
f"{beam_size:.2f} microns",
)
console.print(table)
diameters = [150e-6, 250e-6]
console = Console()
table = Table(title="Outermost zone width \033[1m30 nm\033[0m", box=box.SQUARE)
table.add_column("Diameter", justify="center")
table.add_column("Focal distance", justify="center")
table.add_column("Current beam size", justify="center")
wavelength = 1.2398e-9 / mokev_val
for diameter in diameters:
outermost_zonewidth = 30e-9
focal_distance = diameter * outermost_zonewidth / wavelength * 1000
beam_size = -diameter / (focal_distance * 1000) * (focal_distance - distance) * 1e9
table.add_row(
f"{diameter*1e6:.2f} microns",
f"{focal_distance:.2f} mm",
f"{beam_size:.2f} microns",
)
console.print(table)
fosaz_val = dev.fosaz.readback.get()
print("\nOSA Information:")
print(f" Current fosaz {fosaz_val:.1f}")
print(
f" The OSA will collide with a normal OMNY pin at fosaz \033[1m{(33-fosaz_val):.1f}\033[0m"
)
print(f" Remaining space: \033[1m{-fosaz_val+(33-foptz_val):.1f}\033[0m")

View File

@@ -0,0 +1,225 @@
import builtins
from bec_widgets.cli.client import BECDockArea
# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
if builtins.__dict__.get("bec") is not None:
bec = builtins.__dict__.get("bec")
dev = builtins.__dict__.get("dev")
umv = builtins.__dict__.get("umv")
umvr = builtins.__dict__.get("umvr")
class flomniGuiToolsError(Exception):
pass
class flomniGuiTools:
def __init__(self):
self.text_box = None
self.progressbar = None
def set_client(self, client):
self.client = client
self.gui = self.client.gui
def flomnigui_show_gui(self):
if "flomni" in self.gui.windows:
self.gui.flomni.show()
else:
self.gui.new("flomni")
def flomnigui_stop_gui(self):
self.gui.flomni.hide()
def flomnigui_raise(self):
self.gui.flomni.raise_window()
# def flomnigui_show_xeyealign(self):
# self.flomnigui_show_gui()
# if self.xeyegui is None:
# self.flomnigui_remove_all_docks()
# self.xeyegui = self.gui.flomni.new("xeyegui").new("XRayEye")
# # start live
# if not dev.cam_xeye.live_mode:
# dev.cam_xeye.live_mode = True
def flomnigui_show_xeyealign(self):
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("xeyegui"):
self.flomnigui_remove_all_docks()
self.xeyegui = self.gui.flomni.new("xeyegui").new("XRayEye")
# start live
if not dev.cam_xeye.live_mode:
dev.cam_xeye.live_mode = True
def _flomnigui_check_attribute_not_exists(self, attribute_name):
if hasattr(self.gui,"flomni"):
if hasattr(self.gui.flomni,attribute_name):
return False
return True
def flomnigui_show_cameras(self):
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("camera_gripper") or self._flomnigui_check_attribute_not_exists("camera_overview"):
self.flomnigui_remove_all_docks()
camera_gripper_image = self.gui.flomni.new("camera_gripper").new("Image")
if self._flomnicam_check_device_exists(dev.cam_flomni_gripper):
camera_gripper_image.image(("cam_flomni_gripper", "preview"))
camera_gripper_image.lock_aspect_ratio = True
camera_gripper_image.enable_fps_monitor = True
camera_gripper_image.enable_toolbar = False
camera_gripper_image.outer_axes = False
camera_gripper_image.inner_axes = False
dev.cam_flomni_gripper.start_live_mode()
else:
print("Cannot open camera_gripper. Device does not exist.")
camera_overview_image = self.gui.flomni.new("camera_overview").new("Image")
if self._flomnicam_check_device_exists(dev.cam_flomni_overview):
camera_overview_image.image(("cam_flomni_overview", "preview"))
camera_overview_image.lock_aspect_ratio = True
camera_overview_image.enable_fps_monitor = True
camera_overview_image.enable_toolbar = False
camera_overview_image.outer_axes = False
camera_overview_image.inner_axes = False
dev.cam_flomni_overview.start_live_mode()
else:
print("Cannot open camera_overview. Device does not exist.")
def flomnigui_remove_all_docks(self):
#dev.cam_flomni_overview.stop_live_mode()
#dev.cam_flomni_gripper.stop_live_mode()
#dev.cam_xeye.live_mode = False
self.gui.flomni.delete_all()
self.progressbar = None
self.text_box = None
def flomnigui_idle(self):
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("idle_text_box"):
self.flomnigui_remove_all_docks()
idle_text_box = self.gui.flomni.new("idle_textbox").new("TextBox")
text = (
"<pre>"
+ " ,---.,--. ,-----. ,--. ,--.,--. ,--.,--. \n"
+ "/ .-'| |' .-. '| `.' || ,'.| || | \n"
+ "| `-,| || | | || |'.'| || |' ' || | \n"
+ "| .-'| |' '-' '| | | || | ` || | \n"
+ "`--' `--' `-----' `--' `--'`--' `--'`--' \n"
+ "</pre>"
)
idle_text_box.set_html_text(text)
def flomnigui_docs(self, filename: str | None = None):
import csaxs_bec
from pathlib import Path
print("The general flOMNI documentation is at \nhttps://sls-csaxs.readthedocs.io/en/latest/user/ptychography/flomni.html#user-ptychography-flomni")
csaxs_bec_basepath = Path(csaxs_bec.__file__).parent
docs_folder = (
csaxs_bec_basepath /
"bec_ipython_client" / "plugins" / "flomni" / "docs"
)
if not docs_folder.is_dir():
raise NotADirectoryError(f"Docs folder not found: {docs_folder}")
pdfs = sorted(docs_folder.glob("*.pdf"))
if not pdfs:
raise FileNotFoundError(f"No PDF files found in {docs_folder}")
# --- Resolve PDF ------------------------------------------------------
if filename is not None:
pdf_file = docs_folder / filename
if not pdf_file.exists():
raise FileNotFoundError(f"Requested file not found: {filename}")
else:
print("\nAvailable flOMNI documentation PDFs:\n")
for i, pdf in enumerate(pdfs, start=1):
print(f" {i:2d}) {pdf.name}")
print()
while True:
try:
choice = int(input(f"Select a file (1{len(pdfs)}): "))
if 1 <= choice <= len(pdfs):
pdf_file = pdfs[choice - 1]
break
print(f"Enter a number between 1 and {len(pdfs)}.")
except ValueError:
print("Invalid input. Please enter a number.")
# --- GUI handling (active existence check) ----------------------------
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("PdfViewerWidget"):
self.flomnigui_remove_all_docks()
self.pdf_viewer = self.gui.flomni.new(widget="PdfViewerWidget")
# --- Load PDF ---------------------------------------------------------
self.pdf_viewer.PdfViewerWidget.load_pdf(str(pdf_file.resolve()))
print(f"\nLoaded: {pdf_file.name}\n")
def _flomnicam_check_device_exists(self, device):
try:
device
except:
return False
else:
return True
def flomnigui_show_progress(self):
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("progressbar"):
self.flomnigui_remove_all_docks()
# Add a new dock with a RingProgressBar widget
self.progressbar = self.gui.flomni.new("progressbar").new("RingProgressBar")
# Customize the size of the progress ring
self.progressbar.set_line_widths(20)
# Disable automatic updates and manually set the self.progressbar value
self.progressbar.enable_auto_updates(False)
# Set precision for the self.progressbar display
self.progressbar.set_precision(1) # Display self.progressbar with one decimal places
# Setting multiple rigns with different values
self.progressbar.set_number_of_bars(3)
self.progressbar.rings[0].set_update("manual")
self.progressbar.rings[1].set_update("manual")
self.progressbar.rings[2].set_update("scan")
# Set the values of the rings to 50, 75, and 25 from outer to inner ring
# self.progressbar.set_value([50, 75])
# Add a new dock with a TextBox widget
self.text_box = self.gui.flomni.new(name="progress_text").new("TextBox")
self._flomnigui_update_progress()
def _flomnigui_update_progress(self):
if self.progressbar is not None:
progress = self.progress["projection"] / self.progress["total_projections"] * 100
subtomo_progress = (
self.progress["subtomo_projection"]
/ self.progress["subtomo_total_projections"]
* 100
)
self.progressbar.set_value([progress, subtomo_progress, 0])
if self.text_box is not None:
text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}"
self.text_box.set_plain_text(text)
if __name__ == "__main__":
from bec_lib.client import BECClient
from bec_widgets.cli.client_utils import BECGuiClient
client = BECClient()
client.start()
client.gui = BECGuiClient()
flomni_gui = flomniGuiTools(client)
flomni_gui.flomnigui_show_gui()
flomni_gui.flomnigui_show_progress()

View File

@@ -0,0 +1,277 @@
from __future__ import annotations
import builtins
import os
import time
from typing import TYPE_CHECKING
from bec_lib import bec_logger
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
logger = bec_logger.logger
# import builtins to avoid linter errors
bec = builtins.__dict__.get("bec")
dev = builtins.__dict__.get("dev")
umv = builtins.__dict__.get("umv")
umvr = builtins.__dict__.get("umvr")
if TYPE_CHECKING:
from bec_ipython_client.plugins.flomni import Flomni
class XrayEyeAlign:
# pixel calibration, multiply to get mm
labview=False
PIXEL_CALIBRATION = 0.1 / 113 # .2 with binning
def __init__(self, client, flomni: Flomni) -> None:
self.client = client
self.flomni = flomni
self.device_manager = client.device_manager
self.scans = client.scans
self.alignment_values = {}
self.flomni.reset_correction()
self.flomni.reset_tomo_alignment_fit()
def _reset_init_values(self):
self.shift_xy = [0, 0]
self._xray_fov_xy = [0, 0]
def save_frame(self):
epics_put("XOMNYI-XEYE-SAVFRAME:0", 1)
def update_frame(self,keep_shutter_open=False):
if self.labview:
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
if not self.labview:
self.flomni.flomnigui_show_xeyealign()
if not dev.cam_xeye.live_mode:
dev.cam_xeye.live_mode = True
epics_put("XOMNYI-XEYE-ACQ:0", 1)
if self.labview:
# wait for start live
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
time.sleep(0.5)
print("waiting for live view to start...")
fshopen()
if self.labview:
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
print("waiting for new frame...")
time.sleep(0.5)
time.sleep(0.5)
# stop live view
if not keep_shutter_open:
epics_put("XOMNYI-XEYE-ACQ:0", 0)
time.sleep(0.1)
fshclose()
print("got new frame")
else:
print("Staying in live view, shutter is and remains open!")
def tomo_rotate(self, val: float):
# pylint: disable=undefined-variable
umv(self.device_manager.devices.fsamroy, val)
def get_tomo_angle(self):
return self.device_manager.devices.fsamroy.readback.get()
def update_fov(self, k: int):
self._xray_fov_xy[0] = max(epics_get(f"XOMNYI-XEYE-XWIDTH_X:{k}"), self._xray_fov_xy[0])
self._xray_fov_xy[1] = max(0, self._xray_fov_xy[0])
@property
def movement_buttons_enabled(self):
return [epics_get("XOMNYI-XEYE-ENAMVX:0"), epics_get("XOMNYI-XEYE-ENAMVY:0")]
@movement_buttons_enabled.setter
def movement_buttons_enabled(self, enabled: bool):
enabled = int(enabled)
epics_put("XOMNYI-XEYE-ENAMVX:0", enabled)
epics_put("XOMNYI-XEYE-ENAMVY:0", enabled)
def send_message(self, msg: str):
epics_put("XOMNYI-XEYE-MESSAGE:0.DESC", msg)
def align(self,keep_shutter_open=False):
if not keep_shutter_open:
print("This routine can be called with paramter keep_shutter_open=True to keep the shutter always open")
self.send_message("Getting things ready. Please wait...")
#potential unresolved movement requests to zero
epics_put("XOMNYI-XEYE-MVX:0", 0)
epics_put("XOMNYI-XEYE-MVY:0", 0)
# reset shift xy and fov params
self._reset_init_values()
self.flomni.lights_off()
self.flomni.flomnigui_show_xeyealign()
self.flomni.flomnigui_raise()
self.tomo_rotate(0)
epics_put("XOMNYI-XEYE-ANGLE:0", 0)
self.flomni.feye_in()
self.flomni.laser_tracker_on()
self.flomni.feedback_enable_with_reset()
# disable movement buttons
self.movement_buttons_enabled = False
sample_name = self.flomni.sample_get_name(0)
epics_put("XOMNYI-XEYE-SAMPLENAME:0.DESC", sample_name)
# this makes sure we are in a defined state
self.flomni.feedback_disable()
epics_put("XOMNYI-XEYE-PIXELSIZE:0", self.PIXEL_CALIBRATION)
self.flomni.fosa_out()
fsamx_in = self.flomni._get_user_param_safe("fsamx", "in")
umv(dev.fsamx, fsamx_in - 0.25)
self.flomni.ffzp_in()
self.update_frame(keep_shutter_open)
# enable submit buttons
self.movement_buttons_enabled = False
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
epics_put("XOMNYI-XEYE-STEP:0", 0)
self.send_message("Submit center value of FZP.")
k = 0
while True:
if epics_get("XOMNYI-XEYE-SUBMIT:0") == 1:
val_x = epics_get(f"XOMNYI-XEYE-XVAL_X:{k}") / 2 * self.PIXEL_CALIBRATION # in mm
self.alignment_values[k] = val_x
print(f"Clicked position {k}: x {self.alignment_values[k]}")
rtx_position = dev.rtx.readback.get() / 1000
print(f"Current rtx position {rtx_position}")
self.alignment_values[k] -= rtx_position
print(f"Corrected position {k}: x {self.alignment_values[k]}")
if k == 0: # received center value of FZP
self.send_message("please wait ...")
self.movement_buttons_enabled = False
epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button
self.flomni.feedback_disable()
fsamx_in = self.flomni._get_user_param_safe("fsamx", "in")
umv(dev.fsamx, fsamx_in)
self.flomni.foptics_out()
self.flomni.feedback_disable()
umv(dev.fsamx, fsamx_in - 0.25)
if self.labview:
self.update_frame(keep_shutter_open)
epics_put("XOMNYI-XEYE-RECBG:0", 1)
while epics_get("XOMNYI-XEYE-RECBG:0") == 1:
time.sleep(0.5)
print("waiting for background frame...")
umv(dev.fsamx, fsamx_in)
time.sleep(0.5)
self.flomni.feedback_enable_with_reset()
self.update_frame(keep_shutter_open)
self.send_message("Adjust sample height and submit center")
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
self.movement_buttons_enabled = True
elif 1 <= k < 5: # received sample center value at samroy 0 ... 315
self.send_message("please wait ...")
epics_put("XOMNYI-XEYE-SUBMIT:0", -1)
self.movement_buttons_enabled = False
umv(dev.rtx, 0)
self.tomo_rotate(k * 45)
epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle())
self.update_frame(keep_shutter_open)
self.send_message("Submit sample center")
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
epics_put("XOMNYI-XEYE-ENAMVX:0", 1)
self.update_fov(k)
elif k == 5: # received sample center value at samroy 270 and done
self.send_message("done...")
epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button
self.movement_buttons_enabled = False
self.update_fov(k)
break
k += 1
epics_put("XOMNYI-XEYE-STEP:0", k)
_xrayeyalignmvx = epics_get("XOMNYI-XEYE-MVX:0")
if _xrayeyalignmvx != 0:
umvr(dev.rtx, _xrayeyalignmvx)
print(f"Current rtx position {dev.rtx.readback.get() / 1000}")
epics_put("XOMNYI-XEYE-MVX:0", 0)
if k > 0:
epics_put(f"XOMNYI-XEYE-STAGEPOSX:{k}", dev.rtx.readback.get() / 1000)
time.sleep(3)
self.update_frame(keep_shutter_open)
if k < 2:
# allow movements, store movements to calculate center
_xrayeyalignmvy = epics_get("XOMNYI-XEYE-MVY:0")
if _xrayeyalignmvy != 0:
self.flomni.feedback_disable()
umvr(dev.fsamy, _xrayeyalignmvy / 1000)
time.sleep(2)
epics_put("XOMNYI-XEYE-MVY:0", 0)
self.flomni.feedback_enable_with_reset()
self.update_frame(keep_shutter_open)
time.sleep(0.2)
self.write_output()
fovx = self._xray_fov_xy[0] * self.PIXEL_CALIBRATION * 1000 / 2
fovy = self._xray_fov_xy[1] * self.PIXEL_CALIBRATION * 1000 / 2
self.tomo_rotate(0)
umv(dev.rtx, 0)
# free camera
if self.labview:
epics_put("XOMNYI-XEYE-ACQ:0", 2)
if keep_shutter_open and not self.labview:
if self.flomni.OMNYTools.yesno("Close the shutter now?","y"):
fshclose()
epics_put("XOMNYI-XEYE-ACQ:0", 0)
if not self.labview:
self.flomni.flomnigui_idle()
print(
f"The largest field of view from the xrayeyealign was \nfovx = {fovx:.0f} microns, fovy"
f" = {fovy:.0f} microns"
)
print("Use the matlab routine to FIT the current alignment...")
print("Then LOAD ALIGNMENT PARAMETERS by running flomni.read_alignment_offset()\n")
def write_output(self):
file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues")
if not os.path.exists(file):
os.makedirs(os.path.dirname(file), exist_ok=True)
with open(file, "w") as alignment_values_file:
alignment_values_file.write("angle\thorizontal\n")
for k in range(1, 6):
fovx_offset = self.alignment_values[0] - self.alignment_values[k]
print(f"Writing to file new alignment: number {k}, value x {fovx_offset}")
alignment_values_file.write(f"{(k-1)*45}\t{fovx_offset*1000}\n")

View File

@@ -0,0 +1 @@
from .omny import OMNY

View File

@@ -0,0 +1,715 @@
corr_elements = 357
corr_angle[0] = 0.097400
corr_angle[1] = 0.603500
corr_angle[2] = 1.134200
corr_angle[3] = 1.625000
corr_angle[4] = 2.162200
corr_angle[5] = 2.700100
corr_angle[6] = 3.191600
corr_angle[7] = 3.714300
corr_angle[8] = 4.223200
corr_angle[9] = 4.730900
corr_angle[10] = 5.253300
corr_angle[11] = 5.743300
corr_angle[12] = 6.279200
corr_angle[13] = 6.782900
corr_angle[14] = 7.301200
corr_angle[15] = 7.808100
corr_angle[16] = 8.325300
corr_angle[17] = 8.859400
corr_angle[18] = 9.359400
corr_angle[19] = 9.887900
corr_angle[20] = 10.395400
corr_angle[21] = 10.930700
corr_angle[22] = 11.415400
corr_angle[23] = 11.928900
corr_angle[24] = 12.456900
corr_angle[25] = 12.955900
corr_angle[26] = 13.479000
corr_angle[27] = 13.982700
corr_angle[28] = 14.500900
corr_angle[29] = 15.016200
corr_angle[30] = 15.528000
corr_angle[31] = 16.053800
corr_angle[32] = 16.562800
corr_angle[33] = 17.076600
corr_angle[34] = 17.592400
corr_angle[35] = 18.094100
corr_angle[36] = 18.623800
corr_angle[37] = 19.118400
corr_angle[38] = 19.655000
corr_angle[39] = 20.143900
corr_angle[40] = 20.672200
corr_angle[41] = 21.171500
corr_angle[42] = 21.696300
corr_angle[43] = 22.215900
corr_angle[44] = 22.728900
corr_angle[45] = 23.233100
corr_angle[46] = 23.760000
corr_angle[47] = 24.267700
corr_angle[48] = 24.792100
corr_angle[49] = 25.287900
corr_angle[50] = 25.824600
corr_angle[51] = 26.321900
corr_angle[52] = 26.842600
corr_angle[53] = 27.337200
corr_angle[54] = 27.873500
corr_angle[55] = 28.373900
corr_angle[56] = 28.895900
corr_angle[57] = 29.404900
corr_angle[58] = 29.926700
corr_angle[59] = 30.439300
corr_angle[60] = 30.963700
corr_angle[61] = 31.460000
corr_angle[62] = 31.989500
corr_angle[63] = 32.489700
corr_angle[64] = 33.026400
corr_angle[65] = 33.516500
corr_angle[66] = 34.044000
corr_angle[67] = 34.547700
corr_angle[68] = 35.070800
corr_angle[69] = 35.578400
corr_angle[70] = 36.103200
corr_angle[71] = 36.603200
corr_angle[72] = 37.128200
corr_angle[73] = 37.645200
corr_angle[74] = 38.164300
corr_angle[75] = 38.662100
corr_angle[76] = 39.191000
corr_angle[77] = 39.693400
corr_angle[78] = 40.222500
corr_angle[79] = 40.719500
corr_angle[80] = 41.247000
corr_angle[81] = 41.755700
corr_angle[82] = 42.271300
corr_angle[83] = 42.767600
corr_angle[84] = 43.302200
corr_angle[85] = 43.818700
corr_angle[86] = 44.330000
corr_angle[87] = 44.835300
corr_angle[88] = 45.359800
corr_angle[89] = 45.867200
corr_angle[90] = 46.396200
corr_angle[91] = 46.895000
corr_angle[92] = 47.411500
corr_angle[93] = 47.915200
corr_angle[94] = 48.436600
corr_angle[95] = 48.946100
corr_angle[96] = 49.472300
corr_angle[97] = 49.979800
corr_angle[98] = 50.503300
corr_angle[99] = 51.008700
corr_angle[100] = 51.535000
corr_angle[101] = 52.036700
corr_angle[102] = 52.563700
corr_angle[103] = 53.063600
corr_angle[104] = 53.591700
corr_angle[105] = 54.091200
corr_angle[106] = 54.618900
corr_angle[107] = 55.116900
corr_angle[108] = 55.636100
corr_angle[109] = 56.143100
corr_angle[110] = 56.672200
corr_angle[111] = 57.172900
corr_angle[112] = 57.704600
corr_angle[113] = 58.204800
corr_angle[114] = 58.728600
corr_angle[115] = 59.239500
corr_angle[116] = 59.768400
corr_angle[117] = 60.268300
corr_angle[118] = 60.788800
corr_angle[119] = 61.289300
corr_angle[120] = 61.813300
corr_angle[121] = 62.310800
corr_angle[122] = 62.836700
corr_angle[123] = 63.353700
corr_angle[124] = 63.866600
corr_angle[125] = 64.377200
corr_angle[126] = 64.906600
corr_angle[127] = 65.414000
corr_angle[128] = 65.937100
corr_angle[129] = 66.429400
corr_angle[130] = 66.970000
corr_angle[131] = 67.459200
corr_angle[132] = 67.996400
corr_angle[133] = 68.499800
corr_angle[134] = 69.014500
corr_angle[135] = 69.509500
corr_angle[136] = 70.044000
corr_angle[137] = 70.543200
corr_angle[138] = 71.079400
corr_angle[139] = 71.579300
corr_angle[140] = 72.103500
corr_angle[141] = 72.607000
corr_angle[142] = 73.137100
corr_angle[143] = 73.633000
corr_angle[144] = 74.164500
corr_angle[145] = 74.660200
corr_angle[146] = 75.180600
corr_angle[147] = 75.674200
corr_angle[148] = 76.215400
corr_angle[149] = 76.718900
corr_angle[150] = 77.242300
corr_angle[151] = 77.752000
corr_angle[152] = 78.279300
corr_angle[153] = 78.780500
corr_angle[154] = 79.314900
corr_angle[155] = 79.424500
corr_angle[156] = 79.807000
corr_angle[157] = 80.336500
corr_angle[158] = 80.338700
corr_angle[159] = 80.835300
corr_angle[160] = 81.367100
corr_angle[161] = 81.376100
corr_angle[162] = 81.859000
corr_angle[163] = 82.382300
corr_angle[164] = 82.384000
corr_angle[165] = 82.881100
corr_angle[166] = 83.415000
corr_angle[167] = 83.421600
corr_angle[168] = 83.917100
corr_angle[169] = 84.439200
corr_angle[170] = 84.439300
corr_angle[171] = 84.947400
corr_angle[172] = 85.472700
corr_angle[173] = 85.987700
corr_angle[174] = 86.512400
corr_angle[175] = 87.009500
corr_angle[176] = 87.536500
corr_angle[177] = 88.035600
corr_angle[178] = 88.560100
corr_angle[179] = 89.057600
corr_angle[180] = 89.583200
corr_angle[181] = 90.090500
corr_angle[182] = 90.614600
corr_angle[183] = 91.119900
corr_angle[184] = 91.638300
corr_angle[185] = 92.153300
corr_angle[186] = 92.681600
corr_angle[187] = 93.181800
corr_angle[188] = 93.710900
corr_angle[189] = 94.206100
corr_angle[190] = 94.732700
corr_angle[191] = 95.226600
corr_angle[192] = 95.766400
corr_angle[193] = 96.259000
corr_angle[194] = 96.783400
corr_angle[195] = 97.283100
corr_angle[196] = 97.811500
corr_angle[197] = 98.323600
corr_angle[198] = 98.839400
corr_angle[199] = 99.350700
corr_angle[200] = 99.880200
corr_angle[201] = 100.378100
corr_angle[202] = 100.913300
corr_angle[203] = 101.405000
corr_angle[204] = 101.935700
corr_angle[205] = 102.426300
corr_angle[206] = 102.957300
corr_angle[207] = 103.456500
corr_angle[208] = 103.985100
corr_angle[209] = 104.490300
corr_angle[210] = 105.015100
corr_angle[211] = 105.518400
corr_angle[212] = 106.047400
corr_angle[213] = 106.551100
corr_angle[214] = 107.077100
corr_angle[215] = 107.575800
corr_angle[216] = 108.115200
corr_angle[217] = 108.598800
corr_angle[218] = 109.129100
corr_angle[219] = 109.626200
corr_angle[220] = 110.158400
corr_angle[221] = 110.661700
corr_angle[222] = 111.188600
corr_angle[223] = 111.695000
corr_angle[224] = 112.216400
corr_angle[225] = 112.720000
corr_angle[226] = 113.246200
corr_angle[227] = 113.757100
corr_angle[228] = 114.281900
corr_angle[229] = 114.780000
corr_angle[230] = 115.306800
corr_angle[231] = 115.799800
corr_angle[232] = 116.328400
corr_angle[233] = 116.829900
corr_angle[234] = 117.355400
corr_angle[235] = 117.858200
corr_angle[236] = 118.380600
corr_angle[237] = 118.893200
corr_angle[238] = 119.418200
corr_angle[239] = 119.921000
corr_angle[240] = 120.446100
corr_angle[241] = 120.953800
corr_angle[242] = 121.480300
corr_angle[243] = 121.977600
corr_angle[244] = 122.507100
corr_angle[245] = 122.996200
corr_angle[246] = 123.529600
corr_angle[247] = 124.032200
corr_angle[248] = 124.554800
corr_angle[249] = 125.057800
corr_angle[250] = 125.580800
corr_angle[251] = 126.090000
corr_angle[252] = 126.611600
corr_angle[253] = 127.127000
corr_angle[254] = 127.650900
corr_angle[255] = 128.153700
corr_angle[256] = 128.677200
corr_angle[257] = 129.170800
corr_angle[258] = 129.703000
corr_angle[259] = 130.194900
corr_angle[260] = 130.733600
corr_angle[261] = 131.228300
corr_angle[262] = 131.756900
corr_angle[263] = 132.263200
corr_angle[264] = 132.784700
corr_angle[265] = 133.296300
corr_angle[266] = 133.818800
corr_angle[267] = 134.318900
corr_angle[268] = 134.846800
corr_angle[269] = 135.344800
corr_angle[270] = 135.876000
corr_angle[271] = 136.372600
corr_angle[272] = 136.902700
corr_angle[273] = 137.397800
corr_angle[274] = 137.926800
corr_angle[275] = 138.428000
corr_angle[276] = 138.953700
corr_angle[277] = 139.461300
corr_angle[278] = 139.987800
corr_angle[279] = 140.492600
corr_angle[280] = 141.017900
corr_angle[281] = 141.524000
corr_angle[282] = 142.061200
corr_angle[283] = 142.545400
corr_angle[284] = 143.069900
corr_angle[285] = 143.568700
corr_angle[286] = 144.102200
corr_angle[287] = 144.596200
corr_angle[288] = 145.126400
corr_angle[289] = 145.626900
corr_angle[290] = 146.158700
corr_angle[291] = 146.662200
corr_angle[292] = 147.184700
corr_angle[293] = 147.692300
corr_angle[294] = 148.225900
corr_angle[295] = 148.716000
corr_angle[296] = 149.243400
corr_angle[297] = 149.739400
corr_angle[298] = 150.272400
corr_angle[299] = 150.762100
corr_angle[300] = 151.301800
corr_angle[301] = 151.796300
corr_angle[302] = 152.333500
corr_angle[303] = 152.833100
corr_angle[304] = 153.363600
corr_angle[305] = 153.862900
corr_angle[306] = 154.396100
corr_angle[307] = 154.895400
corr_angle[308] = 155.417700
corr_angle[309] = 155.916900
corr_angle[310] = 156.451600
corr_angle[311] = 156.942700
corr_angle[312] = 157.467600
corr_angle[313] = 157.970900
corr_angle[314] = 158.501100
corr_angle[315] = 158.999600
corr_angle[316] = 159.526100
corr_angle[317] = 160.028100
corr_angle[318] = 160.555800
corr_angle[319] = 161.067700
corr_angle[320] = 161.597300
corr_angle[321] = 162.091300
corr_angle[322] = 162.616600
corr_angle[323] = 163.107400
corr_angle[324] = 163.639100
corr_angle[325] = 164.139700
corr_angle[326] = 164.672500
corr_angle[327] = 165.176800
corr_angle[328] = 165.698600
corr_angle[329] = 166.205400
corr_angle[330] = 166.727300
corr_angle[331] = 167.238600
corr_angle[332] = 167.771500
corr_angle[333] = 168.265000
corr_angle[334] = 168.794800
corr_angle[335] = 169.293800
corr_angle[336] = 169.813300
corr_angle[337] = 170.320200
corr_angle[338] = 170.845100
corr_angle[339] = 171.344000
corr_angle[340] = 171.866200
corr_angle[341] = 172.375300
corr_angle[342] = 172.899400
corr_angle[343] = 173.404400
corr_angle[344] = 173.928500
corr_angle[345] = 174.430800
corr_angle[346] = 174.827800
corr_angle[347] = 175.460400
corr_angle[348] = 175.993100
corr_angle[349] = 176.492100
corr_angle[350] = 177.014200
corr_angle[351] = 177.512100
corr_angle[352] = 178.044200
corr_angle[353] = 178.643800
corr_angle[354] = 179.067200
corr_angle[355] = 179.571600
corr_angle[356] = 180.093700
corr_pos[0] = -0.814519
corr_pos[1] = -0.810675
corr_pos[2] = -0.806338
corr_pos[3] = -0.802047
corr_pos[4] = -0.797044
corr_pos[5] = -0.791712
corr_pos[6] = -0.786558
corr_pos[7] = -0.780781
corr_pos[8] = -0.774864
corr_pos[9] = -0.768674
corr_pos[10] = -0.762005
corr_pos[11] = -0.755474
corr_pos[12] = -0.748024
corr_pos[13] = -0.740730
corr_pos[14] = -0.732929
corr_pos[15] = -0.725011
corr_pos[16] = -0.716637
corr_pos[17] = -0.707676
corr_pos[18] = -0.698861
corr_pos[19] = -0.686245
corr_pos[20] = -0.676596
corr_pos[21] = -0.663643
corr_pos[22] = -0.655664
corr_pos[23] = -0.642249
corr_pos[24] = -0.633516
corr_pos[25] = -0.621402
corr_pos[26] = -0.612846
corr_pos[27] = -0.599978
corr_pos[28] = -0.592135
corr_pos[29] = -0.577949
corr_pos[30] = -0.570695
corr_pos[31] = -0.555560
corr_pos[32] = -0.549015
corr_pos[33] = -0.532162
corr_pos[34] = -0.524479
corr_pos[35] = -0.507848
corr_pos[36] = -0.500301
corr_pos[37] = -0.484361
corr_pos[38] = -0.477365
corr_pos[39] = -0.461751
corr_pos[40] = -0.455963
corr_pos[41] = -0.439438
corr_pos[42] = -0.436103
corr_pos[43] = -0.420587
corr_pos[44] = -0.417361
corr_pos[45] = -0.402270
corr_pos[46] = -0.400459
corr_pos[47] = -0.384745
corr_pos[48] = -0.385280
corr_pos[49] = -0.371217
corr_pos[50] = -0.373612
corr_pos[51] = -0.361838
corr_pos[52] = -0.365771
corr_pos[53] = -0.354641
corr_pos[54] = -0.360647
corr_pos[55] = -0.351446
corr_pos[56] = -0.359147
corr_pos[57] = -0.348696
corr_pos[58] = -0.359348
corr_pos[59] = -0.349962
corr_pos[60] = -0.360782
corr_pos[61] = -0.355040
corr_pos[62] = -0.367441
corr_pos[63] = -0.360511
corr_pos[64] = -0.372617
corr_pos[65] = -0.366120
corr_pos[66] = -0.377568
corr_pos[67] = -0.370362
corr_pos[68] = -0.380918
corr_pos[69] = -0.372553
corr_pos[70] = -0.381460
corr_pos[71] = -0.371828
corr_pos[72] = -0.379428
corr_pos[73] = -0.368465
corr_pos[74] = -0.374297
corr_pos[75] = -0.360340
corr_pos[76] = -0.365429
corr_pos[77] = -0.349168
corr_pos[78] = -0.351544
corr_pos[79] = -0.332514
corr_pos[80] = -0.333505
corr_pos[81] = -0.313316
corr_pos[82] = -0.313014
corr_pos[83] = -0.291107
corr_pos[84] = -0.287807
corr_pos[85] = -0.264483
corr_pos[86] = -0.260891
corr_pos[87] = -0.236646
corr_pos[88] = -0.231362
corr_pos[89] = -0.205833
corr_pos[90] = -0.200663
corr_pos[91] = -0.174834
corr_pos[92] = -0.171662
corr_pos[93] = -0.146200
corr_pos[94] = -0.142890
corr_pos[95] = -0.118389
corr_pos[96] = -0.112895
corr_pos[97] = -0.088879
corr_pos[98] = -0.084146
corr_pos[99] = -0.061625
corr_pos[100] = -0.054742
corr_pos[101] = -0.032741
corr_pos[102] = -0.026725
corr_pos[103] = -0.004827
corr_pos[104] = 0.002678
corr_pos[105] = 0.024418
corr_pos[106] = 0.031105
corr_pos[107] = 0.052334
corr_pos[108] = 0.060072
corr_pos[109] = 0.081470
corr_pos[110] = 0.088550
corr_pos[111] = 0.109685
corr_pos[112] = 0.116675
corr_pos[113] = 0.136298
corr_pos[114] = 0.142152
corr_pos[115] = 0.161986
corr_pos[116] = 0.166996
corr_pos[117] = 0.186466
corr_pos[118] = 0.190874
corr_pos[119] = 0.208729
corr_pos[120] = 0.212714
corr_pos[121] = 0.229992
corr_pos[122] = 0.232685
corr_pos[123] = 0.248748
corr_pos[124] = 0.249688
corr_pos[125] = 0.264768
corr_pos[126] = 0.264432
corr_pos[127] = 0.278801
corr_pos[128] = 0.278424
corr_pos[129] = 0.289656
corr_pos[130] = 0.289612
corr_pos[131] = 0.299899
corr_pos[132] = 0.299782
corr_pos[133] = 0.307473
corr_pos[134] = 0.309489
corr_pos[135] = 0.315999
corr_pos[136] = 0.317413
corr_pos[137] = 0.325060
corr_pos[138] = 0.325931
corr_pos[139] = 0.332544
corr_pos[140] = 0.332095
corr_pos[141] = 0.338738
corr_pos[142] = 0.337864
corr_pos[143] = 0.343909
corr_pos[144] = 0.342973
corr_pos[145] = 0.348449
corr_pos[146] = 0.346060
corr_pos[147] = 0.351949
corr_pos[148] = 0.349873
corr_pos[149] = 0.354969
corr_pos[150] = 0.352375
corr_pos[151] = 0.358388
corr_pos[152] = 0.354736
corr_pos[153] = 0.359539
corr_pos[154] = 0.354369
corr_pos[155] = 0.358549
corr_pos[156] = 0.353217
corr_pos[157] = 0.355795
corr_pos[158] = 0.351299
corr_pos[159] = 0.352922
corr_pos[160] = 0.346933
corr_pos[161] = 0.350201
corr_pos[162] = 0.343937
corr_pos[163] = 0.349304
corr_pos[164] = 0.344857
corr_pos[165] = 0.347471
corr_pos[166] = 0.335793
corr_pos[167] = 0.341109
corr_pos[168] = 0.330453
corr_pos[169] = 0.326576
corr_pos[170] = 0.320299
corr_pos[171] = 0.315348
corr_pos[172] = 0.300744
corr_pos[173] = 0.294366
corr_pos[174] = 0.280438
corr_pos[175] = 0.273618
corr_pos[176] = 0.259335
corr_pos[177] = 0.251464
corr_pos[178] = 0.237247
corr_pos[179] = 0.229092
corr_pos[180] = 0.214882
corr_pos[181] = 0.207363
corr_pos[182] = 0.193246
corr_pos[183] = 0.185868
corr_pos[184] = 0.173256
corr_pos[185] = 0.165378
corr_pos[186] = 0.153853
corr_pos[187] = 0.147031
corr_pos[188] = 0.135535
corr_pos[189] = 0.129435
corr_pos[190] = 0.119843
corr_pos[191] = 0.115062
corr_pos[192] = 0.107115
corr_pos[193] = 0.105182
corr_pos[194] = 0.099647
corr_pos[195] = 0.098162
corr_pos[196] = 0.094628
corr_pos[197] = 0.095257
corr_pos[198] = 0.084490
corr_pos[199] = 0.085389
corr_pos[200] = 0.074302
corr_pos[201] = 0.075265
corr_pos[202] = 0.063623
corr_pos[203] = 0.064487
corr_pos[204] = 0.052841
corr_pos[205] = 0.053559
corr_pos[206] = 0.042022
corr_pos[207] = 0.043321
corr_pos[208] = 0.032826
corr_pos[209] = 0.032277
corr_pos[210] = 0.022169
corr_pos[211] = 0.020740
corr_pos[212] = 0.010378
corr_pos[213] = 0.008668
corr_pos[214] = -0.001930
corr_pos[215] = -0.003882
corr_pos[216] = -0.014694
corr_pos[217] = -0.016629
corr_pos[218] = -0.026380
corr_pos[219] = -0.028069
corr_pos[220] = -0.037125
corr_pos[221] = -0.039429
corr_pos[222] = -0.047773
corr_pos[223] = -0.049261
corr_pos[224] = -0.056872
corr_pos[225] = -0.058756
corr_pos[226] = -0.065279
corr_pos[227] = -0.066859
corr_pos[228] = -0.073027
corr_pos[229] = -0.074253
corr_pos[230] = -0.079062
corr_pos[231] = -0.079800
corr_pos[232] = -0.084350
corr_pos[233] = -0.084805
corr_pos[234] = -0.088621
corr_pos[235] = -0.088437
corr_pos[236] = -0.092166
corr_pos[237] = -0.090769
corr_pos[238] = -0.093820
corr_pos[239] = -0.091724
corr_pos[240] = -0.094152
corr_pos[241] = -0.090866
corr_pos[242] = -0.092575
corr_pos[243] = -0.089239
corr_pos[244] = -0.089666
corr_pos[245] = -0.086864
corr_pos[246] = -0.087183
corr_pos[247] = -0.083974
corr_pos[248] = -0.083709
corr_pos[249] = -0.079848
corr_pos[250] = -0.078494
corr_pos[251] = -0.072782
corr_pos[252] = -0.069638
corr_pos[253] = -0.063004
corr_pos[254] = -0.058599
corr_pos[255] = -0.051737
corr_pos[256] = -0.046451
corr_pos[257] = -0.040238
corr_pos[258] = -0.034071
corr_pos[259] = -0.028578
corr_pos[260] = -0.021481
corr_pos[261] = -0.016346
corr_pos[262] = -0.009475
corr_pos[263] = -0.004141
corr_pos[264] = 0.002780
corr_pos[265] = 0.008455
corr_pos[266] = 0.015507
corr_pos[267] = 0.021672
corr_pos[268] = 0.029704
corr_pos[269] = 0.036662
corr_pos[270] = 0.044794
corr_pos[271] = 0.052031
corr_pos[272] = 0.061478
corr_pos[273] = 0.069150
corr_pos[274] = 0.078715
corr_pos[275] = 0.087785
corr_pos[276] = 0.098593
corr_pos[277] = 0.107863
corr_pos[278] = 0.118256
corr_pos[279] = 0.127631
corr_pos[280] = 0.139011
corr_pos[281] = 0.150077
corr_pos[282] = 0.162154
corr_pos[283] = 0.173758
corr_pos[284] = 0.186998
corr_pos[285] = 0.200111
corr_pos[286] = 0.214116
corr_pos[287] = 0.227291
corr_pos[288] = 0.240662
corr_pos[289] = 0.252955
corr_pos[290] = 0.265359
corr_pos[291] = 0.275995
corr_pos[292] = 0.287613
corr_pos[293] = 0.295789
corr_pos[294] = 0.306424
corr_pos[295] = 0.313027
corr_pos[296] = 0.322181
corr_pos[297] = 0.329313
corr_pos[298] = 0.338191
corr_pos[299] = 0.345243
corr_pos[300] = 0.353316
corr_pos[301] = 0.360491
corr_pos[302] = 0.367891
corr_pos[303] = 0.375027
corr_pos[304] = 0.380865
corr_pos[305] = 0.388354
corr_pos[306] = 0.395257
corr_pos[307] = 0.401478
corr_pos[308] = 0.408087
corr_pos[309] = 0.414083
corr_pos[310] = 0.420361
corr_pos[311] = 0.424284
corr_pos[312] = 0.429614
corr_pos[313] = 0.433888
corr_pos[314] = 0.438529
corr_pos[315] = 0.442302
corr_pos[316] = 0.445899
corr_pos[317] = 0.449014
corr_pos[318] = 0.451693
corr_pos[319] = 0.453795
corr_pos[320] = 0.455132
corr_pos[321] = 0.455438
corr_pos[322] = 0.455334
corr_pos[323] = 0.454055
corr_pos[324] = 0.451146
corr_pos[325] = 0.447259
corr_pos[326] = 0.442478
corr_pos[327] = 0.437520
corr_pos[328] = 0.432297
corr_pos[329] = 0.426442
corr_pos[330] = 0.418918
corr_pos[331] = 0.411040
corr_pos[332] = 0.402610
corr_pos[333] = 0.394491
corr_pos[334] = 0.383925
corr_pos[335] = 0.374590
corr_pos[336] = 0.363650
corr_pos[337] = 0.353143
corr_pos[338] = 0.339756
corr_pos[339] = 0.328074
corr_pos[340] = 0.315463
corr_pos[341] = 0.302641
corr_pos[342] = 0.288898
corr_pos[343] = 0.275134
corr_pos[344] = 0.260308
corr_pos[345] = 0.245582
corr_pos[346] = 0.233584
corr_pos[347] = 0.213812
corr_pos[348] = 0.196540
corr_pos[349] = 0.179844
corr_pos[350] = 0.161839
corr_pos[351] = 0.144160
corr_pos[352] = 0.124715
corr_pos[353] = 0.102123
corr_pos[354] = 0.085736
corr_pos[355] = 0.065743
corr_pos[356] = 0.044511

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,171 @@
import builtins
from bec_widgets.cli.client import BECDockArea
# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
if builtins.__dict__.get("bec") is not None:
bec = builtins.__dict__.get("bec")
dev = builtins.__dict__.get("dev")
umv = builtins.__dict__.get("umv")
umvr = builtins.__dict__.get("umvr")
class OMNYGuiToolsError(Exception):
pass
class OMNYGuiTools:
def __init__(self, client):
self.gui = getattr(client, "gui", None)
self.gui_window = self.gui.windows['main'].widget
self.fig200 = None
self.fig201 = None
self.fig202 = None
self.fig203 = None
self.progressbar = None
self.text_box = None
self.idle_text_box = None
def omnygui_show_gui(self):
self.gui_window.show()
def omnygui_stop_gui(self):
self.gui_window.hide()
def _omnycam_parking(self):
self.omnygui_show_omnycam_parking()
def omnygui_show_omnycam_parking(self):
self.omnygui_show_gui()
if self.fig200 is None:
self._omnycam_clear()
self.fig200 = self.gui_window.add_dock(name="omnycam200").add_widget("BECImageWidget")
if self._omnycam_check_device_exists(dev.cam200):
fig = self.fig200.image("cam200")
fig.set_rotation(deg_90=3)
self.fig200.lock_aspect_ratio(True)
else:
print("Cannot open cam200. Device does not exist.")
self.fig203 = self.gui_window.add_dock(name="omnycam203").add_widget("BECImageWidget")
if self._omnycam_check_device_exists(dev.cam203):
fig = self.fig203.image("cam203")
fig.set_rotation(deg_90=3)
self.fig203.lock_aspect_ratio(True)
else:
print("Cannot open cam203. Device does not exist.")
try:
self.gui_window.remove_dock(name="default_figure")
except:
pass
def omnygui_remove_all_docks(self):
self.gui_window.clear_all()
self.fig200 = None
self.fig201 = None
self.fig202 = None
self.fig203 = None
self.progressbar = None
self.text_box = None
self.idle_text_box = None
def omnygui_idle(self):
self.omnygui_show_gui()
if self.idle_text_box is None:
self.omnygui_remove_all_docks()
self.idle_text_box = self.gui_window.add_dock(name="idle_text").add_widget("TextBox")
try:
self.gui_window.remove_dock(name="default_figure")
except:
pass
text = (
"<pre>"
+ " ,o888888o. ,8. ,8. b. 8 `8.`8888. ,8' \n"
+ " . 8888 `88. ,888. ,888. 888o. 8 `8.`8888. ,8' \n"
+ ",8 8888 `8b .`8888. .`8888. Y88888o. 8 `8.`8888. ,8' \n"
+ "88 8888 `8b ,8.`8888. ,8.`8888. .`Y888888o. 8 `8.`8888.,8' \n"
+ "88 8888 88 ,8'8.`8888,8^8.`8888. 8o. `Y888888o. 8 `8.`88888' \n"
+ "88 8888 88 ,8' `8.`8888' `8.`8888. 8`Y8o. `Y88888o8 `8. 8888 \n"
+ "88 8888 ,8P ,8' `8.`88' `8.`8888. 8 `Y8o. `Y8888 `8 8888 \n"
+ "`8 8888 ,8P ,8' `8.`' `8.`8888. 8 `Y8o. `Y8 8 8888 \n"
+ " ` 8888 ,88' ,8' `8 `8.`8888. 8 `Y8o.` 8 8888 \n"
+ " `8888888P' ,8' ` `8.`8888. 8 `Yo 8 8888 \n"
+ "</pre>"
)
self.idle_text_box.set_html_text(text)
def _omnycam_clear(self):
self.omnygui_remove_all_docks()
def _omnycam_check_device_exists(self, device):
try:
device
except:
return False
else:
return True
def _omnycam_samplestage(self):
self.omnygui_show_omnycam_samplestage()
def omnygui_show_omnycam_samplestage(self):
self.omnygui_show_gui()
if self.fig201 is None:
self.omnygui_remove_all_docks()
self.fig201 = self.gui_window.add_dock(name="omnycam201").add_widget("BECImageWidget")
if self._omnycam_check_device_exists(dev.cam201):
fig = self.fig201.image("cam201")
fig.set_rotation(deg_90=3)
self.fig201.lock_aspect_ratio(True)
else:
print("Cannot open cam201. Device does not exist.")
self.fig202 = self.gui_window.add_dock(name="omnycam202").add_widget("BECImageWidget")
if self._omnycam_check_device_exists(dev.cam202):
fig = self.fig202.image("cam202")
fig.set_rotation(deg_90=3)
self.fig202.lock_aspect_ratio(True)
else:
print("Cannot open cam202. Device does not exist.")
try:
self.gui_window.remove_dock(name="default_figure")
except:
pass
def omnygui_show_progress(self):
self.omnygui_show_gui()
if self.progressbar is None:
self.omnygui_remove_all_docks()
# Add a new dock with a RingProgressBar widget
self.progressbar = self.gui_window.add_dock(name="progress").add_widget("RingProgressBar")
# Customize the size of the progress ring
self.progressbar.set_line_widths(20)
# Disable automatic updates and manually set the self.progressbar value
self.progressbar.enable_auto_updates(False)
# Set precision for the self.progressbar display
self.progressbar.set_precision(1) # Display self.progressbar with one decimal places
# Setting multiple rigns with different values
self.progressbar.set_number_of_bars(3)
self.progressbar.rings[2].set_update("scan")
# Set the values of the rings to 50, 75, and 25 from outer to inner ring
# self.progressbar.set_value([50, 75])
# Add a new dock with a TextBox widget
self.text_box = self.gui_window.add_dock(name="progress_text").add_widget("TextBox")
try:
self.gui_window.remove_dock(name="default_figure")
except:
pass
self._omnygui_update_progress()
def _omnygui_update_progress(self):
if self.progressbar is not None:
progress = self.progress["projection"] / self.progress["total_projections"] * 100
subotmo_progress = (
self.progress["subtomo_projection"]
/ self.progress["subtomo_total_projections"]
* 100
)
self.progressbar.set_value([progress, subotmo_progress])
text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}"
self.text_box.set_plain_text(text)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,246 @@
import time
import numpy as np
import os
from rich import box
from rich.console import Console
from rich.table import Table
from typeguard import typechecked
from bec_lib import bec_logger
logger = bec_logger.logger
class OMNYAlignmentError(Exception):
pass
class OMNYAlignmentMixin:
default_correction_file = "correction_omny_202204.txt"
default_correction_file_x = "correction_omny_202204_x.txt"
def reset_correction(self, use_default_correction=True):
"""
Reset the correction to the default values.
If use_default_correction is False, the correction will be set to empty values.
Otherwise the default values will be loaded.
Args:
use_default_correction (bool, optional): If set to true, a call reset the correction to the default values. Defaults to True.
"""
self.corr_pos_x = []
self.corr_angle_x = []
self.corr_pos_y = []
self.corr_angle_y = []
self.corr_pos_y_2 = []
self.corr_angle_y_2 = []
if use_default_correction:
try:
self.read_additional_correction_x(self.default_correction_file_x)
logger.info(f"Applying default x correction from {self.default_correction_file_x}")
except FileNotFoundError:
logger.warning(
f"Could not find default correction file {self.default_correction_file_x}."
)
logger.warning("Not applying any correction.")
try:
self.read_additional_correction_y(self.default_correction_file)
logger.info(f"Applying default y correction from {self.default_correction_file}")
except FileNotFoundError:
logger.warning(
f"Could not find default correction file {self.default_correction_file}."
)
logger.warning("Not applying any correction.")
def reset_tomo_alignment_fit(self):
self.client.delete_global_var("tomo_alignment_fit")
def read_alignment_offset(
self,
dir_path=os.path.expanduser("~/Data10/specES1/internal/"),
setup="omny",
use_vertical_default_values=True,
):
"""
Read the alignment offset from the given directory and set the global parameter
tomo_alignment_fit.
Args:
dir_path (str, optional): The directory to read the alignment offset from. Defaults to os.path.expanduser("~/Data10/specES1/internal/").
"""
tomo_alignment_fit = np.zeros((2, 5))
with open(os.path.join(dir_path, "ptychotomoalign_Ax.txt"), "r") as file:
tomo_alignment_fit[0][0] = file.readline()
with open(os.path.join(dir_path, "ptychotomoalign_Bx.txt"), "r") as file:
tomo_alignment_fit[0][1] = file.readline()
with open(os.path.join(dir_path, "ptychotomoalign_Cx.txt"), "r") as file:
tomo_alignment_fit[0][2] = file.readline()
with open(os.path.join(dir_path, "ptychotomoalign_Ay.txt"), "r") as file:
tomo_alignment_fit[1][0] = file.readline()
with open(os.path.join(dir_path, "ptychotomoalign_By.txt"), "r") as file:
tomo_alignment_fit[1][1] = file.readline()
with open(os.path.join(dir_path, "ptychotomoalign_Cy.txt"), "r") as file:
tomo_alignment_fit[1][2] = file.readline()
with open(os.path.join(dir_path, "ptychotomoalign_Ay3.txt"), "r") as file:
tomo_alignment_fit[1][3] = file.readline()
with open(os.path.join(dir_path, "ptychotomoalign_Cy3.txt"), "r") as file:
tomo_alignment_fit[1][4] = file.readline()
print("New alignment parameters loaded:")
print(
f"X Amplitude {tomo_alignment_fit[0][0]}, "
f"X Phase {tomo_alignment_fit[0][1]}, "
f"X Offset {tomo_alignment_fit[0][2]}, "
f"Y Amplitude {tomo_alignment_fit[1][0]}, "
f"Y Phase {tomo_alignment_fit[1][1]}, "
f"Y Offset {tomo_alignment_fit[1][2]}, "
f"Y 3rd Order Amplitude {tomo_alignment_fit[1][3]}, "
f"Y 3rd Order Phase {tomo_alignment_fit[1][4]} ."
)
if use_vertical_default_values:
print(
f"Using default values for vertical alignment for setup {setup}. Optional: use_vertical_default_values=False"
)
if setup == "flomni":
tomo_alignment_fit[1][0] = 0
tomo_alignment_fit[1][1] = 0
tomo_alignment_fit[1][2] = 0
tomo_alignment_fit[1][3] = 0
tomo_alignment_fit[1][4] = 0
elif setup == "omny":
tomo_alignment_fit[1][0] = 2.588628
tomo_alignment_fit[1][1] = -2.385422
tomo_alignment_fit[1][2] = 0
tomo_alignment_fit[1][3] = 1.010583
tomo_alignment_fit[1][4] = -1.359157
print("Follwing parameters will be used:")
print(
f"X Amplitude {tomo_alignment_fit[0][0]}, "
f"X Phase {tomo_alignment_fit[0][1]}, "
f"X Offset {tomo_alignment_fit[0][2]}, "
f"Y Amplitude {tomo_alignment_fit[1][0]}, "
f"Y Phase {tomo_alignment_fit[1][1]}, "
f"Y Offset {tomo_alignment_fit[1][2]}, "
f"Y 3rd Order Amplitude {tomo_alignment_fit[1][3]}, "
f"Y 3rd Order Phase {tomo_alignment_fit[1][4]} ."
)
self.client.set_global_var("tomo_alignment_fit", tomo_alignment_fit.tolist())
# x amp, phase, offset, y amp, phase, offset, 3rd order amp, 3rd order phase
# 0 0 0 1 0 2 1 0 1 1 1 2 1 3 1 4
def get_alignment_offset(self, angle: float):
"""
Compute the alignment offset for the given angle.
Args:
angle (float): The angle to compute the alignment offset for.
Returns:
tuple: The alignment offset in x, y and z direction.
"""
tomo_alignment_fit = self.client.get_global_var("tomo_alignment_fit")
if tomo_alignment_fit is None:
print("Not applying any alignment offsets. No tomo alignment fit data available.\n")
return (0, 0, 0)
# x amp, phase, offset, y amp, phase, offset
# 0 0 0 1 0 2 1 0 1 1 1 2
correction_x = (
tomo_alignment_fit[0][0] * np.sin(np.radians(angle) + tomo_alignment_fit[0][1])
+ tomo_alignment_fit[0][2]
)
correction_y = (
tomo_alignment_fit[1][0] * np.sin(np.radians(angle) + tomo_alignment_fit[1][1])
+ tomo_alignment_fit[1][2]
+ tomo_alignment_fit[1][3] * np.sin(3 * np.radians(angle) + tomo_alignment_fit[1][4])
)
correction_z = tomo_alignment_fit[0][0] * np.sin(
np.radians(angle + 90) + tomo_alignment_fit[0][1]
)
print(
f"Alignment offset x {correction_x}, y {correction_y}, z {correction_z} for angle"
f" {angle}\n"
)
return (correction_x, correction_y, correction_z)
def _read_correction_file(self, correction_file: str):
with open(correction_file, "r") as f:
num_elements = f.readline()
int_num_elements = int(num_elements.split(" ")[2])
corr_pos = []
corr_angle = []
for j in range(int_num_elements * 2):
line = f.readline()
value = line.split(" ")[2]
name = line.split(" ")[0].split("[")[0]
if name == "corr_pos":
corr_pos.append(float(value))
elif name == "corr_angle":
corr_angle.append(float(value))
print(
f"Loading default mirror correction from file {correction_file} containing {int_num_elements} elements."
)
# print(corr_pos)
return corr_pos, corr_angle
def read_additional_correction_x(self, correction_file: str):
self.corr_pos_x, self.corr_angle_x = self._read_correction_file(correction_file)
def read_additional_correction_y(self, correction_file: str):
self.corr_pos_y, self.corr_angle_y = self._read_correction_file(correction_file)
def read_additional_correction_y_2(self, correction_file: str):
self.corr_pos_y_2, self.corr_angle_y_2 = self._read_correction_file(correction_file)
def compute_additional_correction_x(self, angle):
return self._compute_additional_correction(angle, iteration="x1")
def compute_additional_correction_y(self, angle):
return self._compute_additional_correction(angle, iteration="y1")
def compute_additional_correction_y_2(self, angle):
return self._compute_additional_correction(angle, iteration="y2")
def _compute_additional_correction(self, angle, iteration="y1"):
if iteration == "x1":
corr_pos = self.corr_pos_x
corr_angle = self.corr_angle_x
elif iteration == "y1":
corr_pos = self.corr_pos_y
corr_angle = self.corr_angle_y
elif iteration == "y2":
corr_pos = self.corr_pos_y_2
corr_angle = self.corr_angle_y_2
if not corr_pos:
print(f"Not applying any additional correction {iteration}. No data available.\n")
return 0
# find index of closest angle
for j, _ in enumerate(corr_pos):
newangledelta = np.fabs(corr_angle[j] - angle)
if j == 0:
angledelta = newangledelta
additional_correction_shift = corr_pos[j]
continue
if newangledelta < angledelta:
additional_correction_shift = corr_pos[j]
angledelta = newangledelta
if additional_correction_shift == 0 and angle > corr_angle[-1]:
additional_correction_shift = corr_pos[-1]
print(f"Additional correction shift {iteration}: {additional_correction_shift}")
return additional_correction_shift

View File

@@ -0,0 +1,153 @@
import time
import numpy as np
import sys
import termios
import tty
import fcntl
import os
import builtins
from rich import box
from rich.console import Console
from rich.table import Table
# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
if builtins.__dict__.get("bec") is not None:
bec = builtins.__dict__.get("bec")
dev = builtins.__dict__.get("dev")
umv = builtins.__dict__.get("umv")
umvr = builtins.__dict__.get("umvr")
class OMNYToolsError(Exception):
pass
class OMNYTools:
HEADER = "\033[95m"
OKBLUE = "\033[94m"
OKCYAN = "\033[96m"
OKGREEN = "\033[92m"
WARNING = "\033[93m"
FAIL = "\033[91m"
ENDC = "\033[0m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
def __init__(self, client) -> None:
self.client = client
@staticmethod
def _get_user_param_safe(device, var):
param = dev[device].user_parameter
if not param or param.get(var) is None:
raise ValueError(f"Device {device} has no user parameter definition for {var}.")
return param.get(var)
def printgreen(self, string: str):
print(self.OKGREEN + string + self.ENDC)
def printgreenbold(self, string: str):
print(self.BOLD + self.OKGREEN + string + self.ENDC)
def yesno(self, message: str, default="none", autoconfirm=0) -> bool:
if autoconfirm and default == "y":
self.printgreen(message + " Automatically confirming default: yes")
return True
elif autoconfirm and default == "n":
self.printgreen(message + " Automatically confirming default: no")
return False
if default == "y":
message_ending = " [Y]/n? "
elif default == "n":
message_ending = " y/[N]? "
else:
message_ending = " y/n? "
while True:
user_input = input(self.OKBLUE + message + message_ending + self.ENDC)
if (
user_input == "Y" or user_input == "y" or user_input == "yes" or user_input == "Yes"
) or (default == "y" and user_input == ""):
return True
if (
user_input == "N" or user_input == "n" or user_input == "no" or user_input == "No"
) or (default == "n" and user_input == ""):
return False
else:
print("Please expicitely confirm y or n.")
def tweak_cursor(
self, dev1, step1: float, dev2="none", step2: float = "0", special_command="none"
):
if dev1 not in dev.enabled_devices:
print(f"Device 1 {dev} is not in enabled devices.")
return
if dev2 not in dev.enabled_devices and dev2 != "none":
print(f"Device 2 {dev} is not in enabled devices.")
return
# Save the current terminal settings
fd = sys.stdin.fileno()
old_term = termios.tcgetattr(fd)
try:
# Set the terminal to raw mode to capture single key presses
tty.setraw(fd)
# Set stdin to non-blocking mode
old_flags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags | os.O_NONBLOCK)
print("Tweak Cursor." + self.BOLD + self.OKBLUE + "Press (q) to quit!\r" + self.ENDC)
while True:
try:
# Read single character input
key = sys.stdin.read(1)
if key == "q":
print("\n\rExiting tweak mode\r")
break
elif key == "\x1b": # Escape sequences for arrow keys
next1, next2 = sys.stdin.read(2)
if next1 == "[":
if next2 == "A":
# print("up")
if dev2 != "none":
umvr(dev2, step2)
if special_command != "none":
special_command()
elif next2 == "B":
# print(" down")
if dev2 != "none":
umvr(dev2, -step2)
if special_command != "none":
special_command()
elif next2 == "C":
# print("right")
umvr(dev1, step1)
if special_command != "none":
special_command()
elif next2 == "D":
# print("left")
umvr(dev1, -step1)
if special_command != "none":
special_command()
elif key == "+":
step1 = step1 * 2
if dev2 != "none":
step2 = step2 * 2
print(f"\rDouble step size. New step size: {step1}, {step2}\r")
elif key == "-":
step1 = step1 / 2
if dev2 != "none":
step2 = step2 / 2
print(f"\rHalf step size. New step size: {step1}, {step2}\r")
except IOError:
# No input available, keep looping
pass
# Sleep for a short period to avoid high CPU usage
time.sleep(0.02)
finally:
# Restore the terminal to its original state
termios.tcsetattr(fd, termios.TCSADRAIN, old_term)
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags)

View File

@@ -0,0 +1,384 @@
import time
import numpy as np
from rich import box
from rich.console import Console
from rich.table import Table
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
class OMNYError(Exception):
pass
class OMNYOpticsMixin:
@staticmethod
def _get_user_param_safe(device, var):
param = dev[device].user_parameter
if not param or param.get(var) is None:
raise ValueError(f"Device {device} has no user parameter definition for {var}.")
return param.get(var)
def ooptics_in(self):
self.ofzp_in()
# ocs_in
self.oosa_in()
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_enable()
self.align.update_frame()
user_input = input(
"Is the direct beam gone on the xray eye? Do you see the cone of the FZP?"
)
if user_input == "y":
printf("Next oeye_out...\n")
else:
raise OMNYError("Failed to properly move in the Xray optics")
def _oeyey_mv(self, position):
# direction dependent speeds
if dev.oeyez.get().readback < position:
dev.oeyez.controller.socket_put_confirmed("axspeed[7]=15000")
else:
dev.oeyez.controller.socket_put_confirmed("axspeed[7]=10000")
umv(dev.oeyey, position)
dev.oeyez.controller.socket_put_confirmed("axspeed[7]=10000")
def oeye_out(self):
fshclose()
if self.OMNYTools.yesno("Did you move in the optics?"):
umv(dev.oeyez, -2)
self._oeyey_mv(-60.3)
# free camera
epics_put("XOMNYI-XEYE-ACQ:0", 2)
else:
raise OMNYError("The optics were not moved in. Please do so prior to eyey_out")
self.OMNYTools.printgreen("Oeye is out.")
def oeye_cam_in(self):
if dev.oeyez.get().readback < -80:
umv(dev.oeyez, -50)
if np.fabs(dev.oeyey.get().readback + 4.8) > 0.1:
self._oeyey_mv(-4.8)
if np.fabs(dev.oeyez.get().readback + 2) > 0.1 or np.fabs(dev.oeyex.get().readback) > 0.1:
umv(dev.oeyez, -2, dev.oeyex, 0)
# if still too close in z -- safety check
if np.fabs(dev.oeyez.get().readback + 2) > 0.1:
raise OMNYError("The oeye is too close in z for transfer. ERROR! Aborting.")
self.OMNYTools.printgreen("Oeye is at cam position.")
def _oeye_xray_is_in(self) -> bool:
omny_oeye_xray_inx = self._get_user_param_safe("oeyex", "xray_in")
omny_oeye_xray_iny = self._get_user_param_safe("oeyey", "xray_in")
omny_oeye_currentx = dev.oeyex.get().readback
omny_oeye_currenty = dev.oeyey.get().readback
if (
np.fabs(omny_oeye_currentx - omny_oeye_xray_inx) < 0.1
and np.fabs(omny_oeye_currenty - omny_oeye_xray_iny) < 0.1
):
return True
else:
return False
def oeye_xray_in(self):
if self._oeye_xray_is_in():
pass
else:
# todo
# self._otransfer_gripper_safe_xray_in_operation()
# if(!_oshield_is_ST_closed())
# {
# printf("The shield of the sample stage is not closed. Aborting.\n")
# exit
# }
omny_oeye_xray_inx = self._get_user_param_safe("oeyex", "xray_in")
omny_oeye_xray_iny = self._get_user_param_safe("oeyey", "xray_in")
omny_oeye_xray_inz = self._get_user_param_safe("oeyez", "xray_in")
self._oeyey_mv(omny_oeye_xray_iny)
omny_oeye_currenty = dev.oeyey.get().readback
if np.fabs(omny_oeye_currenty - omny_oeye_xray_iny) > 0.1:
raise OMNYError("The oeye did not move up.\n")
umv(dev.oeyex, omny_oeye_xray_inx, dev.oeyez, omny_oeye_xray_inz)
self.OMNYTools.printgreen("Oeye is at X-ray position.")
# some notes for the vis microscope:
# initial position for the vis light microscope
# do not open the shield when the microscope is at the vis mic position
# found eoeyx -45.13, z -84.9, y 0.64
# for a samy position of 2.8 with delta off
# the osa position should be in z around 7.4. in x it seems better
# around -0.6, where potentially xrays dont pass anymore
#
def _oosa_check_y(self):
omny_oosa_currenty = dev.oosay.get().readback
if np.fabs(omny_oosa_currenty - 0.9) > 0.05:
umv(dev.oosay, 0.9)
omny_oosa_currenty = dev.oosay.get().readback
if np.fabs(omny_oosa_currenty - 0.9) > 0.05:
raise OMNYError("oosay is not around 0.9. Aborting.")
def _oosa_to_move_corridor(self):
self._oosa_check_y()
dev.oosax.limits = [-3, 3.7] # risk collision with shield
umv(dev.oosax, -2)
dev.oosax.read(cached=False)
omny_oosa_currentx = dev.oosax.get().readback
if np.fabs(omny_oosa_currentx + 2) > 0.1:
raise OMNYError("oosax did not reach target position. Not moving in z.\n")
def oosa_in(self):
self._oosa_check_y()
dev.oshield.read(cached=False)
omny_oshield_current = dev.oshield.get().readback
if omny_oshield_current < 15:
self._oshield_ST_close()
if self.near_field == False:
x_in_pos = self._get_user_param_safe("oosax", "far_field_in")
y_in_pos = self._get_user_param_safe("oosay", "far_field_in")
z_in_pos = self._get_user_param_safe("oosaz", "far_field_in")
print("OSA movement in far-field mode.")
dev.oosaz.read(cached=False)
omny_oosa_currentz = dev.oosaz.get().readback
if omny_oosa_currentz < 6.4:
self._oosa_to_move_corridor()
dev.oosaz.limits = [6.4, 6.6]
umv(dev.oosaz, z_in_pos)
umv(dev.oosax, x_in_pos)
umv(dev.oosay, y_in_pos)
#### For the 30 nm FZP 220 um we use this part
# umv oosaz 6.5
# umv oosax 3.2453
# umv oosay 0.386015
if self.near_field == True:
x_in_pos = self._get_user_param_safe("oosax", "near_field_in")
y_in_pos = self._get_user_param_safe("oosay", "near_field_in")
z_in_pos = self._get_user_param_safe("oosaz", "near_field_in")
print("OSA movement in near-field mode.")
dev.oosaz.read(cached=False)
omny_oosa_currentz = dev.oosaz.get().readback
if omny_oosa_currentz > 0:
self._oosa_to_move_corridor()
dev.oosaz.limits = [-0.4, -0.6]
umv(dev.oosaz, z_in_pos)
umv(dev.oosax, x_in_pos)
omny_osamy_current = dev.osamy.get().readback
if omny_osamy_current < 3.25:
umv(dev.oosay, y_in_pos)
else:
raise OMNYError("Failed to move oosa in. osamy position is too large.")
self.OMNYTools.printgreen("OSA is in.")
# todo
# _omny_interferometer_align_tracking
# rt_feedback_enable
def oosa_out(self):
self._oosa_check_y()
dev.oshield.read(cached=False)
omny_oshield_current = dev.oshield.get().readback
if omny_oshield_current < 15:
self._oshield_ST_close()
omny_oosaz_current = dev.oosaz.get().readback
if self.near_field == False:
print("OSA movement in far-field mode.")
if omny_oosaz_current < 6.4:
self._oosa_to_move_corridor()
dev.oosaz.limits = [6.4, 6.6]
umv(dev.oosaz, 6.5)
umv(dev.oosax, -2)
if self.near_field == True:
print("OSA movement in near-field mode.")
if omny_oosaz_current > 0:
self._oosa_to_move_corridor()
dev.oosaz.limits = [-0.4, -0.6]
umv(dev.oosaz, -0.45)
umv(dev.oosax, -2)
# todo _omny_interferometer_align_tracking
self.OMNYTools.printgreen("OSA is out.")
def oosa_move_out_of_shield(self):
# todo: _omnycam_samplestage
self._oosa_check_y()
self._oosa_to_move_corridor()
omny_osamx_current = dev.osamx.get().readback
if np.fabs(omny_osamx_current) > 0.2:
umv(dev.osamx, 0)
omny_oosaz_current = dev.oosaz.get().readback
if omny_oosaz_current > 0.1:
dev.oosaz.limits = [-0.1, 0.1]
umv(dev.oosaz, 0)
self.OMNYTools.printgreen("OSA is out of shield.")
def ofzp_out(self):
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_disable()
y_out_pos = self._get_user_param_safe("ofzpy", "out")
if np.fabs(dev.ofzpy.get().readback - y_out_pos) > 0.02:
umv(dev.ofzpy, y_out_pos)
self.OMNYTools.printgreen("FZP at out position")
def ofzp_in(self):
if "rtx" in dev and dev.rtx.enabled:
dev.rtx.controller.feedback_disable()
x_in_pos = self._get_user_param_safe("ofzpx", "in")
y_in_pos = self._get_user_param_safe("ofzpy", "in")
if np.fabs(dev.ofzpy.get().readback - y_in_pos) > 0.02:
umv(dev.ofzpy, y_in_pos)
if np.fabs(dev.ofzpx.get().readback - x_in_pos) > 0.02:
umv(dev.ofzpx, x_in_pos)
self.OMNYTools.printgreen("FZP at in position")
# 220 mu FZP at ofzpz 31.8025 for eiger probe (about 2.4 mm propagation after focus)
# umv(dev.ofzpy, 0.7944)
# if np.fabs(dev.ofzpx.get().readback+0.4317)>0.05:
# umv(dev.ofzpx, -0.4317)
# note the 220 fzp also works for near field 6.2 kev by just moving back osa and fzp
# ofzpz 24.8 leads to a 9.5 mm propagation distance.
# With the 220 mu FZP this gives 100 nm pixel recons
# for the oosa macro set near_field=1
# 170 mu FZP at 6.2 kev for large beam at ofzpz 31.8025 of about 58 mu diameter
# 120 mu FZP at ofzpz 28.1991
# 250 mu FZP 60 nm at 5.65 keV
# ofzpz 29.7 for propagation distance 2.2
# umv ofzpx -0.4457
# umv ofzpy 0.193630
# 150 um fzp, 60 nm, ofzpz 33.8 at 8.9 kev for propagation of 1.7 mm after focus
# umv ofzpx -0.756678
# umv ofzpy 0.193515
# 250 um 30 nm FZP upper right
# small abberrations, seems to give good results in weak objects
# ofzpx -0.609240
# umv ofzpy 0.118265
# 250 um 30 nm FZP lower right very aberated
# ofzpx -0.881935
# umv ofzpy 0.537050
# ofzpz 28.4027
# 5.30 mm prop at 8.9 keV, 45 nm pixel in near field
# ofzpz 33.103
# 0.6 mm prop at 8.9 kev far field 7 m flight tube at foptz
# ofzpz 49.4 is reachable just without interferometer swap
# which at 6.2 keV and 250 um diam, 30 nm should gives a propagation of 0.8 after focus
# and a beam size of 6 microns diamter
###coordinates 30 nm FZP for comparing them
# not sure if that is really correct
# FZP 1 - FZP 2
# FZP 5
# FZP 4 - FZP 1
# FZP
##upper right
# umv ofzpx -0.6154 ofzpy 0.1183
# umv ocsx -0.6070 ocsy 0.0540
# lower right
# umv ofzpx -0.8341 ofzpy 0.5683
# umv ocsx -0.3880 ocsy -0.3960
# lower left
# umv ofzpx -0.3876 ofzpy 0.7902
# umv ocsx -0.8380 ocsy -0.6180
# upper left
# umv ofzpx -0.1678 ofzpy 0.3403
# umv ocsx -1.0550 ocsy -0.1680
def ofzp_info(self, mokev_val=-1, ofzpz_val=-1):
print(f"{ofzpz_val}")
if mokev_val == -1:
try:
mokev_val = dev.mokev.readback.get()
except:
print(
"Device mokev does not exist. You can specify the energy in keV as an argument instead."
)
return
if ofzpz_val == -1:
ofzpz_val = dev.ofzpz.readback.get()
distance = 66 + 2.4 + 31.8025 - ofzpz_val
print(
f"\nThe sample is in a distance of \033[1m{distance:.1f} mm\033[0m from the 60 nm FZP.\n"
)
print(f"At the current energy of {mokev_val:.4f} keV we have following options:\n")
diameters = [80e-6, 100e-6, 120e-6, 150e-6, 170e-6, 200e-6, 220e-6, 250e-6]
console = Console()
table = Table(title="Outermost zone width \033[1m60 nm\033[0m", box=box.SQUARE)
table.add_column("Diameter", justify="center")
table.add_column("Focal distance", justify="center")
table.add_column("Current beam size", justify="center")
wavelength = 1.2398e-9 / mokev_val
for diameter in diameters:
outermost_zonewidth = 60e-9
focal_distance = diameter * outermost_zonewidth / wavelength * 1000
beam_size = -diameter / (focal_distance * 1000) * (focal_distance - distance) * 1e9
table.add_row(
f"{diameter*1e6:.2f} microns",
f"{focal_distance:.2f} mm",
f"{beam_size:.2f} microns",
)
console.print(table)
# 30 nm with additional spacer
distance = 53.84 + 0.6 + 33.1 - ofzpz_val
print(
f"\nThe sample is in a distance of \033[1m{distance:.1f} mm\033[0m from the 30 nm FZP.\n"
)
diameters = [150e-6, 250e-6]
console = Console()
table = Table(title="Outermost zone width \033[1m30 nm\033[0m", box=box.SQUARE)
table.add_column("Diameter", justify="center")
table.add_column("Focal distance", justify="center")
table.add_column("Current beam size", justify="center")
wavelength = 1.2398e-9 / mokev_val
for diameter in diameters:
outermost_zonewidth = 30e-9
focal_distance = diameter * outermost_zonewidth / wavelength * 1000
beam_size = -diameter / (focal_distance * 1000) * (focal_distance - distance) * 1e9
table.add_row(
f"{diameter*1e6:.2f} microns",
f"{focal_distance:.2f} mm",
f"{beam_size:.2f} microns",
)
console.print(table)
print(
"This function can be called with explicit energy and ofzpz position.\n Example: omny.ffzp_info(mokev_val=6.2, ofzpz_val=33.2)"
)
# from flomni
# oosaz_val = dev.oosaz.readback.get()
# print("\nOSA Information:")
# print(f" Current fosaz {fosaz_val:.1f}")
# print(
# f" The OSA will collide with a normal OMNY pin at fosaz \033[1m{(33-fosaz_val):.1f}\033[0m"
# )
# print(f" Remaining space: \033[1m{-fosaz_val+(33-foptz_val):.1f}\033[0m")

View File

@@ -0,0 +1,241 @@
import time
import numpy as np
import sys
import termios
import tty
import fcntl
import os
from rich import box
from rich.console import Console
from rich.table import Table
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
class OMNY_rt_clientError(Exception):
pass
class OMNY_rt_client:
def __init__(self):
self.mirror_channel = -1
self.mirror_amplitutde_increase = 0
self.mirror_parameters = {}
for j in range(1, 9):
self.mirror_parameters[j] = dev.rtx.controller.get_mirror_parameters(j)
@staticmethod
def _get_user_param_safe(device, var):
param = dev[device].user_parameter
if not param or param.get(var) is None:
raise OMNY_rt_clientError(
f"Device {device} has no user parameter definition for {var}."
)
return param.get(var)
def _omny_interferometer_openloop_steps(self, channel, steps, amplitude):
dev.rtx.controller._omny_interferometer_openloop_steps(channel, steps, amplitude)
def interferometer_tweaking(self):
self._tweak_interferometer()
def _tweak_interferometer(self):
self.mirror_channel = -1
# Save the current terminal settings
fd = sys.stdin.fileno()
old_term = termios.tcgetattr(fd)
print("Ready to tweak the interferometer. Press q to quit.")
print("The arrows adjust directions.")
print("Numbers select the mirror aligner.")
try:
# Set the terminal to raw mode to capture single key presses
tty.setraw(fd)
# Set stdin to non-blocking mode
old_flags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags | os.O_NONBLOCK)
opt_mirrorname = "none"
max = 0
while True:
try:
# Read single character input
key = sys.stdin.read(1)
if key == "q":
self.mirror_amplitutde_increase = 0
self.mirror_channel = -1
print("\n\rExiting tweak mode\r")
break
elif key == "\x1b": # Escape sequences for arrow keys
next1, next2 = sys.stdin.read(2)
if next1 == "[":
printit = True
if next2 == "A":
# print("up")
if self.mirror_channel != -1:
self._omny_interferometer_openloop_steps(
4,
-self.mirror_parameters[self.mirror_channel][
"opt_steps2_neg"
],
self.mirror_parameters[self.mirror_channel][
"opt_amplitude2_neg"
]
+ self.mirror_amplitutde_increase,
)
elif next2 == "B":
# print(" down")
if self.mirror_channel != -1:
self._omny_interferometer_openloop_steps(
4,
self.mirror_parameters[self.mirror_channel][
"opt_steps2_pos"
],
self.mirror_parameters[self.mirror_channel][
"opt_amplitude2_pos"
]
+ self.mirror_amplitutde_increase,
)
elif next2 == "C":
# print("right")
if self.mirror_channel != -1:
self._omny_interferometer_openloop_steps(
3,
-self.mirror_parameters[self.mirror_channel][
"opt_steps1_neg"
],
self.mirror_parameters[self.mirror_channel][
"opt_amplitude1_neg"
]
+ self.mirror_amplitutde_increase,
)
elif next2 == "D":
# print("left")
if self.mirror_channel != -1:
self._omny_interferometer_openloop_steps(
3,
self.mirror_parameters[self.mirror_channel][
"opt_steps1_pos"
],
self.mirror_parameters[self.mirror_channel][
"opt_amplitude1_pos"
]
+ self.mirror_amplitutde_increase,
)
elif key.isdigit() and 1 <= int(key) <= 8:
self.mirror_channel = int(key)
opt_mirrorname = self.mirror_parameters[self.mirror_channel][
"opt_mirrorname"
]
autostop = self.mirror_parameters[self.mirror_channel]["opt_signal_stop"]
averaging_time = self.mirror_parameters[self.mirror_channel][
"opt_averaging_time"
]
print(
f"\nSelected mirror channel {self.mirror_channel}: {opt_mirrorname}. Autostop {autostop}. Signal averaging time: {averaging_time}\r"
)
if int(key) == 6:
dev.rtx.controller.laser_tracker_on()
dev.rtx.controller._omny_interferometer_switch_channel(self.mirror_channel)
max = 0
printit = True
elif key == "+":
print("\nIncreasing voltage amplitudes by 100.\r")
self.mirror_amplitutde_increase += 100
elif key == "-":
print("\nDecreasing voltage amplitudes by 100.\r")
self.mirror_amplitutde_increase -= 100
elif key == "a":
if self.mirror_channel != -1:
dev.rtx.controller._omny_interferometer_optimize(
mirror_channel=self.mirror_channel, channel=3
)
dev.rtx.controller._omny_interferometer_optimize(
mirror_channel=self.mirror_channel, channel=4
)
dev.rtx.controller._omny_interferometer_optimize(
mirror_channel=self.mirror_channel, channel=3
)
dev.rtx.controller._omny_interferometer_optimize(
mirror_channel=self.mirror_channel, channel=4
)
if self.mirror_channel != -1 and printit:
printit = False
signal = dev.rtx.controller._omny_interferometer_get_signalsample(
self.mirror_parameters[self.mirror_channel]["opt_signalchannel"],
self.mirror_parameters[self.mirror_channel]["opt_averaging_time"],
)
if signal > max:
max = signal
info_str = f"Channel {self.mirror_channel}, {opt_mirrorname}, Current signal: {signal:.0f}"
filling = " " * (50 - len(info_str))
# Calculate the number of filled and unfilled segments
length = 30
percentage = signal / max
filled_length = int(length * percentage)
unfilled_length = length - filled_length
bar = "#" * filled_length + "-" * unfilled_length
print(info_str + filling + "0 " + bar + f" {max:.0f} (q)uit\r", end="")
except IOError:
# No input available, keep looping
pass
# Sleep for a short period to avoid high CPU usage
time.sleep(0.02)
finally:
# Restore the terminal to its original state
termios.tcsetattr(fd, termios.TCSADRAIN, old_term)
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags)
dev.rtx.controller._omny_interferometer_switch_alloff()
self.mirror_channel = -1
self.mirror_amplitutde_increase = 0
dev.rtx.controller.show_signal_strength_interferometer()
def show_signal_strength_interferometer(self):
dev.rtx.controller.show_signal_strength_interferometer()
def omny_interferometer_align_incoupling_angle(self):
dev.rtx.controller.omny_interferometer_align_incoupling_angle()
def interferometer_tweak_otrack(self):
self.OMNYTools.tweak_cursor(
dev.otrackz,
0.1,
dev.otracky,
0.1,
special_command=dev.rtx.controller.laser_tracker_print_intensity_for_otrack_tweaking,
)
def feedback_enable_with_reset(self):
dev.rtx.controller.feedback_enable_with_reset()
def feedback_disable(self):
dev.rtx.controller.feedback_disable()
def feedback_status(self):
if dev.rtx.controller.feedback_is_running():
print("Feedback is running.")
else:
print("Feedback is NOT running.")
def laser_tracker_on(self):
dev.rtx.controller.laser_tracker_on()
def laser_tracker_off(self):
dev.rtx.controller.laser_tracker_off()
def laser_tracker_show_all(self):
dev.rtx.controller.laser_tracker_show_all()
def omny_interferometer_align_tracking(self):
dev.rtx.controller.omny_interferometer_align_tracking()
def laser_tracker_check_and_wait_for_signalstrength(self):
dev.rtx.controller.laser_tracker_check_and_wait_for_signalstrength()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,235 @@
from __future__ import annotations
import builtins
import os
import time
from typing import TYPE_CHECKING
from bec_lib import bec_logger
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen
logger = bec_logger.logger
# import builtins to avoid linter errors
bec = builtins.__dict__.get("bec")
dev = builtins.__dict__.get("dev")
umv = builtins.__dict__.get("umv")
umvr = builtins.__dict__.get("umvr")
if TYPE_CHECKING:
from bec_ipython_client.plugins.omny import OMNY
class XrayEyeAlign:
# pixel calibration, multiply to get mm
PIXEL_CALIBRATION = 0.2 / 218 # .2 with binning
def __init__(self, client, omny: OMNY) -> None:
self.client = client
self.omny = omny
self.device_manager = client.device_manager
self.scans = client.scans
self.alignment_values = {}
self.omny.reset_correction()
self.omny.reset_tomo_alignment_fit()
def _reset_init_values(self):
self.shift_xy = [0, 0]
self._xray_fov_xy = [0, 0]
def save_frame(self):
epics_put("XOMNYI-XEYE-SAVFRAME:0", 1)
def update_frame(self):
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
# start live
epics_put("XOMNYI-XEYE-ACQ:0", 1)
# wait for start live
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
time.sleep(0.5)
print("waiting for live view to start...")
fshopen()
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
print("waiting for new frame...")
time.sleep(0.5)
time.sleep(0.5)
# stop live view
epics_put("XOMNYI-XEYE-ACQ:0", 0)
time.sleep(1)
# fshclose
print("got new frame")
def tomo_rotate(self, val: float):
# pylint: disable=undefined-variable
umv(self.device_manager.devices.osamroy, val)
def get_tomo_angle(self):
return self.device_manager.devices.osamroy.readback.get()
def update_fov(self, k: int):
self._xray_fov_xy[0] = max(epics_get(f"XOMNYI-XEYE-XWIDTH_X:{k}"), self._xray_fov_xy[0])
self._xray_fov_xy[1] = max(0, self._xray_fov_xy[0])
@property
def movement_buttons_enabled(self):
return [epics_get("XOMNYI-XEYE-ENAMVX:0"), epics_get("XOMNYI-XEYE-ENAMVY:0")]
@movement_buttons_enabled.setter
def movement_buttons_enabled(self, enabled: bool):
enabled = int(enabled)
epics_put("XOMNYI-XEYE-ENAMVX:0", enabled)
epics_put("XOMNYI-XEYE-ENAMVY:0", enabled)
def send_message(self, msg: str):
epics_put("XOMNYI-XEYE-MESSAGE:0.DESC", msg)
def align(self):
# reset shift xy and fov params
self._reset_init_values()
self.tomo_rotate(0)
epics_put("XOMNYI-XEYE-ANGLE:0", 0)
self.omny.oeye_xray_in()
self.omny.feedback_enable_with_reset()
# disable movement buttons
self.movement_buttons_enabled = False
sample_name = dev.omny_samples.get_sample_name_in_samplestage()
epics_put("XOMNYI-XEYE-SAMPLENAME:0.DESC", sample_name)
# this makes sure we are in a defined state
self.omny.feedback_disable()
epics_put("XOMNYI-XEYE-PIXELSIZE:0", self.PIXEL_CALIBRATION)
osamx_in = self.omny.OMNYTools._get_user_param_safe("osamx", "in")
umv(dev.osamx, osamx_in - 0.35)
self.omny.ofzp_in()
self.update_frame()
# enable submit buttons
self.movement_buttons_enabled = False
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
epics_put("XOMNYI-XEYE-STEP:0", 0)
self.send_message("Submit center value of FZP.")
k = 0
while True:
if epics_get("XOMNYI-XEYE-SUBMIT:0") == 1:
val_x = epics_get(f"XOMNYI-XEYE-XVAL_X:{k}") / 2 * self.PIXEL_CALIBRATION # in mm
self.alignment_values[k] = val_x
print(f"Clicked position {k}: x {self.alignment_values[k]}")
rtx_position = dev.rtx.readback.get() / 1000
print(f"Current rtx position {rtx_position}")
self.alignment_values[k] -= rtx_position
print(f"Corrected position {k}: x {self.alignment_values[k]}")
if k == 0: # received center value of FZP
self.send_message("please wait ...")
self.movement_buttons_enabled = False
epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button
self.omny.feedback_disable()
osamx_in = self.omny.OMNYTools._get_user_param_safe("osamx", "in")
umv(dev.osamx, osamx_in)
self.omny.ofzp_out()
self.update_frame()
epics_put("XOMNYI-XEYE-RECBG:0", 1)
while epics_get("XOMNYI-XEYE-RECBG:0") == 1:
time.sleep(0.5)
print("waiting for background frame...")
umv(dev.osamx, osamx_in)
time.sleep(0.5)
self.omny.feedback_enable_with_reset()
self.update_frame()
self.send_message("Adjust sample height and submit center")
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
self.movement_buttons_enabled = True
elif 1 <= k < 5: # received sample center value at samroy 0 ... 315
self.send_message("please wait ...")
epics_put("XOMNYI-XEYE-SUBMIT:0", -1)
self.movement_buttons_enabled = False
umv(dev.rtx, 0)
self.tomo_rotate(k * 45)
epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle())
self.update_frame()
self.send_message("Submit sample center")
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
epics_put("XOMNYI-XEYE-ENAMVX:0", 1)
self.update_fov(k)
elif k == 5: # received sample center value at samroy 270 and done
self.send_message("done...")
epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button
self.movement_buttons_enabled = False
self.update_fov(k)
break
k += 1
epics_put("XOMNYI-XEYE-STEP:0", k)
_xrayeyalignmvx = epics_get("XOMNYI-XEYE-MVX:0")
if _xrayeyalignmvx != 0:
umvr(dev.rtx, _xrayeyalignmvx)
print(f"Current rtx position {dev.rtx.readback.get() / 1000}")
epics_put("XOMNYI-XEYE-MVX:0", 0)
if k > 0:
epics_put(f"XOMNYI-XEYE-STAGEPOSX:{k}", dev.rtx.readback.get() / 1000)
time.sleep(3)
self.update_frame()
if k < 2:
# allow movements, store movements to calculate center
_xrayeyalignmvy = epics_get("XOMNYI-XEYE-MVY:0")
if _xrayeyalignmvy != 0:
self.omny.feedback_disable()
umvr(dev.osamy, _xrayeyalignmvy / 1000)
time.sleep(2)
epics_put("XOMNYI-XEYE-MVY:0", 0)
self.omny.feedback_enable_with_reset()
self.update_frame()
time.sleep(0.2)
self.write_output()
fovx = self._xray_fov_xy[0] * self.PIXEL_CALIBRATION * 1000 / 2
fovy = self._xray_fov_xy[1] * self.PIXEL_CALIBRATION * 1000 / 2
self.tomo_rotate(0)
umv(dev.rtx, 0)
# free camera
epics_put("XOMNYI-XEYE-ACQ:0", 2)
print(
f"The largest field of view from the xrayeyealign was \nfovx = {fovx:.0f} microns, fovy"
f" = {fovy:.0f} microns"
)
print("Use the matlab routine to FIT the current alignment...")
print("Then LOAD ALIGNMENT PARAMETERS by running omny.read_alignment_offset()\n")
def write_output(self):
file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues")
if not os.path.exists(file):
os.makedirs(os.path.dirname(file), exist_ok=True)
with open(file, "w") as alignment_values_file:
alignment_values_file.write("angle\thorizontal\n")
for k in range(1, 6):
fovx_offset = self.alignment_values[0] - self.alignment_values[k]
print(f"Writing to file new alignment: number {k}, value x {fovx_offset}")
alignment_values_file.write(f"{(k-1)*45}\t{fovx_offset*1000}\n")

View File

@@ -16,14 +16,13 @@ parse the --session argument, add the following lines to the script:
if args.session == "my_session":
print("Loading my_session session")
from bec_plugins.bec_client.plugins.my_session import *
from bec_plugins.bec_ipython_client.plugins.my_session import *
else:
print("Loading default session")
from bec_plugins.bec_client.plugins.default_session import *
from bec_plugins.bec_ipython_client.plugins.default_session import *
"""
# pylint: disable=invalid-name, unused-import, import-error, undefined-variable, unused-variable, unused-argument, no-name-in-module
import argparse
from bec_lib import bec_logger
@@ -31,46 +30,34 @@ logger = bec_logger.logger
logger.info("Using the cSAXS startup script.")
parser = argparse.ArgumentParser()
parser.add_argument("--session", help="Session name", type=str, default="cSAXS")
args = parser.parse_args()
# pylint: disable=import-error
_args = _main_dict["args"]
if args.session == "LamNI":
print("Loading LamNI session")
from bec_plugins.bec_client.plugins.cSAXS import *
from bec_plugins.bec_client.plugins.LamNI import *
_session_name = "cSAXS"
if _args.session.lower() == "lamni":
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
from csaxs_bec.bec_ipython_client.plugins.LamNI import *
_session_name = "LamNI"
lamni = LamNI(bec)
logger.success("LamNI session loaded.")
elif args.session == "cSAXS":
elif _args.session.lower() == "csaxs":
print("Loading cSAXS session")
from bec_plugins.bec_client.plugins.cSAXS import *
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
logger.success("cSAXS session loaded.")
# SETUP BEAMLINE INFO
from bec_client.plugins.SLS.sls_info import OperatorInfo, SLSInfo
from bec_ipython_client.plugins.SLS.sls_info import OperatorInfo, SLSInfo
from bec_plugins.bec_client.plugins.cSAXS.beamline_info import BeamlineInfo
from csaxs_bec.bec_ipython_client.plugins.cSAXS.beamline_info import BeamlineInfo
bec._beamline_mixin._bl_info_register(BeamlineInfo)
bec._beamline_mixin._bl_info_register(SLSInfo)
bec._beamline_mixin._bl_info_register(OperatorInfo)
# SETUP PROMPTS
bec._ip.prompts.username = args.session
bec._ip.prompts.session_name = _session_name
bec._ip.prompts.status = 1
# REGISTER BEAMLINE CHECKS
from bec_lib.bl_conditions import (
FastOrbitFeedbackCondition,
LightAvailableCondition,
ShutterCondition,
)
# _fast_orbit_feedback_condition = FastOrbitFeedbackCondition(dev.sls_fast_orbit_feedback)
_light_available_condition = LightAvailableCondition(dev.sls_machine_status)
_shutter_condition = ShutterCondition(dev.x12sa_es1_shutter_status)
# bec.bl_checks.register(_fast_orbit_feedback_condition)
bec.bl_checks.register(_light_available_condition)
bec.bl_checks.register(_shutter_condition)

View File

@@ -0,0 +1,32 @@
"""
Pre-startup script for BEC client. This script is executed before the BEC client
is started. It can be used to add additional command line arguments.
"""
import os
from bec_lib.service_config import ServiceConfig
import csaxs_bec
def extend_command_line_args(parser):
"""
Extend the command line arguments of the BEC client.
"""
parser.add_argument("--session", help="Session name", type=str, default="cSAXS")
return parser
# def get_config() -> ServiceConfig:
# """
# Create and return the ServiceConfig for the plugin repository
# """
# deployment_path = os.path.dirname(os.path.dirname(os.path.dirname(csaxs_bec.__file__)))
# files = os.listdir(deployment_path)
# if "bec_config.yaml" in files:
# return ServiceConfig(config_path=os.path.join(deployment_path, "bec_config.yaml"))
# else:
# return ServiceConfig(redis={"host": "localhost", "port": 6379})

View File

@@ -0,0 +1,63 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
if TYPE_CHECKING: # pragma: no cover
from bec_lib.messages import ScanStatusMessage
class cSAXSUpdate(AutoUpdates):
#######################################################################
################# GUI Callbacks #######################################
#######################################################################
def on_start(self) -> None:
"""
Procedure to run when the auto updates are enabled.
"""
self.start_default_dock()
def on_stop(self) -> None:
"""
Procedure to run when the auto updates are disabled.
"""
def on_scan_open(self, msg: ScanStatusMessage) -> None:
"""
Procedure to run when a scan starts.
Args:
msg (ScanStatusMessage): The scan status message.
"""
if msg.scan_name == "line_scan" and msg.scan_report_devices:
return self.simple_line_scan(msg)
if msg.scan_name == "grid_scan" and msg.scan_report_devices:
return self.simple_grid_scan(msg)
if msg.scan_report_devices:
return self.best_effort(msg)
return None
def on_scan_closed(self, msg: ScanStatusMessage) -> None:
"""
Procedure to run when a scan ends.
Args:
msg (ScanStatusMessage): The scan status message.
"""
def on_scan_abort(self, msg: ScanStatusMessage) -> None:
"""
Procedure to run when a scan is aborted.
Args:
msg (ScanStatusMessage): The scan status message.
"""
class cSAXSUpdateAlignment(AutoUpdates): ...
class cSAXSUpdateScan(AutoUpdates): ...

View File

@@ -0,0 +1,148 @@
# This file was automatically generated by generate_cli.py
# type: ignore
from __future__ import annotations
from bec_lib.logger import bec_logger
from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call, rpc_timeout
logger = bec_logger.logger
# pylint: skip-file
_Widgets = {
"OmnyAlignment": "OmnyAlignment",
"XRayEye": "XRayEye",
}
class OmnyAlignment(RPCBase):
@property
@rpc_call
def enable_live_view(self):
"""
None
"""
@enable_live_view.setter
@rpc_call
def enable_live_view(self):
"""
None
"""
@property
@rpc_call
def user_message(self):
"""
None
"""
@user_message.setter
@rpc_call
def user_message(self):
"""
None
"""
@property
@rpc_call
def sample_name(self):
"""
None
"""
@sample_name.setter
@rpc_call
def sample_name(self):
"""
None
"""
@property
@rpc_call
def enable_move_buttons(self):
"""
None
"""
@enable_move_buttons.setter
@rpc_call
def enable_move_buttons(self):
"""
None
"""
class XRayEye(RPCBase):
@rpc_call
def active_roi(self) -> "BaseROI | None":
"""
Return the currently active ROI, or None if no ROI is active.
"""
@property
@rpc_call
def enable_live_view(self):
"""
Get or set the live view enabled state.
"""
@enable_live_view.setter
@rpc_call
def enable_live_view(self):
"""
Get or set the live view enabled state.
"""
@property
@rpc_call
def user_message(self):
"""
None
"""
@user_message.setter
@rpc_call
def user_message(self):
"""
None
"""
@property
@rpc_call
def sample_name(self):
"""
None
"""
@sample_name.setter
@rpc_call
def sample_name(self):
"""
None
"""
@property
@rpc_call
def enable_move_buttons(self):
"""
None
"""
@enable_move_buttons.setter
@rpc_call
def enable_move_buttons(self):
"""
None
"""
class XRayEye2DControl(RPCBase):
@rpc_call
def remove(self):
"""
Cleanup the BECConnector
"""

View File

@@ -0,0 +1,140 @@
from typing import TypedDict
from bec_widgets.utils.error_popups import SafeSlot
import os
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.ui_loader import UILoader
from qtpy.QtWidgets import QWidget, QPushButton, QLineEdit, QLabel, QVBoxLayout
from bec_qthemes import material_icon
from bec_lib.logger import bec_logger
logger = bec_logger.logger
# class OmnyAlignmentUIComponents(TypedDict):
# moveRightButton: QPushButton
# moveLeftButton: QPushButton
# moveUpButton: QPushButton
# moveDownButton: QPushButton
# image: Image
class OmnyAlignment(BECWidget, QWidget):
USER_ACCESS = ["enable_live_view", "enable_live_view.setter", "user_message", "user_message.setter","sample_name", "sample_name.setter", "enable_move_buttons", "enable_move_buttons.setter"]
PLUGIN = True
ui_file = "./omny_alignment.ui"
def __init__(self, parent=None, **kwargs):
super().__init__(parent=parent, **kwargs)
self._load_ui()
def _load_ui(self):
current_path = os.path.dirname(__file__)
self.ui = UILoader(self).loader(os.path.join(current_path, self.ui_file))
layout = QVBoxLayout()
layout.addWidget(self.ui)
self.setLayout(layout)
icon_options = {"size": (16, 16), "convert_to_pixmap": False}
self.ui.moveRightButton.setText("")
self.ui.moveRightButton.setIcon(
material_icon(icon_name="keyboard_arrow_right", **icon_options)
)
self.ui.moveLeftButton.setText("")
self.ui.moveLeftButton.setIcon(
material_icon(icon_name="keyboard_arrow_left", **icon_options)
)
self.ui.moveUpButton.setText("")
self.ui.moveUpButton.setIcon(
material_icon(icon_name="keyboard_arrow_up", **icon_options)
)
self.ui.moveDownButton.setText("")
self.ui.moveDownButton.setIcon(
material_icon(icon_name="keyboard_arrow_down", **icon_options)
)
self.ui.confirmButton.setText("OK")
self.ui.liveViewSwitch.enabled.connect(self.on_live_view_enabled)
# self.ui.moveUpButton.clicked.connect(self.on_move_up)
@property
def enable_live_view(self):
return self.ui.liveViewSwitch.checked
@enable_live_view.setter
def enable_live_view(self, enable:bool):
self.ui.liveViewSwitch.checked = enable
@property
def user_message(self):
return self.ui.messageLineEdit.text()
@user_message.setter
def user_message(self, message:str):
self.ui.messageLineEdit.setText(message)
@property
def sample_name(self):
return self.ui.sampleLineEdit.text()
@sample_name.setter
def sample_name(self, message:str):
self.ui.sampleLineEdit.setText(message)
@SafeSlot(bool)
def on_live_view_enabled(self, enabled:bool):
from bec_widgets.widgets.plots.image.image import Image
logger.info(f"Live view is enabled: {enabled}")
image: Image = self.ui.image
if enabled:
image.image("cam_xeye")
return
image.disconnect_monitor("cam_xeye")
@property
def enable_move_buttons(self):
move_up:QPushButton = self.ui.moveUpButton
move_down:QPushButton = self.ui.moveDownButton
move_left:QPushButton = self.ui.moveLeftButton
move_right:QPushButton = self.ui.moveRightButton
return move_up.isEnabled() and move_down.isEnabled() and move_left.isEnabled() and move_right.isEnabled()
@enable_move_buttons.setter
def enable_move_buttons(self, enabled:bool):
move_up:QPushButton = self.ui.moveUpButton
move_down:QPushButton = self.ui.moveDownButton
move_left:QPushButton = self.ui.moveLeftButton
move_right:QPushButton = self.ui.moveRightButton
move_up.setEnabled(enabled)
move_down.setEnabled(enabled)
move_left.setEnabled(enabled)
move_right.setEnabled(enabled)
if __name__ == "__main__":
from qtpy.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
widget = OmnyAlignment()
widget.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1 @@
{'files': ['omny_alignment.py']}

View File

@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>988</width>
<height>821</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="2">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="2">
<widget class="QPushButton" name="moveRightButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="moveLeftButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="moveUpButton">
<property name="text">
<string>Up</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="moveDownButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="confirmButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QLineEdit" name="sampleLineEdit"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="messageLineEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Sample</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Message</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="3">
<widget class="Image" name="image">
<property name="enable_toolbar" stdset="0">
<bool>false</bool>
</property>
<property name="inner_axes" stdset="0">
<bool>false</bool>
</property>
<property name="monitor" stdset="0">
<string>cam_xeye</string>
</property>
<property name="rotation" stdset="0">
<number>3</number>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="ToggleSwitch" name="liveViewSwitch"/>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Live View</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Image</class>
<extends>QWidget</extends>
<header>image</header>
</customwidget>
<customwidget>
<class>ToggleSwitch</class>
<extends>QWidget</extends>
<header>toggle_switch</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,54 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from bec_widgets.utils.bec_designer import designer_material_icon
from csaxs_bec.bec_widgets.widgets.omny_alignment.omny_alignment import OmnyAlignment
DOM_XML = """
<ui language='c++'>
<widget class='OmnyAlignment' name='omny_alignment'>
</widget>
</ui>
"""
class OmnyAlignmentPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
def __init__(self):
super().__init__()
self._form_editor = None
def createWidget(self, parent):
t = OmnyAlignment(parent)
return t
def domXml(self):
return DOM_XML
def group(self):
return ""
def icon(self):
return designer_material_icon(OmnyAlignment.ICON_NAME)
def includeFile(self):
return "omny_alignment"
def initialize(self, form_editor):
self._form_editor = form_editor
def isContainer(self):
return False
def isInitialized(self):
return self._form_editor is not None
def name(self):
return "OmnyAlignment"
def toolTip(self):
return "OmnyAlignment"
def whatsThis(self):
return self.toolTip()

View File

@@ -0,0 +1,15 @@
def main(): # pragma: no cover
from qtpy import PYSIDE6
if not PYSIDE6:
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
return
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
from csaxs_bec.bec_widgets.widgets.omny_alignment.omny_alignment_plugin import OmnyAlignmentPlugin
QPyDesignerCustomWidgetCollection.addCustomWidget(OmnyAlignmentPlugin())
if __name__ == "__main__": # pragma: no cover
main()

View File

@@ -0,0 +1,15 @@
def main(): # pragma: no cover
from qtpy import PYSIDE6
if not PYSIDE6:
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
return
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
from csaxs_bec.bec_widgets.widgets.xray_eye.x_ray_eye_plugin import XRayEyePlugin
QPyDesignerCustomWidgetCollection.addCustomWidget(XRayEyePlugin())
if __name__ == "__main__": # pragma: no cover
main()

View File

@@ -0,0 +1,426 @@
from __future__ import annotations
from bec_lib import bec_logger
from bec_lib.endpoints import MessageEndpoints
from bec_qthemes import material_icon
from bec_widgets import BECWidget, SafeProperty, SafeSlot
from bec_widgets.widgets.plots.image.image import Image
from bec_widgets.widgets.plots.image.setting_widgets.image_roi_tree import ROIPropertyTree
from bec_widgets.widgets.plots.roi.image_roi import BaseROI, CircularROI, RectangularROI
from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch
from qtpy.QtCore import Qt, QTimer
from qtpy.QtWidgets import (
QFrame,
QGridLayout,
QHBoxLayout,
QLabel,
QLineEdit,
QPushButton,
QSizePolicy,
QSpinBox,
QToolButton,
QVBoxLayout,
QWidget,
)
logger = bec_logger.logger
CAMERA = ("cam_xeye", "image")
class XRayEye2DControl(BECWidget, QWidget):
def __init__(self, parent=None, step_size: int = 100, *arg, **kwargs):
super().__init__(parent=parent, *arg, **kwargs)
self.get_bec_shortcuts()
self._step_size = step_size
self.root_layout = QGridLayout(self)
self.setStyleSheet("""
QToolButton {
border: 1px solid;
border-radius: 4px;
}
""")
# Up
self.move_up_button = QToolButton(parent=self)
self.move_up_button.setIcon(material_icon('keyboard_double_arrow_up'))
self.root_layout.addWidget(self.move_up_button, 0, 2)
# Up tweak button
self.move_up_tweak_button = QToolButton(parent=self)
self.move_up_tweak_button.setIcon(material_icon('keyboard_arrow_up'))
self.root_layout.addWidget(self.move_up_tweak_button, 1, 2)
# Left
self.move_left_button = QToolButton(parent=self)
self.move_left_button.setIcon(material_icon('keyboard_double_arrow_left'))
self.root_layout.addWidget(self.move_left_button, 2, 0)
# Left tweak button
self.move_left_tweak_button = QToolButton(parent=self)
self.move_left_tweak_button.setIcon(material_icon('keyboard_arrow_left'))
self.root_layout.addWidget(self.move_left_tweak_button, 2, 1)
# Right
self.move_right_button = QToolButton(parent=self)
self.move_right_button.setIcon(material_icon('keyboard_double_arrow_right'))
self.root_layout.addWidget(self.move_right_button, 2, 4)
# Right tweak button
self.move_right_tweak_button = QToolButton(parent=self)
self.move_right_tweak_button.setIcon(material_icon('keyboard_arrow_right'))
self.root_layout.addWidget(self.move_right_tweak_button, 2, 3)
# Down
self.move_down_button = QToolButton(parent=self)
self.move_down_button.setIcon(material_icon('keyboard_double_arrow_down'))
self.root_layout.addWidget(self.move_down_button, 4, 2)
# Down tweak button
self.move_down_tweak_button = QToolButton(parent=self)
self.move_down_tweak_button.setIcon(material_icon('keyboard_arrow_down'))
self.root_layout.addWidget(self.move_down_tweak_button, 3, 2)
# Connections
self.move_up_button.clicked.connect(lambda: self.move("up", tweak=False))
self.move_up_tweak_button.clicked.connect(lambda: self.move("up", tweak=True))
self.move_down_button.clicked.connect(lambda: self.move("down", tweak=False))
self.move_down_tweak_button.clicked.connect(lambda: self.move("down", tweak=True))
self.move_left_button.clicked.connect(lambda: self.move("left", tweak=False))
self.move_left_tweak_button.clicked.connect(lambda: self.move("left", tweak=True))
self.move_right_button.clicked.connect(lambda: self.move("right", tweak=False))
self.move_right_tweak_button.clicked.connect(lambda: self.move("right", tweak=True))
@SafeProperty(int)
def step_size(self) -> int:
return self._step_size
@step_size.setter
def step_size(self, step_size: int):
self._step_size = step_size
@SafeSlot(bool)
def enable_controls_hor(self, enable: bool):
self.move_left_button.setEnabled(enable)
self.move_left_tweak_button.setEnabled(enable)
self.move_right_button.setEnabled(enable)
self.move_right_tweak_button.setEnabled(enable)
@SafeSlot(bool)
def enable_controls_ver(self, enable: bool):
self.move_up_button.setEnabled(enable)
self.move_up_tweak_button.setEnabled(enable)
self.move_down_button.setEnabled(enable)
self.move_down_tweak_button.setEnabled(enable)
def move(self, direction: str, tweak: bool = False):
step = self._step_size
if tweak:
step = int(self._step_size / 5)
if direction == "up":
self.dev.omny_xray_gui.mvy.set(step)
elif direction == "down":
self.dev.omny_xray_gui.mvy.set(-step)
elif direction == "left":
self.dev.omny_xray_gui.mvx.set(-step)
elif direction == "right":
self.dev.omny_xray_gui.mvx.set(step)
else:
logger.warning(f"Unknown direction {direction} for move command.")
class XRayEye(BECWidget, QWidget):
USER_ACCESS = ["active_roi", "enable_live_view", "enable_live_view.setter", "user_message", "user_message.setter",
"sample_name", "sample_name.setter", "enable_move_buttons", "enable_move_buttons.setter"]
PLUGIN = True
def __init__(self, parent=None, **kwargs):
super().__init__(parent=parent, **kwargs)
self.get_bec_shortcuts()
self._init_ui()
self._make_connections()
# Connection to redis endpoints
self.bec_dispatcher.connect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui"))
self.connect_motors()
self.resize(800, 600)
QTimer.singleShot(0, self._init_gui_trigger)
def _init_ui(self):
self.core_layout = QHBoxLayout(self)
self.image = Image(parent=self)
self.image.enable_toolbar = False # Disable default toolbar to not allow to user set anything
self.image.inner_axes = False # Disable inner axes to maximize image area
self.image.plot_item.vb.invertY(True) # #TODO Invert y axis to match logic of LabView GUI
# Control panel on the right: vertical layout inside a fixed-width widget
self.control_panel = QWidget(parent=self)
self.control_panel_layout = QVBoxLayout(self.control_panel)
self.control_panel_layout.setContentsMargins(0, 0, 0, 0)
self.control_panel_layout.setSpacing(10)
# ROI toolbar + Live toggle (header row)
self.roi_manager = ROIPropertyTree(parent=self, image_widget=self.image, compact=True,
compact_orientation="horizontal")
header_row = QHBoxLayout()
header_row.setContentsMargins(0, 0, 0, 0)
header_row.setSpacing(8)
header_row.addWidget(self.roi_manager, 0)
header_row.addStretch()
self.live_preview_label = QLabel("Live Preview", parent=self)
self.live_preview_toggle = ToggleSwitch(parent=self)
self.live_preview_toggle.checked = False
header_row.addWidget(self.live_preview_label, 0, Qt.AlignVCenter)
header_row.addWidget(self.live_preview_toggle, 0, Qt.AlignVCenter)
self.control_panel_layout.addLayout(header_row)
# separator
self.control_panel_layout.addWidget(self._create_separator())
# 2D Positioner (fixed size)
self.motor_control_2d = XRayEye2DControl(parent=self)
self.control_panel_layout.addWidget(self.motor_control_2d, 0, Qt.AlignTop | Qt.AlignCenter)
# separator
self.control_panel_layout.addWidget(self._create_separator())
# Step size label
step_size_form = QGridLayout()
# General Step size
self.step_size = QSpinBox(parent=self)
self.step_size.setRange(10, 100)
self.step_size.setSingleStep(10)
self.step_size.setValue(100)
# Submit button
self.submit_button = QPushButton("Submit", parent=self)
# Add to layout form
step_size_form.addWidget(QLabel("Horizontal", parent=self), 0, 0)
step_size_form.addWidget(self.step_size, 0, 1)
step_size_form.addWidget(QLabel("Vertical", parent=self), 1, 0)
step_size_form.addWidget(self.submit_button, 2, 0, 1, 2)
# Add form to control panel
self.control_panel_layout.addLayout(step_size_form)
# Push form to bottom
self.control_panel_layout.addStretch()
# Sample/Message form (bottom)
form = QGridLayout()
self.sample_name_line_edit = QLineEdit(parent=self)
self.sample_name_line_edit.setReadOnly(True)
form.addWidget(QLabel("Sample", parent=self), 0, 0)
form.addWidget(self.sample_name_line_edit, 0, 1)
self.message_line_edit = QLineEdit(parent=self)
self.message_line_edit.setReadOnly(True)
form.addWidget(QLabel("Message", parent=self), 1, 0)
form.addWidget(self.message_line_edit, 1, 1)
self.control_panel_layout.addLayout(form)
# Fix panel width and allow vertical expansion
self.control_panel.adjustSize()
p_hint = self.control_panel.sizeHint()
self.control_panel.setFixedWidth(p_hint.width())
self.control_panel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
# Core Layout: image (expanding) | control panel (fixed)
self.core_layout.addWidget(self.image)
self.core_layout.addWidget(self.control_panel)
def _make_connections(self):
# Fetch initial state
self.on_live_view_enabled(True)
self.step_size.setValue(self.motor_control_2d.step_size)
# Make connections
self.live_preview_toggle.enabled.connect(self.on_live_view_enabled)
self.step_size.valueChanged.connect(lambda x: self.motor_control_2d.setProperty("step_size", x))
self.submit_button.clicked.connect(self.submit)
def _create_separator(self):
sep = QFrame(parent=self)
sep.setFrameShape(QFrame.HLine)
sep.setFrameShadow(QFrame.Sunken)
sep.setLineWidth(1)
return sep
def _init_gui_trigger(self):
self.dev.omny_xray_gui.read()
################################################################################
# Device Connection logic
################################################################################
def connect_motors(self):
""" Checks one of the possible motors for flomni, omny and lamni setup."""
possible_motors = ['osamroy', 'lsamrot', 'fsamroy']
for motor in possible_motors:
if motor in self.dev:
self.bec_dispatcher.connect_slot(self.on_tomo_angle_readback, MessageEndpoints.device_readback(motor))
logger.info(f"Succesfully connected to {motor}")
################################################################################
# Properties ported from the original OmnyAlignment, can be adjusted as needed
################################################################################
@SafeProperty(str)
def user_message(self):
return self.message_line_edit.text()
@user_message.setter
def user_message(self, message: str):
self.message_line_edit.setText(message)
@SafeProperty(str)
def sample_name(self):
return self.sample_name_line_edit.text()
@sample_name.setter
def sample_name(self, message: str):
self.sample_name_line_edit.setText(message)
@SafeProperty(bool)
def enable_move_buttons(self):
return self.motor_control_2d.isEnabled()
@enable_move_buttons.setter
def enable_move_buttons(self, enabled: bool):
self.motor_control_2d.setEnabled(enabled)
def active_roi(self) -> BaseROI | None:
"""Return the currently active ROI, or None if no ROI is active."""
return self.roi_manager.single_active_roi
################################################################################
# Slots ported from the original OmnyAlignment, can be adjusted as needed
################################################################################
@SafeSlot()
def get_roi_coordinates(self) -> dict | None:
"""Get the coordinates of the currently active ROI."""
roi = self.roi_manager.single_active_roi
if roi is None:
logger.warning("No active ROI")
return None
logger.info(f"Active ROI coordinates: {roi.get_coordinates()}")
return roi.get_coordinates()
@SafeSlot(bool)
def on_live_view_enabled(self, enabled: bool):
logger.info(f"Live view is enabled: {enabled}")
self.live_preview_toggle.blockSignals(True)
if enabled:
self.live_preview_toggle.checked = enabled
self.image.image(CAMERA)
self.live_preview_toggle.blockSignals(False)
return
self.image.disconnect_monitor(CAMERA)
self.live_preview_toggle.checked = enabled
self.live_preview_toggle.blockSignals(False)
@SafeSlot(bool, bool)
def on_motors_enable(self, x_enable: bool, y_enable: bool):
"""
Enable/Disable motor controls
Args:
x_enable(bool): enable x motor controls
y_enable(bool): enable y motor controls
"""
self.motor_control_2d.enable_controls_hor(x_enable)
self.motor_control_2d.enable_controls_ver(y_enable)
@SafeSlot(int)
def enable_submit_button(self, enable: int):
"""
Enable/disable submit button.
Args:
enable(int): -1 disable else enable
"""
if enable == -1:
self.submit_button.setEnabled(False)
else:
self.submit_button.setEnabled(True)
@SafeSlot(bool, bool)
def on_tomo_angle_readback(self, data: dict, meta: dict):
#TODO implement if needed
print(f"data: {data}")
print(f"meta: {meta}")
@SafeSlot(dict, dict)
def device_updates(self, data: dict, meta: dict):
"""
Slot to handle device updates from omny_xray_gui device.
Args:
data(dict): data from device
meta(dict): metadata from device
"""
signals = data.get('signals')
enable_live_preview = signals.get("omny_xray_gui_update_frame_acq").get('value')
enable_x_motor = signals.get("omny_xray_gui_enable_mv_x").get('value')
enable_y_motor = signals.get("omny_xray_gui_enable_mv_y").get('value')
self.on_live_view_enabled(bool(enable_live_preview))
self.on_motors_enable(bool(enable_x_motor), bool(enable_y_motor))
# Signals from epics gui device
# send message
user_message = signals.get("omny_xray_gui_send_message").get('value')
self.user_message = user_message
# sample name
sample_message = signals.get("omny_xray_gui_sample_name").get('value')
self.sample_name = sample_message
# enable frame acquisition
update_frame_acq = signals.get("omny_xray_gui_update_frame_acq").get('value')
self.on_live_view_enabled(bool(update_frame_acq))
# enable submit button
enable_submit_button = signals.get("omny_xray_gui_submit").get('value')
self.enable_submit_button(enable_submit_button)
@SafeSlot()
def submit(self):
"""Execute submit action by submit button."""
if self.roi_manager.single_active_roi is None:
logger.warning("No active ROI")
return
roi_coordinates = self.roi_manager.single_active_roi.get_coordinates()
roi_center_x = roi_coordinates['center_x']
roi_center_y = roi_coordinates['center_y']
# Case of rectangular ROI
if isinstance(self.roi_manager.single_active_roi, RectangularROI):
roi_width = roi_coordinates['width']
roi_height = roi_coordinates['height']
elif isinstance(self.roi_manager.single_active_roi, CircularROI):
roi_width = roi_coordinates['diameter']
roi_height = roi_coordinates['radius']
else:
logger.warning("Unsupported ROI type for submit action.")
return
print(f"current roi: x:{roi_center_x}, y:{roi_center_y}, w:{roi_width},h:{roi_height}") #TODO remove when will be not needed for debugging
# submit roi coordinates
step = int(self.dev.omny_xray_gui.step.read().get("omny_xray_gui_step").get('value'))
xval_x = getattr(self.dev.omny_xray_gui.xval_x, f"xval_x_{step}").set(roi_center_x)
xval_y = getattr(self.dev.omny_xray_gui.yval_y, f"yval_y_{step}").set(roi_center_y)
width_x = getattr(self.dev.omny_xray_gui.width_x, f"width_x_{step}").set(roi_width)
width_y = getattr(self.dev.omny_xray_gui.width_y, f"width_y_{step}").set(roi_height)
self.dev.omny_xray_gui.submit.set(1)
def cleanup(self):
"""Cleanup connections on widget close -> disconnect slots and stop live mode of camera."""
self.bec_dispatcher.disconnect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui"))
getattr(self.dev,CAMERA[0]).live_mode = False
super().cleanup()
if __name__ == "__main__":
import sys
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
win = XRayEye()
win.resize(1000, 800)
win.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1 @@
{'files': ['x_ray_eye.py']}

View File

@@ -0,0 +1,57 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from bec_widgets.utils.bec_designer import designer_material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtWidgets import QWidget
from csaxs_bec.bec_widgets.widgets.xray_eye.x_ray_eye import XRayEye
DOM_XML = """
<ui language='c++'>
<widget class='XRayEye' name='x_ray_eye'>
</widget>
</ui>
"""
class XRayEyePlugin(QDesignerCustomWidgetInterface): # pragma: no cover
def __init__(self):
super().__init__()
self._form_editor = None
def createWidget(self, parent):
if parent is None:
return QWidget()
t = XRayEye(parent)
return t
def domXml(self):
return DOM_XML
def group(self):
return ""
def icon(self):
return designer_material_icon(XRayEye.ICON_NAME)
def includeFile(self):
return "x_ray_eye"
def initialize(self, form_editor):
self._form_editor = form_editor
def isContainer(self):
return False
def isInitialized(self):
return self._form_editor is not None
def name(self):
return "XRayEye"
def toolTip(self):
return "XRayEye"
def whatsThis(self):
return self.toolTip()

View File

View File

View File

@@ -0,0 +1,11 @@
import os
def setup_epics_ca():
# os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO"
# os.environ["EPICS_CA_ADDR_LIST"] = "129.129.122.255 sls-x12sa-cagw.psi.ch:5836"
os.environ["PYTHONIOENCODING"] = "latin1"
def run():
setup_epics_ca()

View File

View File

@@ -0,0 +1,212 @@
# This configuration file was used for the cSAXS beamtimes in September 2023
##################################################
#############Config for cSAXS SAXS imaging########
##################################################
bpm4i:
description: 'XBPM 4: integrated counts'
deviceClass: ophyd.EpicsSignalRO
deviceConfig:
read_pv: X12SA-OP1-SCALER.
deviceTags:
- monitor
enabled: true
readOnly: false
onFailure: buffer
readoutPriority: baseline
softwareTrigger: false
mokev:
description: Monochromator energy in keV
deviceClass: csaxs_bec.devices.epics.specMotors.EnergyKev
deviceConfig:
read_pv: X12SA-OP-MO:ROX2
deviceTags:
- monitor
enabled: true
readOnly: false
onFailure: buffer
readoutPriority: baseline
softwareTrigger: false
# mcs:
# description: Mcs scalar card for transmission readout
# deviceClass: csaxs_bec.devices.epics.mcs_csaxs.MCScSAXS
# deviceConfig:
# prefix: 'X12SA-MCS:'
# mcs_config:
# num_lines: 1
# deviceTags:
# - cSAXS
# - mcs
# onFailure: buffer
# enabled: true
# readoutPriority: monitored
# softwareTrigger: false
eiger9m:
description: Eiger9m HPC area detector 9M
deviceClass: csaxs_bec.devices.epics.eiger9m_csaxs.Eiger9McSAXS
deviceConfig:
prefix: 'X12SA-ES-EIGER9M:'
deviceTags:
- cSAXS
- eiger9m
onFailure: buffer
enabled: true
readoutPriority: async
softwareTrigger: false
falcon:
description: Falcon detector x-ray fluoresence
deviceClass: csaxs_bec.devices.epics.falcon_csaxs.FalconcSAXS
deviceConfig:
prefix: 'X12SA-SITORO:'
deviceTags:
- cSAXS
- falcon
onFailure: buffer
enabled: true
readoutPriority: async
softwareTrigger: false
pilatus_2:
description: Pilatus2 HPC area detector 300k
deviceClass: csaxs_bec.devices.epics.pilatus_csaxs.PilatuscSAXS
deviceConfig:
prefix: 'X12SA-ES-PILATUS300K:'
deviceTags:
- cSAXS
- pilatus_2
onFailure: buffer
enabled: true
readoutPriority: async
softwareTrigger: false
samx:
description: SGalil motor stage
deviceClass: csaxs_bec.devices.omny.galil.SGalilMotor
deviceConfig:
axis_Id: "E"
host: '129.129.122.26'
port: 23
sign: -1
limits:
- -181
- -0.1
deviceTags:
- cSAXS
- sgalil
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
samy:
description: SGalil motor stage
deviceClass: csaxs_bec.devices.omny.galil.SGalilMotor
deviceConfig:
axis_Id: "C"
host: '129.129.122.26'
port: 23
sign: -1
limits:
- -66
- -12
deviceTags:
- cSAXS
- sgalil
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
micfoc:
description: Focusing motor of Microscope stage
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES06
motor_resolution: 0.00125
base_velocity: 0.25
velocity: 2.5
backlash_distance: 0.125
acceleration: 0.4
user_offset_dir: 0
deviceTags:
- cSAXS
- micfoc
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
owis_samx:
description: Owis motor stage samx
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES01
motor_resolution: 0.00125
base_velocity: 0.0625
velocity: 10
backlash_distance: 0.125
acceleration: 0.2
user_offset_dir: 0
deviceTags:
- cSAXS
- owis_samx
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
owis_samy:
description: Owis motor stage samx
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES02
motor_resolution: 0.00125
base_velocity: 0.0625
velocity: 10
backlash_distance: 0.125
acceleration: 0.2
user_offset_dir: 0
deviceTags:
- cSAXS
- owis_samx
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
rotx:
description: Rotation stage rotx
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES05
motor_resolution: 0.0025
base_velocity: 0.5
velocity: 7.5
backlash_distance: 0.25
acceleration: 0.2
user_offset_dir: 1
limits:
- -0.1
- 0.1
deviceTags:
- cSAXS
- rotx
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
roty:
description: Rotation stage rotx
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES04
motor_resolution: 0.0025
base_velocity: 0.5
velocity: 7.5
backlash_distance: 0.25
acceleration: 0.2
user_offset_dir: 1
limits:
- -0.1
- 0.1
deviceTags:
- cSAXS
- rotx
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
ddg1:
description: Main delay Generator for triggering
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DDG1
enabled: true
deviceConfig:
prefix: 'X12SA-CPCL-DDG1:'
onFailure: raise
readOnly: false
readoutPriority: baseline
softwareTrigger: true
ddg2:
description: Detector delay Generator for trigger burst
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DDG2
enabled: true
deviceConfig:
prefix: 'X12SA-CPCL-DDG2:'
onFailure: raise
readOnly: false
readoutPriority: baseline
softwareTrigger: false
mcs:
description: Mcs scalar card for transmission readout
deviceClass: csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs.MCSCardCSAXS
deviceConfig:
prefix: 'X12SA-MCS:'
onFailure: raise
enabled: true
readoutPriority: monitored
softwareTrigger: false
ids_cam:
description: IDS camera for live image acquisition
deviceClass: csaxs_bec.devices.ids_cameras.IDSCamera
deviceConfig:
camera_id: 201
bits_per_pixel: 24
m_n_colormode: 1
live_mode: True
onFailure: raise
enabled: true
readoutPriority: async
softwareTrigger: True
eiger_1_5:
description: Eiger 1.5M in-vacuum detector
deviceClass: csaxs_bec.devices.jungfraujoch.eiger_1_5m.Eiger1_5M
deviceConfig:
detector_distance: 100
beam_center: [0, 0]
onFailure: raise
enabled: true
readoutPriority: async
softwareTrigger: False

View File

@@ -0,0 +1,8 @@
optics:
- !include ./optics_hutch.yaml
frontend:
- !include ./frontend.yaml
endstation:
- !include ./endstation.yaml

View File

@@ -0,0 +1,468 @@
############################################################
#################### flOMNI Galil motors ###################
############################################################
feyex:
description: Xray eye X
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: D
host: mpc2844.psi.ch
limits:
- -30
- -1
port: 8082
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: -16.267
out: -1
feyey:
description: Xray eye Y
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: E
host: mpc2844.psi.ch
limits:
- -1
- -10
port: 8082
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: -10.467
fheater:
description: Heater Y
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: C
host: mpc2844.psi.ch
limits:
- -15
- 0
port: 8082
sign: -1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
foptx:
description: Optics X
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: B
host: mpc2844.psi.ch
limits:
- -17
- -12
port: 8082
sign: 1
enabled: true
onFailure: buffer
readOnly: true
readoutPriority: baseline
userParameter:
in: -13.761
fopty:
description: Optics Y
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: F
host: mpc2844.psi.ch
limits:
- 0
- 4
port: 8082
sign: 1
enabled: true
onFailure: buffer
readOnly: true
readoutPriority: baseline
userParameter:
in: 0.552
out: 0.752
foptz:
description: Optics Z
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: A
host: mpc2844.psi.ch
limits:
- 0
- 27
port: 8082
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: 23
fsamroy:
description: Sample rotation
deviceClass: csaxs_bec.devices.omny.galil.fupr_ophyd.FuprGalilMotor
deviceConfig:
axis_Id: A
host: mpc2844.psi.ch
limits:
- -5
- 365
port: 8084
sign: -1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
fsamx:
description: Sample coarse X
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: E
host: mpc2844.psi.ch
limits:
- -162
- 0
port: 8081
sign: 1
enabled: true
onFailure: buffer
readOnly: true
readoutPriority: baseline
userParameter:
in: -1.1
fsamy:
description: Sample coarse Y
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: F
host: mpc2844.psi.ch
limits:
- 2
- 3.1
port: 8081
sign: 1
enabled: true
onFailure: buffer
readOnly: true
readoutPriority: baseline
userParameter:
in: 2.75
ftracky:
description: Laser Tracker coarse Y
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: H
host: mpc2844.psi.ch
limits:
- 2.2
- 2.8
port: 8082
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
ftrackz:
description: Laser Tracker coarse Z
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: G
host: mpc2844.psi.ch
limits:
- 4.5
- 5.5
port: 8082
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
ftransx:
description: Sample transer X
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: C
host: mpc2844.psi.ch
limits:
- 0
- 50
port: 8081
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
ftransy:
description: Sample transer Y
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: A
host: mpc2844.psi.ch
limits:
- -100
- 0
port: 8081
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
sensor_voltage: -2.4
ftransz:
description: Sample transer Z
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: B
host: mpc2844.psi.ch
limits:
- 0
- 145
port: 8081
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
ftray:
description: Sample transfer tray
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
deviceConfig:
axis_Id: D
host: mpc2844.psi.ch
limits:
- -200
- 0
port: 8081
sign: -1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
############################################################
#################### flOMNI Sample Names ###################
############################################################
flomni_samples:
description: Sample names and storage
deviceClass: csaxs_bec.devices.omny.flomni_sample_storage.FlomniSampleStorage
deviceConfig: {}
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
############################################################
#################### flOMNI Smaract motors #################
############################################################
fosax:
description: OSA X
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
deviceConfig:
axis_Id: A
host: mpc2844.psi.ch
limits:
- 10.2
- 10.6
port: 3334
sign: -1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: 9.124
out: 5.3
fosay:
description: OSA Y
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
deviceConfig:
axis_Id: B
host: mpc2844.psi.ch
limits:
- -3.1
- -2.9
port: 3334
sign: -1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: 0.367
fosaz:
description: OSA Z
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
deviceConfig:
axis_Id: C
host: mpc2844.psi.ch
limits:
- -6
- -4
port: 3334
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: 8.5
out: 6
############################################################
#################### flOMNI RT motors ######################
############################################################
rtx:
description: flomni rt
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniMotor
deviceConfig:
axis_Id: A
host: mpc2844.psi.ch
port: 2222
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: on_request
userParameter:
low_signal: 10000
min_signal: 9000
rt_pid_voltage: -0.06219
rty:
description: flomni rt
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniMotor
deviceConfig:
axis_Id: B
host: mpc2844.psi.ch
port: 2222
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: on_request
userParameter:
tomo_additional_offsety: 0
rtz:
description: flomni rt
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniMotor
deviceConfig:
axis_Id: C
host: mpc2844.psi.ch
port: 2222
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: on_request
############################################################
####################### Cameras ############################
############################################################
cam_flomni_gripper:
description: Camera sample changer
deviceClass: csaxs_bec.devices.omny.webcam_viewer.WebcamViewer
deviceConfig:
url: http://flomnicamserver:5000/video_high
num_rotation_90: 3
transpose: false
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: on_request
cam_flomni_overview:
description: Camera flomni overview
deviceClass: csaxs_bec.devices.omny.webcam_viewer.WebcamViewer
deviceConfig:
url: http://flomnicamserver:5001/video_high
num_rotation_90: 3
transpose: false
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: on_request
cam_xeye:
description: Camera flOMNI Xray eye ID1
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
deviceConfig:
camera_id: 1
bits_per_pixel: 24
num_rotation_90: 3
transpose: false
force_monochrome: true
m_n_colormode: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: async
cam_ids_rgb:
description: Camera flOMNI Xray eye ID203
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
deviceConfig:
camera_id: 203
bits_per_pixel: 24
num_rotation_90: 3
transpose: false
force_monochrome: true
m_n_colormode: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: async
# ############################################################
# ################### flOMNI temperatures ####################
# ############################################################
flomni_temphum:
description: flOMNI Temperatures and humidity
deviceClass: csaxs_bec.devices.omny.flomni_temp_and_humidity.FlomniTempHum
deviceConfig: {}
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
# ############################################################
# ########## OMNY / flOMNI / LamNI fast shutter ##############
# ############################################################
omnyfsh:
description: omnyfsh connects to read fast shutter at X12 if in that network
deviceClass: csaxs_bec.devices.omny.shutter.OMNYFastEpicsShutter
deviceConfig: {}
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
############################################################
#################### GUI Signals ###########################
############################################################
omny_xray_gui:
description: Gui Epics signals
deviceClass: csaxs_bec.devices.omny.xray_epics_gui.OMNYXRayEpicsGUI
deviceConfig: {}
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: on_request
calculated_signal:
description: Calculated signal from alignment for fit
deviceClass: ophyd_devices.ComputedSignal
deviceConfig:
compute_method: "def just_rand():\n return 42"
enabled: true
readOnly: false
readoutPriority: baseline

View File

@@ -0,0 +1,215 @@
idgap:
description: 'Motor to control the IDGap of X12SA'
deviceClass: ophyd_devices.devices.undulator.UndulatorGap
deviceConfig:
prefix: 'X12SA-UIND:'
onFailure: raise # Consider changing to buffer
enabled: true
readoutPriority: baseline
readOnly: false # put to false if you like to move it
softwareTrigger: false
xbpm1x:
description: 'X-ray BPM1 in frontend translation x'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-XBPM1:TRX'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
xbpm1y:
description: 'X-ray BPM1 in frontend translation y'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-XBPM1:TRY'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
sl1xr:
description: 'slit 1 (frontend) x ring'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-SL1:TRXR'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
sl1xw:
description: 'slit 1 (frontend) x wall'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-SL1:TRXW'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
sl1yb:
description: 'slit 1 (frontend) y bottom'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-SL1:TRYB'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
sl1yt:
description: 'slit 1 (frontend) y top'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-SL1:TRYT'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
sl1xc:
description: 'slit 1 (frontend) x center'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-SL1:CENTERX'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
sl1xs:
description: 'slit 1 (frontend) x size'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-SL1:SIZEX'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
sl1yc:
description: 'slit 1 (frontend) y center'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-SL1:CENTERY'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
sl1ys:
description: 'slit 1 (frontend) y size'
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: 'X12SA-FE-SL1:SIZEY'
onFailure: raise
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
deviceTags:
- cSAXS
- frontend
#####################################
#### XBPM ###########################
#####################################
# Note: The following device may not be relevant anymore
# and can be fully replaced by the combined device "xbpm1", see below
xbpm1c1:
description: 'XBPM1 (frontend) current 1'
deviceClass: ophyd.EpicsSignalRO
deviceConfig:
read_pv: 'X12SA-FE-XBPM1:Current1:MeanValue_RBV'
onFailure: raise
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
xbpm1c2:
description: 'XBPM1 (frontend) current 2'
deviceClass: ophyd.EpicsSignalRO
deviceConfig:
read_pv: 'X12SA-FE-XBPM1:Current2:MeanValue_RBV'
onFailure: raise
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
xbpm1c3:
description: 'XBPM1 (frontend) current 3'
deviceClass: ophyd.EpicsSignalRO
deviceConfig:
read_pv: 'X12SA-FE-XBPM1:Current3:MeanValue_RBV'
onFailure: raise
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
xbpm1c4:
description: 'XBPM1 (frontend) current 4'
deviceClass: ophyd.EpicsSignalRO
deviceConfig:
read_pv: 'X12SA-FE-XBPM1:Current4:MeanValue_RBV'
onFailure: raise
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
############################################
######### End of xbpm sub devices ##########
############################################
xbpm1:
description: 'XBPM1 (frontend)'
deviceClass: csaxs_bec.devices.epics.xbpms.BPMDevice
deviceConfig:
prefix: 'X12SA-FE-XBPM1'
onFailure: raise
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false

View File

@@ -0,0 +1,261 @@
############################################################
#################### LamNI Galil motors ####################
############################################################
leyex:
description: Xray eye X
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
deviceConfig:
axis_Id: G
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8081
sign: -1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: 14.117
leyey:
description: Xray eye Y
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
deviceConfig:
axis_Id: H
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8081
sign: -1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: 48.069
out: 0.5
loptx:
description: Optics X
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
deviceConfig:
axis_Id: E
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8081
sign: 1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: -0.244
out: -0.699
lopty:
description: Optics Y
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
deviceConfig:
axis_Id: F
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8081
sign: 1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: 3.724
out: 3.53
loptz:
description: Optics Z
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
deviceConfig:
axis_Id: D
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8081
sign: -1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
lsamrot:
description: Sample rotation
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
deviceConfig:
axis_Id: C
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8081
sign: 1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
lsamx:
description: Sample coarse X
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
deviceConfig:
axis_Id: A
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8081
sign: -1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
center: 8.768
lsamy:
description: Sample coarse Y
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
deviceConfig:
axis_Id: B
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8081
sign: 1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
center: 10.041
############################################################
################ LamNI Smaract motors ######################
############################################################
losax:
description: OSA X
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
deviceConfig:
axis_Id: A
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8085
sign: -1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: -1.442
losay:
description: OSA Y
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
deviceConfig:
axis_Id: B
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8085
sign: -1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: -0.171
out: 3.8
losaz:
description: OSA Z
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
deviceConfig:
axis_Id: C
host: mpc2680.psi.ch
limits:
- 0
- 0
port: 8085
sign: 1
deviceTags:
- lamni
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
userParameter:
in: -1
out: -3
############################################################
#################### flOMNI RT motors ######################
############################################################
rtx:
deviceClass: csaxs_bec.devices.omny.rt.rt_lamni_ophyd.RtLamniMotor
deviceConfig:
axis_Id: A
device_access: true
host: mpc2680.psi.ch
labels: rtx
limits:
- 0
- 0
port: 3333
sign: 1
deviceTags:
- lamni
readoutPriority: baseline
enabled: true
readOnly: False
rty:
deviceClass: csaxs_bec.devices.omny.rt.rt_lamni_ophyd.RtLamniMotor
deviceConfig:
axis_Id: B
device_access: true
host: mpc2680.psi.ch
labels: rty
limits:
- 0
- 0
port: 3333
sign: 1
deviceTags:
- lamni
readoutPriority: baseline
enabled: true
readOnly: False

View File

@@ -0,0 +1,38 @@
############################################################
#################### npoint motors #########################
############################################################
npx:
description: nPoint x axis on the big npoint controller
deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis
deviceConfig:
axis_Id: A
host: "nPoint000003.psi.ch"
limits:
- -50
- 50
port: 23
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
deviceTags:
- npoint
npy:
description: nPoint y axis on the big npoint controller
deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis
deviceConfig:
axis_Id: B
host: "nPoint000003.psi.ch"
limits:
- -50
- 50
port: 23
sign: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
deviceTags:
- npoint

Some files were not shown because too many files have changed in this diff Show More